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
@@ -0,0 +1,451 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useState } from 'react';
4
+ import { useAppToast } from '@/contexts/app-toast-context';
5
+ import { useConfirm } from '@/hooks/use-confirm';
6
+ import { StatusSelect } from '@/components/ui/status-select';
7
+ import { devToast } from '@/lib/utils';
8
+
9
+ interface MetaLeadConfig {
10
+ id: string;
11
+ name: string;
12
+ pageId: string;
13
+ verifyToken: string;
14
+ active: boolean;
15
+ defaultStatusId: string | null;
16
+ defaultAssignedUserId: string | null;
17
+ defaultStatus: { id: string; name: string; color: string } | null;
18
+ defaultAssignedUser: { id: string; name: string; email: string } | null;
19
+ }
20
+
21
+ interface MetaLeadFormData {
22
+ name: string;
23
+ active: boolean;
24
+ pageId: string;
25
+ accessToken: string;
26
+ verifyToken: string;
27
+ defaultStatusId: string | null;
28
+ defaultAssignedUserId: string | null;
29
+ }
30
+
31
+ const EMPTY_FORM: MetaLeadFormData = {
32
+ name: '',
33
+ active: true,
34
+ pageId: '',
35
+ accessToken: '',
36
+ verifyToken: '',
37
+ defaultStatusId: null,
38
+ defaultAssignedUserId: null,
39
+ };
40
+
41
+ interface MetaLeadIntegrationProps {
42
+ readonly statuses: ReadonlyArray<{ id: string; name: string; color: string }>;
43
+ readonly users: ReadonlyArray<{ id: string; name: string; email: string }>;
44
+ readonly onOpenLogs: (configId: string, configName: string) => void;
45
+ }
46
+
47
+ export function MetaLeadIntegration({ statuses, users, onOpenLogs }: MetaLeadIntegrationProps) {
48
+ const toast = useAppToast();
49
+ const { confirm, ConfirmDialog } = useConfirm();
50
+
51
+ const [loading, setLoading] = useState(true);
52
+ const [saving, setSaving] = useState(false);
53
+ const [configs, setConfigs] = useState<MetaLeadConfig[]>([]);
54
+ const [showModal, setShowModal] = useState(false);
55
+ const [editingConfig, setEditingConfig] = useState<string | null>(null);
56
+ const [formData, setFormData] = useState<MetaLeadFormData>(EMPTY_FORM);
57
+
58
+ const loadConfigs = useCallback(async () => {
59
+ try {
60
+ const res = await fetch('/api/settings/meta-leads');
61
+ if (res.ok) {
62
+ const data = await res.json();
63
+ setConfigs(Array.isArray(data) ? data : []);
64
+ }
65
+ } catch {
66
+ toast.error('Impossible de charger les configurations Meta. Veuillez rafraîchir la page.');
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ }, [toast]);
71
+
72
+ useEffect(() => {
73
+ loadConfigs();
74
+ }, [loadConfigs]);
75
+
76
+ const resetForm = () => {
77
+ setShowModal(false);
78
+ setEditingConfig(null);
79
+ setFormData(EMPTY_FORM);
80
+ };
81
+
82
+ const handleOpenAdd = () => {
83
+ setEditingConfig(null);
84
+ setFormData(EMPTY_FORM);
85
+ setShowModal(true);
86
+ };
87
+
88
+ const handleEdit = (config: MetaLeadConfig) => {
89
+ setEditingConfig(config.id);
90
+ setFormData({
91
+ name: config.name,
92
+ active: config.active,
93
+ pageId: config.pageId,
94
+ accessToken: '',
95
+ verifyToken: config.verifyToken,
96
+ defaultStatusId: config.defaultStatusId,
97
+ defaultAssignedUserId: config.defaultAssignedUserId,
98
+ });
99
+ setShowModal(true);
100
+ };
101
+
102
+ const handleSubmit = async (e: React.FormEvent) => {
103
+ e.preventDefault();
104
+ setSaving(true);
105
+
106
+ try {
107
+ const url = editingConfig
108
+ ? `/api/settings/meta-leads/${editingConfig}`
109
+ : '/api/settings/meta-leads';
110
+ const method = editingConfig ? 'PUT' : 'POST';
111
+
112
+ const response = await fetch(url, {
113
+ method,
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify(formData),
116
+ });
117
+
118
+ const data = await response.json();
119
+
120
+ if (!response.ok) {
121
+ throw new Error(data.error || 'Erreur lors de la sauvegarde de la configuration Meta');
122
+ }
123
+
124
+ toast.success(
125
+ editingConfig
126
+ ? 'Configuration Meta Lead Ads mise à jour avec succès'
127
+ : 'Configuration Meta Lead Ads créée avec succès',
128
+ );
129
+ resetForm();
130
+ await loadConfigs();
131
+ } catch (error: any) {
132
+ toast.error(devToast('Impossible de sauvegarder la configuration Meta. Veuillez réessayer.', error));
133
+ } finally {
134
+ setSaving(false);
135
+ }
136
+ };
137
+
138
+ const handleDelete = async (id: string) => {
139
+ const confirmed = await confirm({
140
+ title: 'Supprimer la configuration Meta Lead',
141
+ description: 'Êtes-vous sûr de vouloir supprimer cette configuration ?',
142
+ confirmText: 'Supprimer',
143
+ cancelText: 'Annuler',
144
+ variant: 'destructive',
145
+ });
146
+
147
+ if (!confirmed) return;
148
+
149
+ try {
150
+ const response = await fetch(`/api/settings/meta-leads/${id}`, {
151
+ method: 'DELETE',
152
+ });
153
+
154
+ if (!response.ok) {
155
+ const data = await response.json();
156
+ throw new Error(data.error || 'Erreur lors de la suppression');
157
+ }
158
+
159
+ toast.success('Configuration supprimée avec succès');
160
+ await loadConfigs();
161
+ } catch (error: any) {
162
+ toast.error(devToast('Impossible de supprimer la configuration. Veuillez réessayer.', error));
163
+ }
164
+ };
165
+
166
+ const getWebhookUrl = () => {
167
+ const origin = globalThis.window?.location.origin ?? '';
168
+ return `${origin}/api/webhooks/meta-leads`;
169
+ };
170
+
171
+ const handleCopyWebhook = () => {
172
+ const url = getWebhookUrl();
173
+ navigator.clipboard?.writeText(url).then(() => toast.success('Lien webhook copié'));
174
+ };
175
+
176
+ return (
177
+ <>
178
+ <div className="rounded-lg bg-white p-6 shadow-sm">
179
+ <div className="flex items-center justify-between">
180
+ <div>
181
+ <h2 className="text-lg font-bold text-gray-900">Intégration Meta Lead Ads</h2>
182
+ <p className="mt-1 text-sm text-gray-600">
183
+ Recevez automatiquement les leads depuis Facebook Lead Ads.
184
+ </p>
185
+ </div>
186
+ <button
187
+ type="button"
188
+ onClick={handleOpenAdd}
189
+ className="cursor-pointer rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
190
+ >
191
+ + Ajouter
192
+ </button>
193
+ </div>
194
+
195
+ {loading && (
196
+ <div className="mt-6 text-center text-gray-500">Chargement...</div>
197
+ )}
198
+
199
+ {!loading && configs.length === 0 && (
200
+ <div className="mt-6 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-8 text-center">
201
+ <p className="text-sm text-gray-600">Aucune configuration Meta Lead Ads</p>
202
+ <p className="mt-1 text-xs text-gray-500">
203
+ Cliquez sur &quot;+ Ajouter&quot; pour créer votre première configuration
204
+ </p>
205
+ </div>
206
+ )}
207
+
208
+ {!loading && configs.length > 0 && (
209
+ <div className="mt-6 space-y-3">
210
+ {configs.map((config) => (
211
+ <div
212
+ key={config.id}
213
+ className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 p-4"
214
+ >
215
+ <div className="flex-1">
216
+ <div className="flex items-center gap-2">
217
+ <h3 className="font-medium text-gray-900">{config.name}</h3>
218
+ {config.active ? (
219
+ <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
220
+ Actif
221
+ </span>
222
+ ) : (
223
+ <span className="rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800">
224
+ Inactif
225
+ </span>
226
+ )}
227
+ </div>
228
+ <p className="mt-1 text-xs text-gray-500">Page ID: {config.pageId}</p>
229
+ <p className="mt-0.5 text-xs text-gray-400">Webhook : {getWebhookUrl()}</p>
230
+ </div>
231
+ <div className="flex flex-wrap items-center gap-2">
232
+ <button
233
+ type="button"
234
+ onClick={() => onOpenLogs(config.id, config.name)}
235
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
236
+ >
237
+ Voir les logs
238
+ </button>
239
+ <button
240
+ type="button"
241
+ onClick={handleCopyWebhook}
242
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
243
+ >
244
+ Copier le lien webhook
245
+ </button>
246
+ <button
247
+ type="button"
248
+ onClick={() => handleEdit(config)}
249
+ className="cursor-pointer rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100"
250
+ >
251
+ Modifier
252
+ </button>
253
+ <button
254
+ type="button"
255
+ onClick={() => handleDelete(config.id)}
256
+ className="cursor-pointer rounded-lg border border-blue-300 px-3 py-1.5 text-xs font-medium text-blue-700 transition-colors hover:bg-blue-50"
257
+ >
258
+ Supprimer
259
+ </button>
260
+ </div>
261
+ </div>
262
+ ))}
263
+ </div>
264
+ )}
265
+ </div>
266
+
267
+ {showModal && (
268
+ <div className="ui-fade-in fixed inset-0 z-50 flex min-h-dvh items-center justify-center bg-gray-500/20 p-4 backdrop-blur-sm sm:p-6">
269
+ <div className="ui-scale-in flex max-h-[90vh] w-full max-w-2xl flex-col rounded-lg bg-white p-6 shadow-xl sm:p-8">
270
+ <div className="shrink-0 border-b border-gray-100 pb-4">
271
+ <div className="flex items-center justify-between">
272
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">
273
+ {editingConfig ? 'Modifier' : 'Ajouter'} une configuration Meta Lead Ads
274
+ </h2>
275
+ <button
276
+ type="button"
277
+ onClick={resetForm}
278
+ className="cursor-pointer rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100"
279
+ >
280
+ <svg
281
+ className="h-6 w-6"
282
+ fill="none"
283
+ stroke="currentColor"
284
+ viewBox="0 0 24 24"
285
+ >
286
+ <path
287
+ strokeLinecap="round"
288
+ strokeLinejoin="round"
289
+ strokeWidth={2}
290
+ d="M6 18L18 6M6 6l12 12"
291
+ />
292
+ </svg>
293
+ </button>
294
+ </div>
295
+ </div>
296
+
297
+ <form
298
+ id="meta-lead-form"
299
+ onSubmit={handleSubmit}
300
+ className="flex-1 space-y-4 overflow-y-auto pt-4"
301
+ >
302
+ <div>
303
+ <label htmlFor="meta-lead-name" className="block text-sm font-medium text-gray-700">
304
+ Nom de la configuration *
305
+ </label>
306
+ <input
307
+ id="meta-lead-name"
308
+ type="text"
309
+ required
310
+ value={formData.name}
311
+ onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
312
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
313
+ placeholder="Ex: Facebook Lead Ads"
314
+ />
315
+ </div>
316
+
317
+ <div className="flex items-center">
318
+ <input
319
+ id="meta-lead-active"
320
+ type="checkbox"
321
+ checked={formData.active}
322
+ onChange={(e) =>
323
+ setFormData((prev) => ({ ...prev, active: e.target.checked }))
324
+ }
325
+ className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-gray-400/30"
326
+ />
327
+ <label
328
+ htmlFor="meta-lead-active"
329
+ className="ml-2 text-sm font-medium text-gray-700"
330
+ >
331
+ Activer l&apos;intégration Meta Lead Ads
332
+ </label>
333
+ </div>
334
+
335
+ <div>
336
+ <label htmlFor="meta-lead-page-id" className="block text-sm font-medium text-gray-700">Page ID *</label>
337
+ <input
338
+ id="meta-lead-page-id"
339
+ type="text"
340
+ required
341
+ value={formData.pageId}
342
+ onChange={(e) => setFormData((prev) => ({ ...prev, pageId: e.target.value }))}
343
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
344
+ placeholder="Page ID Facebook"
345
+ />
346
+ </div>
347
+
348
+ <div>
349
+ <label htmlFor="meta-lead-access-token" className="block text-sm font-medium text-gray-700">Access Token *</label>
350
+ <input
351
+ id="meta-lead-access-token"
352
+ type="password"
353
+ autoComplete="off"
354
+ required
355
+ value={formData.accessToken}
356
+ onChange={(e) =>
357
+ setFormData((prev) => ({ ...prev, accessToken: e.target.value }))
358
+ }
359
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
360
+ placeholder="Access Token"
361
+ />
362
+ </div>
363
+
364
+ <div>
365
+ <label htmlFor="meta-lead-verify-token" className="block text-sm font-medium text-gray-700">Verify Token *</label>
366
+ <input
367
+ id="meta-lead-verify-token"
368
+ type="text"
369
+ required
370
+ value={formData.verifyToken}
371
+ onChange={(e) =>
372
+ setFormData((prev) => ({ ...prev, verifyToken: e.target.value }))
373
+ }
374
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
375
+ placeholder="Verify Token"
376
+ />
377
+ </div>
378
+
379
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
380
+ <div>
381
+ <label htmlFor="meta-lead-assigned-user" className="block text-sm font-medium text-gray-700">
382
+ Utilisateur assigné par défaut (optionnel)
383
+ </label>
384
+ <select
385
+ id="meta-lead-assigned-user"
386
+ value={formData.defaultAssignedUserId || ''}
387
+ onChange={(e) =>
388
+ setFormData((prev) => ({
389
+ ...prev,
390
+ defaultAssignedUserId: e.target.value || null,
391
+ }))
392
+ }
393
+ className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-2 text-gray-900 focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
394
+ >
395
+ <option value="">Aucun utilisateur par défaut</option>
396
+ {users.map((user) => (
397
+ <option key={user.id} value={user.id}>
398
+ {user.name} ({user.email})
399
+ </option>
400
+ ))}
401
+ </select>
402
+ </div>
403
+
404
+ <div>
405
+ <label htmlFor="meta-lead-default-status" className="block text-sm font-medium text-gray-700">
406
+ Statut par défaut
407
+ </label>
408
+ <StatusSelect
409
+ id="meta-lead-default-status"
410
+ statuses={[...statuses]}
411
+ value={formData.defaultStatusId || ''}
412
+ onChange={(v) =>
413
+ setFormData((prev) => ({
414
+ ...prev,
415
+ defaultStatusId: v || null,
416
+ }))
417
+ }
418
+ placeholder="Aucun statut par défaut"
419
+ className="mt-1"
420
+ />
421
+ </div>
422
+ </div>
423
+ </form>
424
+
425
+ <div className="shrink-0 border-t border-gray-100 pt-4">
426
+ <div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
427
+ <button
428
+ type="button"
429
+ onClick={resetForm}
430
+ className="w-full cursor-pointer rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 sm:w-auto"
431
+ >
432
+ Annuler
433
+ </button>
434
+ <button
435
+ type="submit"
436
+ form="meta-lead-form"
437
+ disabled={saving}
438
+ className="w-full cursor-pointer rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
439
+ >
440
+ {saving ? 'Enregistrement...' : 'Enregistrer'}
441
+ </button>
442
+ </div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+ )}
447
+
448
+ <ConfirmDialog />
449
+ </>
450
+ );
451
+ }