create-crm-tmp 1.1.3 → 2.1.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 (276) hide show
  1. package/bin/create-crm-tmp.js +56 -35
  2. package/package.json +1 -1
  3. package/template/.prettierignore +2 -0
  4. package/template/README.md +230 -115
  5. package/template/components.json +22 -0
  6. package/template/eslint.config.mjs +13 -0
  7. package/template/exemple-contacts.csv +54 -0
  8. package/template/next.config.ts +41 -1
  9. package/template/package.json +63 -15
  10. package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
  11. package/template/prisma/schema.prisma +311 -67
  12. package/template/src/app/(auth)/invite/[token]/page.tsx +28 -29
  13. package/template/src/app/(auth)/layout.tsx +1 -1
  14. package/template/src/app/(auth)/reset-password/complete/page.tsx +21 -27
  15. package/template/src/app/(auth)/reset-password/page.tsx +14 -10
  16. package/template/src/app/(auth)/reset-password/verify/page.tsx +14 -10
  17. package/template/src/app/(auth)/signin/page.tsx +34 -23
  18. package/template/src/app/(dashboard)/agenda/page.tsx +3655 -2357
  19. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
  20. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +609 -338
  21. package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
  22. package/template/src/app/(dashboard)/automatisation/page.tsx +463 -186
  23. package/template/src/app/(dashboard)/closing/page.tsx +517 -469
  24. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6151 -4210
  25. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1702 -0
  26. package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
  27. package/template/src/app/(dashboard)/contacts/page.tsx +4124 -2130
  28. package/template/src/app/(dashboard)/dashboard/page.tsx +119 -105
  29. package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
  30. package/template/src/app/(dashboard)/error.tsx +37 -0
  31. package/template/src/app/(dashboard)/layout.tsx +6 -2
  32. package/template/src/app/(dashboard)/loading.tsx +5 -0
  33. package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
  34. package/template/src/app/(dashboard)/settings/page.tsx +1773 -3362
  35. package/template/src/app/(dashboard)/templates/page.tsx +504 -303
  36. package/template/src/app/(dashboard)/users/list/page.tsx +364 -355
  37. package/template/src/app/(dashboard)/users/page.tsx +279 -310
  38. package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
  39. package/template/src/app/(dashboard)/users/roles/page.tsx +169 -140
  40. package/template/src/app/api/agenda/google-events/route.ts +92 -0
  41. package/template/src/app/api/audit-logs/route.ts +1 -1
  42. package/template/src/app/api/auth/check-active/route.ts +3 -2
  43. package/template/src/app/api/auth/google/callback/route.ts +8 -5
  44. package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
  45. package/template/src/app/api/auth/google/route.ts +2 -1
  46. package/template/src/app/api/auth/google/status/route.ts +7 -31
  47. package/template/src/app/api/companies/[id]/activities/route.ts +129 -0
  48. package/template/src/app/api/companies/[id]/route.ts +194 -0
  49. package/template/src/app/api/companies/export/route.ts +206 -0
  50. package/template/src/app/api/companies/route.ts +196 -0
  51. package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
  52. package/template/src/app/api/contact-views/[id]/route.ts +197 -0
  53. package/template/src/app/api/contact-views/route.ts +146 -0
  54. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +55 -0
  55. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +20 -48
  56. package/template/src/app/api/contacts/[id]/files/route.ts +125 -186
  57. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
  58. package/template/src/app/api/contacts/[id]/interactions/route.ts +45 -8
  59. package/template/src/app/api/contacts/[id]/kyc/route.ts +81 -0
  60. package/template/src/app/api/contacts/[id]/meet/route.ts +55 -29
  61. package/template/src/app/api/contacts/[id]/route.ts +184 -21
  62. package/template/src/app/api/contacts/[id]/send-email/route.ts +33 -11
  63. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +67 -0
  64. package/template/src/app/api/contacts/export/route.ts +22 -31
  65. package/template/src/app/api/contacts/import/route.ts +77 -44
  66. package/template/src/app/api/contacts/import-preview/route.ts +139 -0
  67. package/template/src/app/api/contacts/origins/route.ts +63 -0
  68. package/template/src/app/api/contacts/route.ts +322 -57
  69. package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
  70. package/template/src/app/api/dashboard/stats/route.ts +9 -292
  71. package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -3
  72. package/template/src/app/api/dashboard/widgets/route.ts +19 -19
  73. package/template/src/app/api/dev/reminders/test/route.ts +114 -0
  74. package/template/src/app/api/editor/upload-image/route.ts +61 -0
  75. package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
  76. package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
  77. package/template/src/app/api/integrations/google-sheet/sync/route.ts +28 -542
  78. package/template/src/app/api/invite/complete/route.ts +20 -23
  79. package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
  80. package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
  81. package/template/src/app/api/reminders/clear/route.ts +120 -0
  82. package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
  83. package/template/src/app/api/reminders/route.ts +165 -39
  84. package/template/src/app/api/reminders/state/route.ts +164 -0
  85. package/template/src/app/api/reset-password/complete/route.ts +11 -13
  86. package/template/src/app/api/reset-password/request/route.ts +1 -1
  87. package/template/src/app/api/reset-password/verify/route.ts +1 -1
  88. package/template/src/app/api/send/route.ts +25 -47
  89. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
  90. package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
  91. package/template/src/app/api/settings/company/route.ts +19 -26
  92. package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
  93. package/template/src/app/api/settings/google-ads/route.ts +34 -23
  94. package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
  95. package/template/src/app/api/settings/google-calendar/route.ts +124 -0
  96. package/template/src/app/api/settings/google-sheet/[id]/route.ts +48 -23
  97. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +56 -32
  98. package/template/src/app/api/settings/google-sheet/preview/route.ts +110 -0
  99. package/template/src/app/api/settings/google-sheet/route.ts +34 -23
  100. package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
  101. package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
  102. package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -24
  103. package/template/src/app/api/settings/meta-leads/route.ts +34 -25
  104. package/template/src/app/api/settings/smtp/route.ts +53 -6
  105. package/template/src/app/api/settings/statuses/[id]/route.ts +29 -32
  106. package/template/src/app/api/settings/statuses/route.ts +24 -22
  107. package/template/src/app/api/statuses/route.ts +2 -5
  108. package/template/src/app/api/tasks/[id]/attendees/route.ts +36 -13
  109. package/template/src/app/api/tasks/[id]/route.ts +357 -145
  110. package/template/src/app/api/tasks/meet/route.ts +37 -26
  111. package/template/src/app/api/tasks/route.ts +201 -96
  112. package/template/src/app/api/templates/[id]/route.ts +22 -13
  113. package/template/src/app/api/templates/route.ts +22 -5
  114. package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
  115. package/template/src/app/api/users/[id]/route.ts +22 -16
  116. package/template/src/app/api/users/commercials/route.ts +38 -0
  117. package/template/src/app/api/users/for-agenda/route.ts +1 -2
  118. package/template/src/app/api/users/list/route.ts +57 -19
  119. package/template/src/app/api/users/route.ts +89 -34
  120. package/template/src/app/api/webhooks/google-ads/route.ts +40 -1
  121. package/template/src/app/api/webhooks/meta-leads/route.ts +38 -1
  122. package/template/src/app/api/workflows/[id]/route.ts +29 -6
  123. package/template/src/app/api/workflows/process/route.ts +505 -170
  124. package/template/src/app/api/workflows/route.ts +42 -4
  125. package/template/src/app/globals.css +512 -32
  126. package/template/src/app/layout.tsx +28 -9
  127. package/template/src/app/page.tsx +37 -7
  128. package/template/src/components/address-autocomplete.tsx +233 -0
  129. package/template/src/components/config-error-alert.tsx +46 -0
  130. package/template/src/components/contacts/filter-bar.tsx +190 -0
  131. package/template/src/components/contacts/filter-builder.tsx +574 -0
  132. package/template/src/components/contacts/save-view-dialog.tsx +160 -0
  133. package/template/src/components/contacts/views-tab-bar.tsx +449 -0
  134. package/template/src/components/dashboard/activity-chart.tsx +6 -1
  135. package/template/src/components/dashboard/add-widget-dialog.tsx +13 -17
  136. package/template/src/components/dashboard/color-picker.tsx +7 -8
  137. package/template/src/components/dashboard/recent-activity.tsx +2 -5
  138. package/template/src/components/dashboard/stat-card.tsx +1 -3
  139. package/template/src/components/dashboard/status-distribution-chart.tsx +0 -1
  140. package/template/src/components/dashboard/top-contacts-list.tsx +7 -13
  141. package/template/src/components/dashboard/upcoming-tasks-list.tsx +2 -5
  142. package/template/src/components/dashboard/widget-wrapper.tsx +3 -6
  143. package/template/src/components/date-picker.tsx +399 -0
  144. package/template/src/components/editor/upload-editor-image.ts +42 -0
  145. package/template/src/components/editor.tsx +188 -35
  146. package/template/src/components/email-template.tsx +4 -2
  147. package/template/src/components/global-search.tsx +360 -0
  148. package/template/src/components/header.tsx +200 -107
  149. package/template/src/components/inactive-account-guard.tsx +58 -0
  150. package/template/src/components/integration-notifications-listener.tsx +12 -0
  151. package/template/src/components/invitation-email-template.tsx +4 -2
  152. package/template/src/components/lazy-editor.tsx +11 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +11 -3
  154. package/template/src/components/meet-confirmation-email-template.tsx +10 -3
  155. package/template/src/components/meet-update-email-template.tsx +10 -3
  156. package/template/src/components/page-header.tsx +19 -15
  157. package/template/src/components/protected-page.tsx +94 -0
  158. package/template/src/components/reset-password-email-template.tsx +4 -2
  159. package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
  160. package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
  161. package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
  162. package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
  163. package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
  164. package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
  165. package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
  166. package/template/src/components/sidebar.tsx +117 -100
  167. package/template/src/components/skeleton.tsx +128 -45
  168. package/template/src/components/ui/accordion.tsx +64 -0
  169. package/template/src/components/ui/alert-dialog.tsx +139 -0
  170. package/template/src/components/ui/button.tsx +71 -0
  171. package/template/src/components/ui/components.tsx +1 -1
  172. package/template/src/components/ui/date-picker.tsx +422 -0
  173. package/template/src/components/ui/datetime-picker.tsx +338 -0
  174. package/template/src/components/ui/status-select.tsx +271 -0
  175. package/template/src/components/ui/tooltip.tsx +37 -0
  176. package/template/src/components/view-as-banner.tsx +1 -1
  177. package/template/src/components/view-as-modal.tsx +30 -19
  178. package/template/src/config/nav-pages.ts +108 -0
  179. package/template/src/contexts/app-toast-context.tsx +362 -0
  180. package/template/src/contexts/dashboard-theme-context.tsx +2 -7
  181. package/template/src/contexts/sidebar-context.tsx +27 -53
  182. package/template/src/contexts/task-reminder-context.tsx +134 -160
  183. package/template/src/contexts/view-as-context.tsx +32 -10
  184. package/template/src/hooks/use-alert.tsx +65 -0
  185. package/template/src/hooks/use-confirm.tsx +87 -0
  186. package/template/src/hooks/use-contact-views.ts +140 -0
  187. package/template/src/hooks/use-contacts.ts +69 -0
  188. package/template/src/hooks/use-fetch.ts +17 -0
  189. package/template/src/hooks/use-focus-trap.ts +73 -0
  190. package/template/src/hooks/use-statuses.ts +22 -0
  191. package/template/src/hooks/useIntegrationNotifications.ts +49 -0
  192. package/template/src/lib/address-api.ts +155 -0
  193. package/template/src/lib/auth.ts +8 -1
  194. package/template/src/lib/cache.ts +73 -0
  195. package/template/src/lib/check-permission.ts +12 -177
  196. package/template/src/lib/config-links.ts +14 -0
  197. package/template/src/lib/contact-duplicate.ts +79 -61
  198. package/template/src/lib/contact-interactions.ts +24 -22
  199. package/template/src/lib/contact-view-filters.ts +301 -0
  200. package/template/src/lib/contacts-list-url.ts +190 -0
  201. package/template/src/lib/dashboard-stats.ts +282 -0
  202. package/template/src/lib/dashboard-themes.ts +0 -5
  203. package/template/src/lib/date-utils.ts +176 -0
  204. package/template/src/lib/default-widgets.ts +0 -2
  205. package/template/src/lib/editor-html-image-dimensions.ts +172 -0
  206. package/template/src/lib/editor-image-limits.ts +19 -0
  207. package/template/src/lib/email-html-sanitize.ts +19 -0
  208. package/template/src/lib/encryption.ts +9 -6
  209. package/template/src/lib/fr-geography.ts +192 -0
  210. package/template/src/lib/get-auth-user.ts +25 -0
  211. package/template/src/lib/google-calendar-agenda.ts +201 -0
  212. package/template/src/lib/google-calendar.ts +309 -17
  213. package/template/src/lib/google-fetch.ts +63 -0
  214. package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
  215. package/template/src/lib/google-sheet-sync-runner.ts +514 -0
  216. package/template/src/lib/integration-import-log.ts +21 -0
  217. package/template/src/lib/local-storage.ts +34 -0
  218. package/template/src/lib/permissions.ts +268 -40
  219. package/template/src/lib/prisma.ts +15 -12
  220. package/template/src/lib/qstash.ts +65 -0
  221. package/template/src/lib/reminder-state-server.ts +80 -0
  222. package/template/src/lib/reminder-state.ts +29 -0
  223. package/template/src/lib/roles.ts +12 -15
  224. package/template/src/lib/supabase-storage.ts +113 -0
  225. package/template/src/lib/template-variables.ts +204 -29
  226. package/template/src/lib/utils.ts +71 -11
  227. package/template/src/lib/widget-registry.ts +0 -4
  228. package/template/src/lib/workflow-executor.ts +391 -228
  229. package/template/src/proxy.ts +35 -73
  230. package/template/src/types/contact-views.ts +351 -0
  231. package/template/vercel.json +5 -0
  232. package/template/WORKFLOWS_CRON.md +0 -185
  233. package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
  234. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
  235. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
  236. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
  237. package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
  238. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
  239. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
  240. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
  241. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
  242. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
  243. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
  244. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
  245. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
  246. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
  247. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
  248. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
  249. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
  250. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
  251. package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
  252. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
  253. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
  254. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
  255. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
  256. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
  257. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
  258. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
  259. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
  260. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
  261. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
  262. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
  263. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
  264. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
  265. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
  266. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
  267. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
  268. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
  269. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
  270. package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
  271. package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
  272. package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
  273. package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
  274. package/template/prisma/migrations/20260226093949_fix_cascade_on_user_delete/migration.sql +0 -69
  275. package/template/src/app/(dashboard)/users/layout.tsx +0 -30
  276. package/template/src/lib/google-drive.ts +0 -380
@@ -7,11 +7,19 @@ import { useRouter } from 'next/navigation';
7
7
  import Link from 'next/link';
8
8
  import { cn } from '@/lib/utils';
9
9
  import { useMobileMenuContext } from '@/contexts/mobile-menu-context';
10
- import { useViewAs } from '@/contexts/view-as-context';
10
+ import { GlobalSearch } from '@/components/global-search';
11
+ import {
12
+ REMINDERS_CLEAR_UNDO_WINDOW_MS,
13
+ REMINDERS_POLL_INTERVAL_MS,
14
+ REMINDERS_REFRESH_EVENT,
15
+ requestRemindersRefresh,
16
+ } from '@/lib/reminder-state';
17
+ import { useAppToast } from '@/contexts/app-toast-context';
11
18
 
12
19
  interface Reminder {
13
20
  id: string;
14
21
  taskId: string;
22
+ kind: 'due' | 'reminder';
15
23
  type: string;
16
24
  title: string | null;
17
25
  description: string;
@@ -19,6 +27,9 @@ interface Reminder {
19
27
  scheduledAt: string;
20
28
  reminderTime: string;
21
29
  reminderMinutesBefore: number | null;
30
+ isRead: boolean;
31
+ isDismissed: boolean;
32
+ isClearedByCutoff: boolean;
22
33
  contact: {
23
34
  id: string;
24
35
  firstName: string | null;
@@ -35,12 +46,36 @@ const TASK_TYPE_LABELS: Record<string, string> = {
35
46
  };
36
47
 
37
48
  const PRIORITY_COLORS: Record<string, string> = {
38
- LOW: 'bg-gray-100 text-gray-700',
49
+ LOW: 'bg-muted text-muted-foreground',
39
50
  MEDIUM: 'bg-yellow-100 text-yellow-700',
40
51
  HIGH: 'bg-orange-100 text-orange-700',
41
52
  URGENT: 'bg-red-100 text-red-700',
42
53
  };
43
54
 
55
+ const REMINDER_KIND_BADGE: Record<Reminder['kind'], { label: string; className: string }> = {
56
+ due: {
57
+ label: 'À faire maintenant',
58
+ className: 'bg-amber-100 text-amber-900 ring-1 ring-amber-200/80',
59
+ },
60
+ reminder: {
61
+ label: 'Rappel',
62
+ className: 'bg-sky-100 text-sky-900 ring-1 ring-sky-200/80',
63
+ },
64
+ };
65
+
66
+ function priorityLabel(priority: string): string {
67
+ switch (priority) {
68
+ case 'URGENT':
69
+ return 'Urgente';
70
+ case 'HIGH':
71
+ return 'Haute';
72
+ case 'MEDIUM':
73
+ return 'Moyenne';
74
+ default:
75
+ return 'Faible';
76
+ }
77
+ }
78
+
44
79
  function formatDateTime(dateString: string) {
45
80
  const date = new Date(dateString);
46
81
  return {
@@ -57,50 +92,29 @@ function formatDateTime(dateString: string) {
57
92
 
58
93
  export function Header() {
59
94
  const { data: session } = useSession();
95
+ const { persistent: showPersistentToast } = useAppToast();
60
96
  const router = useRouter();
61
97
  const { toggle: toggleMobileMenu } = useMobileMenuContext();
62
- const { viewAsUser, isViewingAsOther } = useViewAs();
63
98
  const [showRemindersDropdown, setShowRemindersDropdown] = useState(false);
64
99
  const [showUserDropdown, setShowUserDropdown] = useState(false);
65
100
  const [reminders, setReminders] = useState<Reminder[]>([]);
66
- const [readReminders, setReadReminders] = useState<Set<string>>(new Set());
67
101
  const [loading, setLoading] = useState(false);
68
102
  const remindersRef = useRef<HTMLDivElement>(null);
69
103
  const userRef = useRef<HTMLDivElement>(null);
70
104
 
71
- const realUserName = session?.user?.name || 'Utilisateur';
72
- const userName = isViewingAsOther ? viewAsUser?.name || 'Utilisateur' : realUserName;
105
+ const userName = session?.user?.name || 'Utilisateur';
73
106
  const userEmail = session?.user?.email || '';
74
107
  const userInitial = userName?.[0]?.toUpperCase() || 'U';
75
108
 
76
- // Charger les rappels lus depuis localStorage
77
- useEffect(() => {
78
- if (typeof window !== 'undefined') {
79
- const stored = localStorage.getItem('readReminders');
80
- if (stored) {
81
- try {
82
- setReadReminders(new Set(JSON.parse(stored)));
83
- } catch (e) {
84
- console.error('Erreur lors du chargement des rappels lus:', e);
85
- }
86
- }
87
- }
88
- }, []);
89
-
90
- // Sauvegarder les rappels lus dans localStorage
91
- useEffect(() => {
92
- if (typeof window !== 'undefined' && readReminders.size > 0) {
93
- localStorage.setItem('readReminders', JSON.stringify(Array.from(readReminders)));
94
- }
95
- }, [readReminders]);
96
-
97
- // Charger les rappels
109
+ // Charger les rappels (polling + retour sur l’onglet + événement explicite)
98
110
  useEffect(() => {
99
111
  if (!session) return;
100
112
 
101
- const fetchReminders = async () => {
113
+ const fetchReminders = async (opts?: { silent?: boolean }) => {
102
114
  try {
103
- setLoading(true);
115
+ if (!opts?.silent) {
116
+ setLoading(true);
117
+ }
104
118
  const response = await fetch('/api/reminders');
105
119
  if (response.ok) {
106
120
  const data = await response.json();
@@ -109,13 +123,29 @@ export function Header() {
109
123
  } catch (error) {
110
124
  console.error('Erreur lors du chargement des rappels:', error);
111
125
  } finally {
112
- setLoading(false);
126
+ if (!opts?.silent) {
127
+ setLoading(false);
128
+ }
129
+ }
130
+ };
131
+
132
+ void fetchReminders();
133
+ const interval = setInterval(() => void fetchReminders({ silent: true }), REMINDERS_POLL_INTERVAL_MS);
134
+
135
+ const onVisible = () => {
136
+ if (document.visibilityState === 'visible') {
137
+ void fetchReminders({ silent: true });
113
138
  }
114
139
  };
140
+ const onRefresh = () => void fetchReminders({ silent: true });
115
141
 
116
- fetchReminders();
117
- const interval = setInterval(fetchReminders, 60 * 1000); // Rafraîchir toutes les minutes
118
- return () => clearInterval(interval);
142
+ document.addEventListener('visibilitychange', onVisible);
143
+ globalThis.addEventListener(REMINDERS_REFRESH_EVENT, onRefresh);
144
+ return () => {
145
+ clearInterval(interval);
146
+ document.removeEventListener('visibilitychange', onVisible);
147
+ globalThis.removeEventListener(REMINDERS_REFRESH_EVENT, onRefresh);
148
+ };
119
149
  }, [session]);
120
150
 
121
151
  // Fermer les dropdowns en cliquant à l'extérieur
@@ -133,15 +163,53 @@ export function Header() {
133
163
  return () => document.removeEventListener('mousedown', handleClickOutside);
134
164
  }, []);
135
165
 
136
- const unreadCount = reminders.filter((r) => !readReminders.has(r.id)).length;
166
+ const unreadCount = reminders.filter((r) => !r.isRead && !r.isDismissed).length;
137
167
 
138
- const handleMarkAsRead = (reminderId: string) => {
139
- setReadReminders((prev) => new Set([...prev, reminderId]));
168
+ const handleMarkAsRead = async (reminderId: string) => {
169
+ setReminders((prev) =>
170
+ prev.map((reminder) =>
171
+ reminder.id === reminderId ? { ...reminder, isRead: true } : reminder,
172
+ ),
173
+ );
174
+ globalThis.dispatchEvent(new CustomEvent('reminders:read', { detail: { reminderId } }));
175
+ try {
176
+ await fetch('/api/reminders/state', {
177
+ method: 'POST',
178
+ headers: { 'Content-Type': 'application/json' },
179
+ body: JSON.stringify({ reminderId, status: 'READ' }),
180
+ });
181
+ } catch (error) {
182
+ console.error("Erreur lors du marquage d'un rappel comme lu:", error);
183
+ }
140
184
  };
141
185
 
142
- const handleMarkAllAsRead = () => {
143
- const allIds = new Set(reminders.map((r) => r.id));
144
- setReadReminders((prev) => new Set([...prev, ...allIds]));
186
+ const handleClearReminders = async () => {
187
+ try {
188
+ const response = await fetch('/api/reminders/clear', { method: 'POST' });
189
+ const data = (await response.json().catch(() => ({}))) as {
190
+ degraded?: boolean;
191
+ undoUntil?: string;
192
+ };
193
+ if (!response.ok) {
194
+ throw new Error('Impossible de vider les rappels.');
195
+ }
196
+ setReminders([]);
197
+ globalThis.dispatchEvent(new CustomEvent('reminders:cleared'));
198
+ if (data.degraded !== true && typeof data.undoUntil === 'string') {
199
+ showPersistentToast('info', 'Rappels vidés.', {
200
+ actionLabel: 'Annuler',
201
+ actionOnClick: async () => {
202
+ const undoRes = await fetch('/api/reminders/clear/undo', { method: 'POST' });
203
+ if (undoRes.ok) {
204
+ requestRemindersRefresh();
205
+ }
206
+ },
207
+ autoDismissMs: REMINDERS_CLEAR_UNDO_WINDOW_MS,
208
+ });
209
+ }
210
+ } catch (error) {
211
+ console.error('Erreur lors du vidage des rappels:', error);
212
+ }
145
213
  };
146
214
 
147
215
  const handleSignOut = async () => {
@@ -150,37 +218,49 @@ export function Header() {
150
218
  };
151
219
 
152
220
  return (
153
- <header className="sticky top-0 z-30 border-b border-gray-200 bg-white px-4 py-3 sm:px-6 lg:px-8">
154
- <div className="flex items-center justify-between gap-2 sm:gap-4">
155
- {/* Left: Burger + Logo + Greeting */}
156
- <div className="flex items-center gap-2 sm:gap-3">
157
- {/* Bouton burger - visible uniquement sur mobile/tablette */}
158
- <button
159
- onClick={toggleMobileMenu}
160
- className="cursor-pointer rounded-lg p-2 text-gray-600 transition-colors hover:bg-gray-100 lg:hidden"
161
- aria-label="Ouvrir le menu"
162
- >
163
- <Menu className="h-5 w-5" />
164
- </button>
221
+ <header className="border-border bg-background/95 sticky top-0 z-40 border-b px-4 py-3 backdrop-blur-sm sm:px-6 lg:px-8">
222
+ <div className="flex items-center gap-2 sm:gap-4">
223
+ {/* Left: Logo + Greeting */}
224
+ <div className="flex shrink-0 items-center gap-2 sm:gap-3">
165
225
  <div className="flex items-center gap-1.5 sm:gap-2">
166
- {/* <Rocket className="h-5 w-5 text-indigo-600 sm:h-6 sm:w-6" /> */}
167
- <span className="text-base font-bold text-gray-900 sm:text-lg">CRM Template</span>
226
+ {/* Bouton burger pour mobile */}
227
+ <button
228
+ onClick={toggleMobileMenu}
229
+ className="text-foreground/80 hover:bg-muted focus-visible:ring-primary cursor-pointer rounded-lg p-2 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none lg:hidden"
230
+ aria-label="Ouvrir ou fermer le menu"
231
+ >
232
+ <Menu className="h-5 w-5" />
233
+ </button>
234
+ <span className="text-foreground text-base font-bold sm:text-lg">CRM Template</span>
168
235
  </div>
169
- <span className="hidden text-sm text-gray-600 sm:inline">👋 Salut {userName} !</span>
236
+ </div>
237
+
238
+ {/* Center: Global Search */}
239
+ <div className="flex min-w-0 flex-1 justify-center">
240
+ <GlobalSearch />
170
241
  </div>
171
242
 
172
243
  {/* Right: Notifications + User Avatar */}
173
- <div className="flex items-center gap-2 sm:gap-3">
244
+ <div className="flex shrink-0 items-center gap-2 sm:gap-3">
174
245
  {/* Notifications Dropdown */}
175
246
  <div className="relative" ref={remindersRef}>
176
247
  <button
177
248
  onClick={() => setShowRemindersDropdown(!showRemindersDropdown)}
178
- className="relative cursor-pointer rounded-lg p-2 text-gray-600 transition-colors hover:bg-gray-100"
249
+ className="text-muted-foreground hover:bg-muted focus-visible:ring-primary relative cursor-pointer rounded-lg p-2 transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none"
179
250
  aria-label="Notifications"
180
251
  >
181
- <Bell className="h-5 w-5" />
252
+ <span
253
+ className={cn(
254
+ unreadCount > 0 && !showRemindersDropdown && 'ui-bell-notify',
255
+ )}
256
+ >
257
+ <Bell className="h-5 w-5" />
258
+ </span>
182
259
  {unreadCount > 0 && (
183
- <span className="absolute top-1 right-1 flex h-4 w-4 items-center justify-center rounded-full bg-blue-600 text-[10px] font-semibold text-white">
260
+ <span
261
+ key={unreadCount}
262
+ className="bg-primary text-primary-foreground ui-count-pop absolute top-1 right-1 flex h-4 w-4 items-center justify-center rounded-full text-[10px] font-semibold"
263
+ >
184
264
  {unreadCount > 9 ? '9+' : unreadCount}
185
265
  </span>
186
266
  )}
@@ -188,66 +268,85 @@ export function Header() {
188
268
 
189
269
  {/* Dropdown des rappels */}
190
270
  {showRemindersDropdown && (
191
- <div className="absolute right-0 mt-2 w-80 max-w-[calc(100vw-2rem)] rounded-lg border border-gray-200 bg-white shadow-xl">
192
- <div className="border-b border-gray-200 px-4 py-3">
271
+ <div className="border-border bg-popover ui-dropdown-enter absolute right-0 mt-2 w-80 max-w-[calc(100vw-2rem)] rounded-xl border shadow-(--shadow-dropdown)">
272
+ <div className="border-border border-b px-4 py-3">
193
273
  <div className="flex items-center justify-between">
194
- <h3 className="text-sm font-semibold text-gray-900">Rappels</h3>
195
- {unreadCount > 0 && (
274
+ <h3 className="text-popover-foreground text-sm font-semibold">Rappels</h3>
275
+ {reminders.length > 0 && (
196
276
  <button
197
- onClick={handleMarkAllAsRead}
198
- className="cursor-pointer text-xs text-indigo-600 hover:text-indigo-700"
277
+ onClick={handleClearReminders}
278
+ className="text-primary hover:text-primary/80 focus-visible:ring-primary cursor-pointer text-xs focus-visible:ring-2 focus-visible:outline-none"
199
279
  >
200
- Tout marquer comme lu
280
+ Vider les rappels
201
281
  </button>
202
282
  )}
203
283
  </div>
204
284
  </div>
205
285
  <div className="max-h-96 overflow-y-auto">
206
- {loading ? (
207
- <div className="px-4 py-8 text-center text-sm text-gray-500">Chargement...</div>
208
- ) : reminders.length === 0 ? (
209
- <div className="px-4 py-8 text-center text-sm text-gray-500">Aucun rappel</div>
210
- ) : (
211
- <div className="divide-y divide-gray-100">
286
+ {loading && (
287
+ <div className="text-muted-foreground px-4 py-8 text-center text-sm">
288
+ Chargement...
289
+ </div>
290
+ )}
291
+ {!loading && reminders.length === 0 && (
292
+ <div className="text-muted-foreground px-4 py-8 text-center text-sm">
293
+ Aucun rappel
294
+ </div>
295
+ )}
296
+ {!loading && reminders.length > 0 && (
297
+ <div className="divide-border divide-y">
212
298
  {reminders.map((reminder) => {
213
- const isRead = readReminders.has(reminder.id);
214
- const { date, time } = formatDateTime(reminder.scheduledAt);
215
- const contactName = reminder.contact
216
- ? `${reminder.contact.firstName || ''} ${reminder.contact.lastName || ''}`.trim() ||
217
- 'Contact sans nom'
218
- : null;
299
+ const isRead = reminder.isRead || reminder.isDismissed;
300
+ const { date, time } = formatDateTime(reminder.reminderTime);
301
+ const kindBadge = REMINDER_KIND_BADGE[reminder.kind] ?? REMINDER_KIND_BADGE.reminder;
302
+ let contactName: string | null = null;
303
+ if (reminder.contact) {
304
+ const full = `${reminder.contact.firstName || ''} ${reminder.contact.lastName || ''}`.trim();
305
+ contactName = full.length > 0 ? full : 'Contact sans nom';
306
+ }
219
307
 
220
308
  return (
221
- <div
309
+ <button
310
+ type="button"
222
311
  key={reminder.id}
223
312
  className={cn(
224
- 'px-4 py-3 transition-colors hover:bg-gray-50',
225
- !isRead && 'bg-blue-50/50',
313
+ 'hover:bg-accent w-full px-4 py-3 text-left transition-colors duration-200',
314
+ isRead ? undefined : 'bg-accent/70',
226
315
  )}
227
- onClick={() => handleMarkAsRead(reminder.id)}
316
+ onClick={() => void handleMarkAsRead(reminder.id)}
228
317
  >
229
318
  <div className="flex items-start gap-3">
230
319
  <div className="mt-0.5 shrink-0">
231
- <Calendar className="h-4 w-4 text-indigo-600" />
320
+ <Calendar aria-hidden="true" className="text-primary h-4 w-4" />
232
321
  </div>
233
322
  <div className="min-w-0 flex-1">
234
323
  <div className="flex items-start justify-between gap-2">
235
324
  <div className="min-w-0 flex-1">
325
+ <span
326
+ className={cn(
327
+ 'mb-1 inline-flex max-w-full rounded-full px-2 py-0.5 text-[10px] font-semibold tracking-wide uppercase',
328
+ kindBadge.className,
329
+ )}
330
+ >
331
+ {kindBadge.label}
332
+ </span>
236
333
  <p
237
334
  className={cn(
238
335
  'text-sm font-medium',
239
- !isRead ? 'text-gray-900' : 'text-gray-700',
336
+ isRead ? 'text-muted-foreground' : 'text-foreground',
240
337
  )}
241
338
  >
242
339
  {reminder.title || TASK_TYPE_LABELS[reminder.type] || 'Tâche'}
243
340
  </p>
244
341
  {contactName && (
245
- <p className="mt-0.5 text-xs text-gray-500">{contactName}</p>
342
+ <p className="text-muted-foreground mt-0.5 text-xs">
343
+ {contactName}
344
+ </p>
246
345
  )}
247
- <div className="mt-1 flex items-center gap-2">
248
- <span className="text-xs text-gray-500">{date}</span>
249
- <span className="text-xs text-gray-400">•</span>
250
- <span className="text-xs text-gray-500">{time}</span>
346
+ <div className="mt-1 flex flex-wrap items-center gap-2">
347
+ <span className="text-muted-foreground text-xs">{date}</span>
348
+ <span className="text-muted-foreground/70 text-xs">•</span>
349
+ <span className="text-muted-foreground text-xs">{time}</span>
251
350
  <span
252
351
  className={cn(
253
352
  'rounded-full px-1.5 py-0.5 text-[10px] font-medium',
@@ -255,32 +354,26 @@ export function Header() {
255
354
  PRIORITY_COLORS.MEDIUM,
256
355
  )}
257
356
  >
258
- {reminder.priority === 'URGENT'
259
- ? 'Urgente'
260
- : reminder.priority === 'HIGH'
261
- ? 'Haute'
262
- : reminder.priority === 'MEDIUM'
263
- ? 'Moyenne'
264
- : 'Faible'}
357
+ {priorityLabel(reminder.priority)}
265
358
  </span>
266
359
  </div>
267
360
  </div>
268
361
  {!isRead && (
269
- <div className="h-2 w-2 shrink-0 rounded-full bg-blue-600" />
362
+ <div className="bg-primary h-2 w-2 shrink-0 rounded-full" />
270
363
  )}
271
364
  </div>
272
365
  {reminder.contact && (
273
366
  <Link
274
367
  href={`/contacts/${reminder.contact.id}`}
275
368
  onClick={(e) => e.stopPropagation()}
276
- className="mt-2 inline-block text-xs font-medium text-indigo-600 hover:text-indigo-700"
369
+ className="text-primary hover:text-primary/80 mt-2 inline-block text-xs font-medium"
277
370
  >
278
371
  Voir le contact
279
372
  </Link>
280
373
  )}
281
374
  </div>
282
375
  </div>
283
- </div>
376
+ </button>
284
377
  );
285
378
  })}
286
379
  </div>
@@ -294,26 +387,26 @@ export function Header() {
294
387
  <div className="relative" ref={userRef}>
295
388
  <button
296
389
  onClick={() => setShowUserDropdown(!showUserDropdown)}
297
- className="flex cursor-pointer items-center gap-1.5 sm:gap-2"
390
+ className="hover:bg-accent focus-visible:ring-primary flex cursor-pointer items-center gap-1.5 rounded-md transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none sm:gap-2"
298
391
  aria-label="Menu utilisateur"
299
392
  >
300
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-indigo-100 text-xs font-semibold text-indigo-600 sm:h-9 sm:w-9 sm:text-sm">
393
+ <div className="bg-primary/15 text-primary flex h-8 w-8 items-center justify-center rounded-full text-xs font-semibold sm:h-9 sm:w-9 sm:text-sm">
301
394
  {userInitial}
302
395
  </div>
303
- <ChevronDown className="hidden h-4 w-4 text-gray-400 transition-colors hover:text-gray-600 sm:block" />
396
+ <ChevronDown className="text-muted-foreground hover:text-foreground hidden h-4 w-4 transition-colors sm:block" />
304
397
  </button>
305
398
 
306
399
  {/* Dropdown utilisateur */}
307
400
  {showUserDropdown && (
308
- <div className="absolute right-0 mt-2 w-56 rounded-lg border border-gray-200 bg-white shadow-xl">
309
- <div className="border-b border-gray-200 px-4 py-3">
310
- <p className="text-sm font-medium text-gray-900">{realUserName}</p>
311
- <p className="mt-0.5 text-xs text-gray-500">{userEmail}</p>
401
+ <div className="border-border bg-popover ui-dropdown-enter absolute right-0 mt-2 w-56 rounded-xl border shadow-(--shadow-dropdown)">
402
+ <div className="border-border border-b px-4 py-3">
403
+ <p className="text-popover-foreground text-sm font-medium">{userName}</p>
404
+ <p className="text-muted-foreground mt-0.5 text-xs">{userEmail}</p>
312
405
  </div>
313
406
  <div className="py-1">
314
407
  <button
315
408
  onClick={handleSignOut}
316
- className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-50"
409
+ className="text-popover-foreground hover:bg-accent focus-visible:ring-primary flex w-full cursor-pointer items-center gap-2 px-4 py-2 text-sm transition-colors duration-200 focus-visible:ring-2 focus-visible:outline-none"
317
410
  >
318
411
  <LogOut className="h-4 w-4" />
319
412
  <span>Déconnexion</span>
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { signOut } from '@/lib/auth-client';
6
+
7
+ const POLL_MS = 25_000;
8
+
9
+ async function fetchActiveStatus(): Promise<boolean | null> {
10
+ try {
11
+ const res = await fetch('/api/auth/check-active', { credentials: 'include' });
12
+ if (!res.ok) return null;
13
+ const data = (await res.json()) as { active?: boolean };
14
+ if (typeof data.active !== 'boolean') return null;
15
+ return data.active;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Déconnecte et redirige vers /signin si le compte courant est inactif ou sans session valide,
23
+ * pour éviter de rester sur le shell du CRM avec une session révoquée côté serveur.
24
+ */
25
+ export function InactiveAccountGuard() {
26
+ const router = useRouter();
27
+ const signingOutRef = useRef(false);
28
+
29
+ useEffect(() => {
30
+ const run = async () => {
31
+ if (signingOutRef.current) return;
32
+ const active = await fetchActiveStatus();
33
+ if (active === false) {
34
+ signingOutRef.current = true;
35
+ await signOut();
36
+ router.replace('/signin?inactive=1');
37
+ }
38
+ };
39
+
40
+ void run();
41
+
42
+ const interval = setInterval(() => {
43
+ if (document.visibilityState === 'visible') void run();
44
+ }, POLL_MS);
45
+
46
+ const onVisibility = () => {
47
+ if (document.visibilityState === 'visible') void run();
48
+ };
49
+ document.addEventListener('visibilitychange', onVisibility);
50
+
51
+ return () => {
52
+ clearInterval(interval);
53
+ document.removeEventListener('visibilitychange', onVisibility);
54
+ };
55
+ }, [router]);
56
+
57
+ return null;
58
+ }
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { useIntegrationNotifications } from '@/hooks/useIntegrationNotifications';
4
+
5
+ /**
6
+ * Renders nothing; runs the integration notifications polling hook
7
+ * (toasts disabled to avoid accumulation).
8
+ */
9
+ export function IntegrationNotificationsListener() {
10
+ useIntegrationNotifications();
11
+ return null;
12
+ }
@@ -1,3 +1,5 @@
1
+ import { sanitizeEmailHtml } from '@/lib/email-html-sanitize';
2
+
1
3
  interface InvitationEmailProps {
2
4
  name: string;
3
5
  invitationUrl: string;
@@ -8,7 +10,7 @@ export function InvitationEmailTemplate({ name, invitationUrl, signature }: Invi
8
10
  return (
9
11
  <div
10
12
  style={{
11
- fontFamily: 'Arial, sans-serif',
13
+ fontFamily: '"Segoe UI", "Helvetica Neue", sans-serif',
12
14
  padding: '20px',
13
15
  maxWidth: '600px',
14
16
  margin: '0 auto',
@@ -71,7 +73,7 @@ export function InvitationEmailTemplate({ name, invitationUrl, signature }: Invi
71
73
  fontSize: '14px',
72
74
  lineHeight: '1.6',
73
75
  }}
74
- dangerouslySetInnerHTML={{ __html: signature }}
76
+ dangerouslySetInnerHTML={{ __html: sanitizeEmailHtml(signature) }}
75
77
  />
76
78
  )}
77
79
  </div>
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+ import { Skeleton } from '@/components/skeleton';
5
+
6
+ export type { DefaultTemplateRef } from '@/components/editor';
7
+
8
+ export const LazyEditor = dynamic(() => import('@/components/editor').then((m) => m.Editor), {
9
+ ssr: false,
10
+ loading: () => <Skeleton className="h-64 rounded-lg" />,
11
+ });
@@ -1,3 +1,5 @@
1
+ import { sanitizeEmailHtml } from '@/lib/email-html-sanitize';
2
+
1
3
  interface MeetCancellationEmailTemplateProps {
2
4
  contactName: string;
3
5
  title: string;
@@ -50,7 +52,13 @@ export function MeetCancellationEmailTemplate({
50
52
  };
51
53
 
52
54
  return (
53
- <div style={{ fontFamily: 'Arial, sans-serif', lineHeight: '1.6', color: '#333' }}>
55
+ <div
56
+ style={{
57
+ fontFamily: '"Segoe UI", "Helvetica Neue", sans-serif',
58
+ lineHeight: '1.6',
59
+ color: '#333',
60
+ }}
61
+ >
54
62
  <div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
55
63
  <h1 style={{ color: '#EF4444', fontSize: '24px', marginBottom: '20px' }}>
56
64
  Annulation de rendez-vous
@@ -93,7 +101,7 @@ export function MeetCancellationEmailTemplate({
93
101
  <strong>Description :</strong>
94
102
  <div
95
103
  style={{ marginTop: '10px' }}
96
- dangerouslySetInnerHTML={{ __html: description }}
104
+ dangerouslySetInnerHTML={{ __html: sanitizeEmailHtml(description) }}
97
105
  />
98
106
  </div>
99
107
  )}
@@ -111,7 +119,7 @@ export function MeetCancellationEmailTemplate({
111
119
  borderTop: '1px solid #ddd',
112
120
  fontSize: '14px',
113
121
  }}
114
- dangerouslySetInnerHTML={{ __html: signature }}
122
+ dangerouslySetInnerHTML={{ __html: sanitizeEmailHtml(signature) }}
115
123
  />
116
124
  )}
117
125
  </div>