create-crm-tmp 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/package.json +1 -1
  2. package/template/.prettierignore +2 -0
  3. package/template/README.md +53 -67
  4. package/template/components.json +22 -0
  5. package/template/exemple-contacts.csv +54 -0
  6. package/template/next.config.ts +27 -1
  7. package/template/package.json +64 -27
  8. package/template/prisma/schema.prisma +821 -72
  9. package/template/skills-lock.json +25 -0
  10. package/template/src/app/(auth)/invite/[token]/page.tsx +21 -24
  11. package/template/src/app/(auth)/reset-password/complete/page.tsx +12 -21
  12. package/template/src/app/(auth)/reset-password/page.tsx +12 -8
  13. package/template/src/app/(auth)/reset-password/verify/page.tsx +12 -8
  14. package/template/src/app/(auth)/signin/page.tsx +20 -17
  15. package/template/src/app/(dashboard)/agenda/page.tsx +2231 -2188
  16. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
  17. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +680 -323
  18. package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
  19. package/template/src/app/(dashboard)/automatisation/page.tsx +473 -180
  20. package/template/src/app/(dashboard)/closing/page.tsx +500 -468
  21. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +5035 -4126
  22. package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1703 -0
  23. package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
  24. package/template/src/app/(dashboard)/contacts/page.tsx +3776 -2064
  25. package/template/src/app/(dashboard)/dashboard/page.tsx +37 -519
  26. package/template/src/app/(dashboard)/error.tsx +37 -0
  27. package/template/src/app/(dashboard)/layout.tsx +1 -1
  28. package/template/src/app/(dashboard)/loading.tsx +5 -0
  29. package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
  30. package/template/src/app/(dashboard)/settings/page.tsx +2685 -2489
  31. package/template/src/app/(dashboard)/templates/page.tsx +500 -300
  32. package/template/src/app/(dashboard)/users/list/page.tsx +356 -350
  33. package/template/src/app/(dashboard)/users/page.tsx +279 -310
  34. package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
  35. package/template/src/app/(dashboard)/users/roles/page.tsx +164 -137
  36. package/template/src/app/api/audit-logs/route.ts +1 -1
  37. package/template/src/app/api/auth/google/callback/route.ts +8 -5
  38. package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
  39. package/template/src/app/api/companies/[id]/activities/route.ts +131 -0
  40. package/template/src/app/api/companies/[id]/route.ts +195 -0
  41. package/template/src/app/api/companies/export/route.ts +206 -0
  42. package/template/src/app/api/companies/route.ts +166 -0
  43. package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
  44. package/template/src/app/api/contact-views/[id]/route.ts +197 -0
  45. package/template/src/app/api/contact-views/route.ts +146 -0
  46. package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +77 -0
  47. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +7 -17
  48. package/template/src/app/api/contacts/[id]/files/route.ts +83 -44
  49. package/template/src/app/api/contacts/[id]/interactions/route.ts +37 -0
  50. package/template/src/app/api/contacts/[id]/kyc/route.ts +71 -0
  51. package/template/src/app/api/contacts/[id]/meet/route.ts +38 -29
  52. package/template/src/app/api/contacts/[id]/route.ts +111 -20
  53. package/template/src/app/api/contacts/[id]/send-email/route.ts +6 -0
  54. package/template/src/app/api/contacts/[id]/workflows/run/route.ts +61 -0
  55. package/template/src/app/api/contacts/export/route.ts +12 -17
  56. package/template/src/app/api/contacts/import/route.ts +22 -19
  57. package/template/src/app/api/contacts/import-preview/route.ts +139 -0
  58. package/template/src/app/api/contacts/route.ts +202 -49
  59. package/template/src/app/api/dashboard/stats/route.ts +9 -292
  60. package/template/src/app/api/integrations/google-sheet/sync/route.ts +203 -185
  61. package/template/src/app/api/invite/complete/route.ts +20 -23
  62. package/template/src/app/api/reminders/route.ts +1 -0
  63. package/template/src/app/api/reset-password/complete/route.ts +11 -13
  64. package/template/src/app/api/send/route.ts +9 -85
  65. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
  66. package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
  67. package/template/src/app/api/settings/company/route.ts +19 -26
  68. package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
  69. package/template/src/app/api/settings/google-ads/route.ts +20 -23
  70. package/template/src/app/api/settings/google-sheet/[id]/route.ts +20 -23
  71. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +23 -32
  72. package/template/src/app/api/settings/google-sheet/preview/route.ts +104 -0
  73. package/template/src/app/api/settings/google-sheet/route.ts +20 -23
  74. package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -23
  75. package/template/src/app/api/settings/meta-leads/route.ts +20 -23
  76. package/template/src/app/api/settings/statuses/[id]/route.ts +33 -23
  77. package/template/src/app/api/settings/statuses/route.ts +24 -22
  78. package/template/src/app/api/statuses/route.ts +2 -5
  79. package/template/src/app/api/tasks/[id]/attendees/route.ts +14 -7
  80. package/template/src/app/api/tasks/[id]/route.ts +161 -137
  81. package/template/src/app/api/tasks/meet/route.ts +11 -8
  82. package/template/src/app/api/tasks/route.ts +155 -95
  83. package/template/src/app/api/templates/[id]/route.ts +22 -13
  84. package/template/src/app/api/templates/route.ts +22 -5
  85. package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
  86. package/template/src/app/api/users/[id]/route.ts +16 -1
  87. package/template/src/app/api/users/commercials/route.ts +38 -0
  88. package/template/src/app/api/users/for-agenda/route.ts +1 -2
  89. package/template/src/app/api/users/route.ts +94 -55
  90. package/template/src/app/api/webhooks/google-ads/route.ts +20 -1
  91. package/template/src/app/api/webhooks/meta-leads/route.ts +18 -1
  92. package/template/src/app/api/workflows/[id]/route.ts +33 -6
  93. package/template/src/app/api/workflows/process/route.ts +509 -146
  94. package/template/src/app/api/workflows/route.ts +46 -4
  95. package/template/src/app/globals.css +210 -101
  96. package/template/src/app/layout.tsx +19 -8
  97. package/template/src/app/page.tsx +37 -7
  98. package/template/src/components/address-autocomplete.tsx +232 -0
  99. package/template/src/components/contacts/filter-bar.tsx +181 -0
  100. package/template/src/components/contacts/filter-builder.tsx +589 -0
  101. package/template/src/components/contacts/save-view-dialog.tsx +160 -0
  102. package/template/src/components/contacts/views-tab-bar.tsx +440 -0
  103. package/template/src/components/dashboard/activity-chart.tsx +31 -39
  104. package/template/src/components/dashboard/dashboard-content.tsx +79 -0
  105. package/template/src/components/dashboard/stat-card.tsx +40 -42
  106. package/template/src/components/dashboard/tasks-pie-chart.tsx +34 -37
  107. package/template/src/components/dashboard/upcoming-tasks-list.tsx +78 -72
  108. package/template/src/components/date-picker.tsx +396 -0
  109. package/template/src/components/editor.tsx +27 -13
  110. package/template/src/components/email-template.tsx +4 -2
  111. package/template/src/components/global-search.tsx +358 -0
  112. package/template/src/components/header.tsx +57 -62
  113. package/template/src/components/invitation-email-template.tsx +4 -2
  114. package/template/src/components/lazy-editor.tsx +11 -0
  115. package/template/src/components/meet-cancellation-email-template.tsx +11 -3
  116. package/template/src/components/meet-confirmation-email-template.tsx +10 -3
  117. package/template/src/components/meet-update-email-template.tsx +10 -3
  118. package/template/src/components/page-header.tsx +19 -15
  119. package/template/src/components/protected-page.tsx +94 -0
  120. package/template/src/components/reset-password-email-template.tsx +4 -2
  121. package/template/src/components/sidebar.tsx +92 -94
  122. package/template/src/components/skeleton.tsx +128 -42
  123. package/template/src/components/ui/accordion.tsx +64 -0
  124. package/template/src/components/ui/alert-dialog.tsx +139 -0
  125. package/template/src/components/ui/button.tsx +60 -0
  126. package/template/src/components/view-as-banner.tsx +1 -1
  127. package/template/src/components/view-as-modal.tsx +21 -16
  128. package/template/src/config/nav-pages.ts +108 -0
  129. package/template/src/contexts/app-toast-context.tsx +174 -0
  130. package/template/src/contexts/sidebar-context.tsx +16 -47
  131. package/template/src/contexts/task-reminder-context.tsx +6 -6
  132. package/template/src/contexts/view-as-context.tsx +11 -16
  133. package/template/src/hooks/use-alert.tsx +65 -0
  134. package/template/src/hooks/use-confirm.tsx +87 -0
  135. package/template/src/hooks/use-contact-views.ts +140 -0
  136. package/template/src/hooks/use-contacts.ts +69 -0
  137. package/template/src/hooks/use-fetch.ts +17 -0
  138. package/template/src/hooks/use-focus-trap.ts +73 -0
  139. package/template/src/hooks/use-statuses.ts +22 -0
  140. package/template/src/lib/address-api.ts +155 -0
  141. package/template/src/lib/cache.ts +73 -0
  142. package/template/src/lib/check-permission.ts +12 -177
  143. package/template/src/lib/contact-interactions.ts +3 -1
  144. package/template/src/lib/contact-view-filters.ts +341 -0
  145. package/template/src/lib/dashboard-stats.ts +224 -0
  146. package/template/src/lib/date-utils.ts +49 -0
  147. package/template/src/lib/get-auth-user.ts +25 -0
  148. package/template/src/lib/google-calendar.ts +54 -12
  149. package/template/src/lib/google-drive.ts +796 -75
  150. package/template/src/lib/google-fetch.ts +63 -0
  151. package/template/src/lib/local-storage.ts +34 -0
  152. package/template/src/lib/permissions.ts +245 -47
  153. package/template/src/lib/prisma.ts +11 -11
  154. package/template/src/lib/roles.ts +14 -39
  155. package/template/src/lib/template-variables.ts +67 -33
  156. package/template/src/lib/utils.ts +26 -2
  157. package/template/src/lib/workflow-executor.ts +445 -229
  158. package/template/src/proxy.ts +34 -73
  159. package/template/src/types/contact-views.ts +351 -0
  160. package/template/src/types/yousign.ts +52 -0
  161. package/template/vercel.json +12 -0
  162. package/template/WORKFLOWS_CRON.md +0 -185
  163. package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
  164. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
  165. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
  166. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
  167. package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
  168. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
  169. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
  170. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
  171. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
  172. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
  173. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
  174. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
  175. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
  176. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
  177. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
  178. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
  179. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
  180. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
  181. package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
  182. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
  183. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
  184. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
  185. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
  186. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
  187. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
  188. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
  189. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
  190. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
  191. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
  192. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
  193. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
  194. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
  195. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
  196. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
  197. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
  198. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
  199. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
  200. package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
  201. package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
  202. package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
  203. package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
  204. package/template/prisma/migrations/migration_lock.toml +0 -3
  205. package/template/src/app/(dashboard)/users/layout.tsx +0 -30
  206. package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -47
  207. package/template/src/app/api/dashboard/widgets/route.ts +0 -181
  208. package/template/src/components/dashboard/add-widget-dialog.tsx +0 -161
  209. package/template/src/components/dashboard/color-picker.tsx +0 -65
  210. package/template/src/components/dashboard/contacts-chart.tsx +0 -69
  211. package/template/src/components/dashboard/interactions-by-type-chart.tsx +0 -121
  212. package/template/src/components/dashboard/recent-activity.tsx +0 -157
  213. package/template/src/components/dashboard/sales-analytics-chart.tsx +0 -77
  214. package/template/src/components/dashboard/status-distribution-chart.tsx +0 -82
  215. package/template/src/components/dashboard/top-contacts-list.tsx +0 -119
  216. package/template/src/components/dashboard/widget-wrapper.tsx +0 -39
  217. package/template/src/contexts/dashboard-theme-context.tsx +0 -58
  218. package/template/src/lib/dashboard-themes.ts +0 -140
  219. package/template/src/lib/default-widgets.ts +0 -14
  220. package/template/src/lib/widget-registry.ts +0 -177
@@ -0,0 +1,38 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // GET /api/users/commercials - Récupérer la liste des commerciaux (accessible à tous les utilisateurs authentifiés)
6
+ export async function GET(request: NextRequest) {
7
+ try {
8
+ const session = await auth.api.getSession({
9
+ headers: request.headers,
10
+ });
11
+
12
+ if (!session) {
13
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
14
+ }
15
+
16
+ // Récupérer tous les utilisateurs avec le rôle COMMERCIAL, ADMIN ou MANAGER
17
+ const users = await prisma.user.findMany({
18
+ where: {
19
+ active: true,
20
+ role: {
21
+ in: ['COMMERCIAL', 'ADMIN', 'MANAGER'],
22
+ },
23
+ },
24
+ select: {
25
+ id: true,
26
+ name: true,
27
+ email: true,
28
+ role: true,
29
+ },
30
+ orderBy: { name: 'asc' },
31
+ });
32
+
33
+ return NextResponse.json(users);
34
+ } catch (error: any) {
35
+ console.error('Erreur lors de la récupération des commerciaux:', error);
36
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
37
+ }
38
+ }
@@ -3,7 +3,7 @@ import { auth } from '@/lib/auth';
3
3
  import { prisma } from '@/lib/prisma';
4
4
  import { checkPermission } from '@/lib/check-permission';
5
5
 
6
- // GET /api/users/for-agenda - Liste les utilisateurs avec leur eventColor pour le filtre de l'agenda
6
+ // GET /api/users/for-agenda - Liste les utilisateurs pour le filtre de l'agenda
7
7
  export async function GET(request: NextRequest) {
8
8
  try {
9
9
  const session = await auth.api.getSession({
@@ -25,7 +25,6 @@ export async function GET(request: NextRequest) {
25
25
  id: true,
26
26
  name: true,
27
27
  email: true,
28
- eventColor: true,
29
28
  },
30
29
  orderBy: {
31
30
  name: 'asc',
@@ -1,8 +1,16 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
+ import { z } from 'zod';
2
3
  import { prisma } from '@/lib/prisma';
3
4
  import { checkPermission } from '@/lib/check-permission';
4
5
  import { auth } from '@/lib/auth';
5
6
  import { logAudit } from '@/lib/audit-log';
7
+ import { resolveRoleFromCustomRoleName } from '@/lib/roles';
8
+
9
+ const createUserSchema = z.object({
10
+ name: z.string().trim().min(1, 'Le nom est requis'),
11
+ email: z.string().trim().email('Email invalide'),
12
+ customRoleId: z.string().trim().min(1, 'Le profil est requis'),
13
+ });
6
14
 
7
15
  // GET /api/users - Liste tous les utilisateurs (admin seulement)
8
16
  export async function GET(request: NextRequest) {
@@ -29,27 +37,68 @@ export async function GET(request: NextRequest) {
29
37
  name: true,
30
38
  },
31
39
  },
40
+ accounts: {
41
+ where: { providerId: 'credential' },
42
+ select: { id: true },
43
+ },
32
44
  },
33
45
  orderBy: {
34
46
  createdAt: 'desc',
35
47
  },
36
48
  });
37
49
 
38
- // Mapper les utilisateurs avec le profil
39
- const usersWithRole = users.map((user: any) => ({
40
- id: user.id,
41
- name: user.name,
42
- email: user.email,
43
- role: user.role || 'USER',
44
- customRoleId: user.customRoleId,
45
- customRole: user.customRole,
46
- emailVerified: user.emailVerified,
47
- active: user.active,
48
- createdAt: user.createdAt,
49
- updatedAt: user.updatedAt,
50
- image: user.image,
51
- eventColor: user.eventColor,
52
- }));
50
+ const emails: string[] = [];
51
+ for (const u of users) {
52
+ if (!u.emailVerified && u.accounts.length === 0) {
53
+ emails.push(u.email);
54
+ }
55
+ }
56
+
57
+ const verifications =
58
+ emails.length > 0
59
+ ? await prisma.verification.findMany({
60
+ where: { identifier: { in: emails } },
61
+ select: { identifier: true, expiresAt: true },
62
+ orderBy: { expiresAt: 'desc' },
63
+ })
64
+ : [];
65
+
66
+ const latestTokenByEmail = new Map<string, Date>();
67
+ for (const v of verifications) {
68
+ if (!latestTokenByEmail.has(v.identifier)) {
69
+ latestTokenByEmail.set(v.identifier, v.expiresAt);
70
+ }
71
+ }
72
+
73
+ const now = new Date();
74
+ const usersWithRole = users.map((user: any) => {
75
+ let invitationStatus: 'completed' | 'pending' | 'expired' | null = null;
76
+ if (user.emailVerified || user.accounts.length > 0) {
77
+ invitationStatus = 'completed';
78
+ } else {
79
+ const expiresAt = latestTokenByEmail.get(user.email);
80
+ if (!expiresAt) {
81
+ invitationStatus = 'expired';
82
+ } else {
83
+ invitationStatus = expiresAt > now ? 'pending' : 'expired';
84
+ }
85
+ }
86
+
87
+ return {
88
+ id: user.id,
89
+ name: user.name,
90
+ email: user.email,
91
+ role: user.role || 'USER',
92
+ customRoleId: user.customRoleId,
93
+ customRole: user.customRole,
94
+ emailVerified: user.emailVerified,
95
+ active: user.active,
96
+ createdAt: user.createdAt,
97
+ updatedAt: user.updatedAt,
98
+ image: user.image,
99
+ invitationStatus,
100
+ };
101
+ });
53
102
 
54
103
  return NextResponse.json(usersWithRole);
55
104
  } catch (error: any) {
@@ -75,17 +124,20 @@ export async function POST(request: NextRequest) {
75
124
  return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
76
125
  }
77
126
 
78
- const body = await request.json();
79
- const { name, email, customRoleId } = body;
127
+ const json = await request.json();
128
+ const parseResult = createUserSchema.safeParse(json);
80
129
 
81
- // Validation
82
- if (!name || !email) {
83
- return NextResponse.json({ error: 'Nom et email requis' }, { status: 400 });
130
+ if (!parseResult.success) {
131
+ return NextResponse.json(
132
+ {
133
+ error: 'Données invalides',
134
+ details: parseResult.error.flatten(),
135
+ },
136
+ { status: 400 },
137
+ );
84
138
  }
85
139
 
86
- if (!customRoleId) {
87
- return NextResponse.json({ error: 'Le profil est requis' }, { status: 400 });
88
- }
140
+ const { name, email, customRoleId } = parseResult.data;
89
141
 
90
142
  // Vérifier si l'email existe déjà
91
143
  const existingUser = await prisma.user.findUnique({
@@ -105,59 +157,38 @@ export async function POST(request: NextRequest) {
105
157
  // Si l'utilisateur existe mais sans compte, on peut régénérer un token
106
158
  }
107
159
 
160
+ const customRole = await prisma.customRole.findUnique({
161
+ where: { id: customRoleId },
162
+ select: { name: true },
163
+ });
164
+ const resolvedRole = resolveRoleFromCustomRoleName(customRole?.name);
165
+
108
166
  let user;
109
167
  if (existingUser && existingUser.accounts.length === 0) {
110
- // Utilisateur existe déjà sans compte, on met à jour et régénère le token
111
168
  user = await prisma.user.update({
112
169
  where: { id: existingUser.id },
113
170
  data: {
114
171
  name,
115
172
  customRoleId,
173
+ role: resolvedRole,
116
174
  active: true,
117
175
  },
118
176
  });
119
177
  } else {
120
- // Générer une couleur aléatoire pour les événements
121
- const colors = [
122
- '#EF4444', // Rouge
123
- '#3B82F6', // Bleu
124
- '#10B981', // Vert
125
- '#F59E0B', // Orange
126
- '#8B5CF6', // Violet
127
- '#EC4899', // Rose
128
- '#06B6D4', // Cyan
129
- '#84CC16', // Lime
130
- '#F97316', // Orange foncé
131
- '#6366F1', // Indigo
132
- '#14B8A6', // Teal
133
- '#A855F7', // Purple
134
- ];
135
- const randomColor = colors[Math.floor(Math.random() * colors.length)];
136
-
137
- // Créer l'utilisateur SANS mot de passe (sans Account)
138
178
  user = await prisma.user.create({
139
179
  data: {
140
180
  id: crypto.randomUUID(),
141
181
  name,
142
182
  email,
143
- role: 'USER', // Rôle par défaut, mais les permissions viendront du customRole
183
+ role: resolvedRole,
144
184
  customRoleId,
145
- emailVerified: false, // Pas encore vérifié
185
+ emailVerified: false,
146
186
  active: true,
147
- eventColor: randomColor,
148
187
  },
149
188
  });
150
189
  }
151
190
 
152
- // Récupérer le nom du profil pour les métadonnées
153
- let customRoleName: string | null = null;
154
- if (customRoleId) {
155
- const customRole = await prisma.customRole.findUnique({
156
- where: { id: customRoleId },
157
- select: { name: true },
158
- });
159
- customRoleName = customRole?.name || null;
160
- }
191
+ const customRoleName = customRole?.name || null;
161
192
 
162
193
  // Log d'audit : création ou réactivation d'utilisateur
163
194
  await logAudit({
@@ -264,6 +295,14 @@ export async function POST(request: NextRequest) {
264
295
  return NextResponse.json({ error: 'Cet email est déjà utilisé' }, { status: 400 });
265
296
  }
266
297
 
267
- return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
298
+ return NextResponse.json(
299
+ {
300
+ error:
301
+ process.env.NODE_ENV === 'development'
302
+ ? error.message || 'Erreur serveur'
303
+ : 'Erreur serveur',
304
+ },
305
+ { status: 500 },
306
+ );
268
307
  }
269
308
  }
@@ -1,4 +1,5 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
+ import crypto from 'crypto';
2
3
  import { prisma } from '@/lib/prisma';
3
4
  import { handleContactDuplicate } from '@/lib/contact-duplicate';
4
5
  import { normalizePhoneNumber } from '@/lib/utils';
@@ -18,7 +19,8 @@ interface GoogleAdsLeadNotification {
18
19
  // POST /api/webhooks/google-ads - Réception des leads Google Ads (lead form extensions)
19
20
  export async function POST(request: NextRequest) {
20
21
  try {
21
- const body = await request.json();
22
+ const rawBody = await request.text();
23
+ const body = JSON.parse(rawBody);
22
24
 
23
25
  const notification: GoogleAdsLeadNotification | undefined = body?.leadNotification;
24
26
 
@@ -27,6 +29,23 @@ export async function POST(request: NextRequest) {
27
29
  return NextResponse.json({ received: true });
28
30
  }
29
31
 
32
+ // Sécurité HMAC (si configurée)
33
+ const webhookSecret = process.env.GOOGLE_ADS_WEBHOOK_SECRET;
34
+ if (webhookSecret) {
35
+ const signatureHeader = request.headers.get('x-goog-signature');
36
+ if (signatureHeader) {
37
+ const expectedSignature = crypto
38
+ .createHmac('sha256', webhookSecret)
39
+ .update(rawBody)
40
+ .digest('base64');
41
+
42
+ if (signatureHeader !== expectedSignature) {
43
+ console.error('Webhook Google Ads Lead Forms: Signature HMAC invalide');
44
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
45
+ }
46
+ }
47
+ }
48
+
30
49
  const client = prisma as any;
31
50
 
32
51
  // Récupérer toutes les configurations actives
@@ -1,4 +1,5 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
+ import crypto from 'crypto';
2
3
  import { prisma } from '@/lib/prisma';
3
4
  import { decrypt } from '@/lib/encryption';
4
5
  import { handleContactDuplicate } from '@/lib/contact-duplicate';
@@ -50,12 +51,28 @@ export async function GET(request: NextRequest) {
50
51
  // POST /api/webhooks/meta-leads - Réception des leads Meta
51
52
  export async function POST(request: NextRequest) {
52
53
  try {
53
- const body = await request.json();
54
+ const rawBody = await request.text();
55
+ const body = JSON.parse(rawBody);
54
56
 
55
57
  if (body.object !== 'page' || !Array.isArray(body.entry)) {
56
58
  return NextResponse.json({ received: true });
57
59
  }
58
60
 
61
+ // Sécurité HMAC (si configurée)
62
+ const webhookSecret = process.env.META_LEADS_WEBHOOK_SECRET;
63
+ if (webhookSecret) {
64
+ const signatureHeader = request.headers.get('x-hub-signature-256');
65
+ if (signatureHeader) {
66
+ const expectedSignature =
67
+ 'sha256=' + crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
68
+
69
+ if (signatureHeader !== expectedSignature) {
70
+ console.error('Webhook Meta Lead Ads: Signature HMAC invalide');
71
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
72
+ }
73
+ }
74
+ }
75
+
59
76
  // Récupérer toutes les configurations actives
60
77
  const configs = await prisma.metaLeadConfig.findMany({
61
78
  where: { active: true },
@@ -1,6 +1,7 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { auth } from '@/lib/auth';
3
3
  import { prisma } from '@/lib/prisma';
4
+ import { checkPermission } from '@/lib/check-permission';
4
5
 
5
6
  // GET /api/workflows/[id] - Récupérer un workflow spécifique
6
7
  export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
@@ -13,6 +14,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
13
14
  return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
14
15
  }
15
16
 
17
+ const canView = await checkPermission('workflows.view');
18
+ if (!canView) {
19
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
20
+ }
21
+
16
22
  const { id } = await params;
17
23
 
18
24
  const workflow = await prisma.workflow.findFirst({
@@ -56,6 +62,11 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
56
62
  return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
57
63
  }
58
64
 
65
+ const canEdit = await checkPermission('workflows.edit');
66
+ if (!canEdit) {
67
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
68
+ }
69
+
59
70
  const { id } = await params;
60
71
  const body = await request.json();
61
72
  const {
@@ -67,10 +78,13 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
67
78
  triggerToStatusId,
68
79
  triggerTimeDays,
69
80
  triggerTimeHours,
81
+ triggerTimeReference,
82
+ triggerTaskType,
83
+ triggerTransactionFromStatus,
84
+ triggerTransactionToStatus,
70
85
  actions = [],
71
86
  } = body;
72
87
 
73
- // Vérifier que le workflow appartient à l'utilisateur
74
88
  const existingWorkflow = await prisma.workflow.findFirst({
75
89
  where: {
76
90
  id,
@@ -82,7 +96,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
82
96
  return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
83
97
  }
84
98
 
85
- // Validation
86
99
  if (!name || !triggerType) {
87
100
  return NextResponse.json(
88
101
  { error: 'Le nom et le type de déclencheur sont requis' },
@@ -90,12 +103,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
90
103
  );
91
104
  }
92
105
 
93
- // Supprimer les anciennes actions
94
106
  await prisma.workflowAction.deleteMany({
95
107
  where: { workflowId: id },
96
108
  });
97
109
 
98
- // Mettre à jour le workflow
99
110
  const workflow = await prisma.workflow.update({
100
111
  where: { id },
101
112
  data: {
@@ -107,6 +118,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
107
118
  triggerToStatusId: triggerToStatusId || null,
108
119
  triggerTimeDays: triggerTimeDays || null,
109
120
  triggerTimeHours: triggerTimeHours || null,
121
+ triggerTimeReference: triggerTimeReference || null,
122
+ triggerTaskType: triggerTaskType || null,
123
+ triggerTransactionFromStatus: triggerTransactionFromStatus || null,
124
+ triggerTransactionToStatus: triggerTransactionToStatus || null,
110
125
  actions: {
111
126
  create: actions.map((action: any, index: number) => ({
112
127
  actionType: action.actionType,
@@ -118,8 +133,17 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
118
133
  newStatusId: action.newStatusId || null,
119
134
  taskTitle: action.taskTitle || null,
120
135
  taskDescription: action.taskDescription || null,
136
+ taskType: action.taskType || null,
137
+ taskPriority: action.taskPriority || null,
138
+ taskAssignedUserId: action.taskAssignedUserId || null,
139
+ assignCommercialId: action.assignCommercialId || null,
140
+ assignTeleproId: action.assignTeleproId || null,
141
+ noteContent: action.noteContent || null,
142
+ notifyUserId: action.notifyUserId || null,
121
143
  conditionOperator: action.conditionOperator || null,
122
144
  conditionStatusId: action.conditionStatusId || null,
145
+ conditionOrigin: action.conditionOrigin || null,
146
+ conditionHasCompany: action.conditionHasCompany ?? null,
123
147
  })),
124
148
  },
125
149
  },
@@ -158,9 +182,13 @@ export async function DELETE(
158
182
  return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
159
183
  }
160
184
 
185
+ const canDelete = await checkPermission('workflows.delete');
186
+ if (!canDelete) {
187
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
188
+ }
189
+
161
190
  const { id } = await params;
162
191
 
163
- // Vérifier que le workflow appartient à l'utilisateur
164
192
  const workflow = await prisma.workflow.findFirst({
165
193
  where: {
166
194
  id,
@@ -172,7 +200,6 @@ export async function DELETE(
172
200
  return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
173
201
  }
174
202
 
175
- // Supprimer le workflow (les actions seront supprimées en cascade)
176
203
  await prisma.workflow.delete({
177
204
  where: { id },
178
205
  });