create-crm-tmp 1.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 (187) hide show
  1. package/bin/create-crm-tmp.js +93 -0
  2. package/package.json +25 -0
  3. package/template/.prettierignore +33 -0
  4. package/template/.prettierrc.json +25 -0
  5. package/template/README.md +173 -0
  6. package/template/eslint.config.mjs +18 -0
  7. package/template/exemple-contacts.csv +11 -0
  8. package/template/next.config.ts +8 -0
  9. package/template/package.json +64 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
  12. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
  13. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
  14. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
  15. package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
  16. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
  17. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
  18. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
  19. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
  20. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
  21. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
  22. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
  23. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
  24. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
  25. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
  26. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
  27. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
  28. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
  29. package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
  30. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
  31. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
  32. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
  33. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
  34. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
  35. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
  36. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
  37. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
  38. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
  39. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
  40. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
  41. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
  42. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
  43. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
  44. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
  45. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
  46. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
  47. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
  48. package/template/prisma/migrations/migration_lock.toml +3 -0
  49. package/template/prisma/schema.prisma +582 -0
  50. package/template/prisma.config.ts +14 -0
  51. package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
  52. package/template/src/app/(auth)/layout.tsx +3 -0
  53. package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
  54. package/template/src/app/(auth)/reset-password/page.tsx +146 -0
  55. package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
  56. package/template/src/app/(auth)/signin/page.tsx +166 -0
  57. package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
  58. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
  59. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
  60. package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
  61. package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
  62. package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
  63. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
  64. package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
  65. package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
  66. package/template/src/app/(dashboard)/layout.tsx +30 -0
  67. package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
  68. package/template/src/app/(dashboard)/templates/page.tsx +567 -0
  69. package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
  70. package/template/src/app/(dashboard)/users/page.tsx +457 -0
  71. package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
  72. package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
  73. package/template/src/app/api/audit-logs/route.ts +57 -0
  74. package/template/src/app/api/auth/[...all]/route.ts +4 -0
  75. package/template/src/app/api/auth/check-active/route.ts +31 -0
  76. package/template/src/app/api/auth/google/callback/route.ts +94 -0
  77. package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
  78. package/template/src/app/api/auth/google/route.ts +34 -0
  79. package/template/src/app/api/auth/google/status/route.ts +32 -0
  80. package/template/src/app/api/closing-reasons/route.ts +27 -0
  81. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
  82. package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
  83. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
  84. package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
  85. package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
  86. package/template/src/app/api/contacts/[id]/route.ts +322 -0
  87. package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
  88. package/template/src/app/api/contacts/export/route.ts +270 -0
  89. package/template/src/app/api/contacts/import/route.ts +381 -0
  90. package/template/src/app/api/contacts/route.ts +283 -0
  91. package/template/src/app/api/dashboard/stats/route.ts +299 -0
  92. package/template/src/app/api/email/track/[id]/route.ts +68 -0
  93. package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
  94. package/template/src/app/api/invite/complete/route.ts +88 -0
  95. package/template/src/app/api/invite/validate/route.ts +55 -0
  96. package/template/src/app/api/reminders/route.ts +95 -0
  97. package/template/src/app/api/reset-password/complete/route.ts +73 -0
  98. package/template/src/app/api/reset-password/request/route.ts +84 -0
  99. package/template/src/app/api/reset-password/validate/route.ts +49 -0
  100. package/template/src/app/api/reset-password/verify/route.ts +74 -0
  101. package/template/src/app/api/roles/[id]/route.ts +183 -0
  102. package/template/src/app/api/roles/route.ts +140 -0
  103. package/template/src/app/api/send/route.ts +282 -0
  104. package/template/src/app/api/settings/change-password/route.ts +95 -0
  105. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
  106. package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
  107. package/template/src/app/api/settings/company/route.ts +121 -0
  108. package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
  109. package/template/src/app/api/settings/google-ads/route.ts +122 -0
  110. package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
  111. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
  112. package/template/src/app/api/settings/google-sheet/route.ts +254 -0
  113. package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
  114. package/template/src/app/api/settings/meta-leads/route.ts +132 -0
  115. package/template/src/app/api/settings/profile/route.ts +42 -0
  116. package/template/src/app/api/settings/smtp/route.ts +130 -0
  117. package/template/src/app/api/settings/smtp/test/route.ts +121 -0
  118. package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
  119. package/template/src/app/api/settings/statuses/route.ts +83 -0
  120. package/template/src/app/api/statuses/route.ts +25 -0
  121. package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
  122. package/template/src/app/api/tasks/[id]/route.ts +728 -0
  123. package/template/src/app/api/tasks/meet/route.ts +240 -0
  124. package/template/src/app/api/tasks/route.ts +417 -0
  125. package/template/src/app/api/templates/[id]/route.ts +140 -0
  126. package/template/src/app/api/templates/route.ts +91 -0
  127. package/template/src/app/api/users/[id]/route.ts +168 -0
  128. package/template/src/app/api/users/list/route.ts +45 -0
  129. package/template/src/app/api/users/me/route.ts +48 -0
  130. package/template/src/app/api/users/route.ts +250 -0
  131. package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
  132. package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
  133. package/template/src/app/api/workflows/[id]/route.ts +192 -0
  134. package/template/src/app/api/workflows/process/route.ts +293 -0
  135. package/template/src/app/api/workflows/route.ts +124 -0
  136. package/template/src/app/favicon.ico +0 -0
  137. package/template/src/app/globals.css +1416 -0
  138. package/template/src/app/layout.tsx +31 -0
  139. package/template/src/app/page.tsx +32 -0
  140. package/template/src/components/dashboard/activity-chart.tsx +67 -0
  141. package/template/src/components/dashboard/contacts-chart.tsx +63 -0
  142. package/template/src/components/dashboard/recent-activity.tsx +164 -0
  143. package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
  144. package/template/src/components/dashboard/stat-card.tsx +61 -0
  145. package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
  146. package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
  147. package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
  148. package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
  149. package/template/src/components/editor.tsx +856 -0
  150. package/template/src/components/email-template.tsx +35 -0
  151. package/template/src/components/header.tsx +320 -0
  152. package/template/src/components/invitation-email-template.tsx +79 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +120 -0
  154. package/template/src/components/meet-confirmation-email-template.tsx +156 -0
  155. package/template/src/components/meet-update-email-template.tsx +209 -0
  156. package/template/src/components/page-header.tsx +61 -0
  157. package/template/src/components/reset-password-email-template.tsx +79 -0
  158. package/template/src/components/sidebar.tsx +294 -0
  159. package/template/src/components/skeleton.tsx +380 -0
  160. package/template/src/components/ui/commands.tsx +396 -0
  161. package/template/src/components/ui/components.tsx +150 -0
  162. package/template/src/components/ui/theme.tsx +5 -0
  163. package/template/src/components/view-as-banner.tsx +45 -0
  164. package/template/src/components/view-as-modal.tsx +186 -0
  165. package/template/src/contexts/mobile-menu-context.tsx +31 -0
  166. package/template/src/contexts/sidebar-context.tsx +107 -0
  167. package/template/src/contexts/task-reminder-context.tsx +239 -0
  168. package/template/src/contexts/view-as-context.tsx +84 -0
  169. package/template/src/hooks/use-user-role.ts +82 -0
  170. package/template/src/lib/audit-log.ts +45 -0
  171. package/template/src/lib/auth-client.ts +16 -0
  172. package/template/src/lib/auth.ts +35 -0
  173. package/template/src/lib/check-permission.ts +193 -0
  174. package/template/src/lib/contact-duplicate.ts +112 -0
  175. package/template/src/lib/contact-interactions.ts +371 -0
  176. package/template/src/lib/encryption.ts +99 -0
  177. package/template/src/lib/google-calendar.ts +300 -0
  178. package/template/src/lib/google-drive.ts +372 -0
  179. package/template/src/lib/permissions.ts +412 -0
  180. package/template/src/lib/prisma.ts +32 -0
  181. package/template/src/lib/roles.ts +120 -0
  182. package/template/src/lib/template-variables.ts +76 -0
  183. package/template/src/lib/utils.ts +46 -0
  184. package/template/src/lib/workflow-executor.ts +482 -0
  185. package/template/src/proxy.ts +91 -0
  186. package/template/tsconfig.json +34 -0
  187. package/template/vercel.json +8 -0
@@ -0,0 +1,482 @@
1
+ import { prisma } from '@/lib/prisma';
2
+ import nodemailer from 'nodemailer';
3
+ import { decrypt } from '@/lib/encryption';
4
+ import { replaceTemplateVariables } from '@/lib/template-variables';
5
+
6
+ /**
7
+ * Exécute les workflows déclenchés par la création d'un contact
8
+ */
9
+ export async function executeWorkflowsOnContactCreated(contactId: string) {
10
+ try {
11
+ // Récupérer tous les workflows actifs avec le déclencheur CONTACT_CREATED
12
+ const workflows = await prisma.workflow.findMany({
13
+ where: {
14
+ active: true,
15
+ triggerType: 'CONTACT_CREATED',
16
+ },
17
+ include: {
18
+ actions: {
19
+ orderBy: { order: 'asc' },
20
+ include: {
21
+ emailTemplate: true,
22
+ newStatus: true,
23
+ conditionStatus: true,
24
+ },
25
+ },
26
+ user: {
27
+ include: {
28
+ smtpConfig: true,
29
+ },
30
+ },
31
+ },
32
+ });
33
+
34
+ const contact = await prisma.contact.findUnique({
35
+ where: { id: contactId },
36
+ include: {
37
+ status: true,
38
+ },
39
+ });
40
+
41
+ if (!contact) return;
42
+
43
+ for (const workflow of workflows) {
44
+ await executeWorkflowActions(workflow, contactId, contact);
45
+ }
46
+ } catch (error) {
47
+ console.error('Erreur lors de l\'exécution des workflows pour contact créé:', error);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Exécute les workflows déclenchés par un changement de statut
53
+ */
54
+ export async function executeWorkflowsOnStatusChanged(
55
+ contactId: string,
56
+ oldStatusId: string | null,
57
+ newStatusId: string | null,
58
+ ) {
59
+ try {
60
+ // Récupérer tous les workflows actifs avec le déclencheur STATUS_CHANGED
61
+ const workflows = await prisma.workflow.findMany({
62
+ where: {
63
+ active: true,
64
+ triggerType: 'STATUS_CHANGED',
65
+ },
66
+ include: {
67
+ actions: {
68
+ orderBy: { order: 'asc' },
69
+ include: {
70
+ emailTemplate: true,
71
+ newStatus: true,
72
+ conditionStatus: true,
73
+ },
74
+ },
75
+ triggerStatus: true,
76
+ triggerToStatus: true,
77
+ user: {
78
+ include: {
79
+ smtpConfig: true,
80
+ },
81
+ },
82
+ },
83
+ });
84
+
85
+ const contact = await prisma.contact.findUnique({
86
+ where: { id: contactId },
87
+ include: {
88
+ status: true,
89
+ },
90
+ });
91
+
92
+ if (!contact) return;
93
+
94
+ // Filtrer les workflows qui correspondent aux conditions de déclenchement
95
+ const matchingWorkflows = workflows.filter((workflow) => {
96
+ // Si le workflow spécifie un statut source, vérifier qu'il correspond
97
+ if (workflow.triggerFromStatusId && oldStatusId !== workflow.triggerFromStatusId) {
98
+ return false;
99
+ }
100
+
101
+ // Si le workflow spécifie un statut cible, vérifier qu'il correspond
102
+ if (workflow.triggerToStatusId && newStatusId !== workflow.triggerToStatusId) {
103
+ return false;
104
+ }
105
+
106
+ return true;
107
+ });
108
+
109
+ for (const workflow of matchingWorkflows) {
110
+ await executeWorkflowActions(workflow, contactId, contact);
111
+ }
112
+ } catch (error) {
113
+ console.error('Erreur lors de l\'exécution des workflows pour changement de statut:', error);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Exécute les actions d'un workflow pour un contact
119
+ */
120
+ async function executeWorkflowActions(workflow: any, contactId: string, contact: any) {
121
+ for (const action of workflow.actions) {
122
+ // Vérifier la condition si elle existe
123
+ if (action.conditionOperator && action.conditionStatusId) {
124
+ const contactStatusId = contact.statusId;
125
+ const conditionMet =
126
+ action.conditionOperator === 'EQUALS'
127
+ ? contactStatusId === action.conditionStatusId
128
+ : contactStatusId !== action.conditionStatusId;
129
+
130
+ if (!conditionMet) {
131
+ continue; // Ignorer cette action si la condition n'est pas remplie
132
+ }
133
+ }
134
+
135
+ // Calculer la date d'exécution avec le délai
136
+ const delayMs =
137
+ (action.delayDays || 0) * 24 * 60 * 60 * 1000 + (action.delayHours || 0) * 60 * 60 * 1000;
138
+ const executeAt = new Date(Date.now() + delayMs);
139
+
140
+ // Exécuter l'action selon son type
141
+ switch (action.actionType) {
142
+ case 'SEND_EMAIL':
143
+ await executeSendEmailAction(action, workflow, contact, executeAt);
144
+ break;
145
+ case 'SEND_SMS':
146
+ await executeSendSMSAction(action, workflow, contact, executeAt);
147
+ break;
148
+ case 'CHANGE_STATUS':
149
+ await executeChangeStatusAction(action, workflow, contactId, executeAt);
150
+ break;
151
+ case 'CREATE_TASK':
152
+ await executeCreateTaskAction(action, workflow, contactId, executeAt);
153
+ break;
154
+ case 'WAIT':
155
+ // L'action "Attendre" est gérée par le délai, rien à faire ici
156
+ break;
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Exécute l'action "Envoyer un email"
163
+ */
164
+ async function executeSendEmailAction(
165
+ action: any,
166
+ workflow: any,
167
+ contact: any,
168
+ executeAt: Date,
169
+ ) {
170
+ if (!action.emailTemplateId || !workflow.user.smtpConfig) {
171
+ return;
172
+ }
173
+
174
+ try {
175
+ const template = action.emailTemplate;
176
+ if (!template) return;
177
+
178
+ // Préparer les variables de template (même logique que l'envoi manuel)
179
+ const variables = {
180
+ firstName: contact.firstName || '',
181
+ lastName: contact.lastName || '',
182
+ civility: contact.civility || '',
183
+ email: contact.email || '',
184
+ phone: contact.phone || '',
185
+ secondaryPhone: contact.secondaryPhone || '',
186
+ address: contact.address || '',
187
+ city: contact.city || '',
188
+ postalCode: contact.postalCode || '',
189
+ companyName: contact.companyName || '',
190
+ };
191
+
192
+ const subject = replaceTemplateVariables(template.subject || '', variables);
193
+ const content = replaceTemplateVariables(template.content || '', variables);
194
+
195
+ // Si le délai est 0, envoyer immédiatement, sinon planifier
196
+ if (executeAt <= new Date()) {
197
+ await sendEmailImmediate(workflow, contact, template, subject, content);
198
+ } else {
199
+ // Planifier l'action pour exécution différée
200
+ await prisma.scheduledWorkflowAction.create({
201
+ data: {
202
+ workflowId: workflow.id,
203
+ actionId: action.id,
204
+ contactId: contact.id,
205
+ actionType: 'SEND_EMAIL',
206
+ actionData: {
207
+ emailTemplateId: action.emailTemplateId,
208
+ templateSubject: template.subject,
209
+ templateContent: template.content,
210
+ templateName: template.name,
211
+ smtpConfigId: workflow.user.smtpConfig.id,
212
+ userId: workflow.userId,
213
+ workflowName: workflow.name,
214
+ },
215
+ executeAt,
216
+ },
217
+ });
218
+ }
219
+ } catch (error) {
220
+ console.error('Erreur lors de l\'envoi de l\'email:', error);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Exécute l'action "Envoyer un SMS"
226
+ */
227
+ async function executeSendSMSAction(action: any, workflow: any, contact: any, executeAt: Date) {
228
+ if (!action.smsMessage || !contact.phone) {
229
+ return;
230
+ }
231
+
232
+ try {
233
+ // Remplacer les variables dans le message
234
+ let message = action.smsMessage;
235
+ const variables: Record<string, string> = {
236
+ firstName: contact.firstName || '',
237
+ lastName: contact.lastName || '',
238
+ email: contact.email || '',
239
+ phone: contact.phone || '',
240
+ companyName: contact.companyName || '',
241
+ };
242
+
243
+ for (const [key, value] of Object.entries(variables)) {
244
+ message = message.replace(new RegExp(`{${key}}`, 'g'), value);
245
+ }
246
+
247
+ // Si le délai est 0, envoyer immédiatement
248
+ if (executeAt <= new Date()) {
249
+ await sendSMSImmediate(workflow, contact, message);
250
+ } else {
251
+ // Planifier l'action pour exécution différée
252
+ await prisma.scheduledWorkflowAction.create({
253
+ data: {
254
+ workflowId: workflow.id,
255
+ actionId: action.id,
256
+ contactId: contact.id,
257
+ actionType: 'SEND_SMS',
258
+ actionData: {
259
+ smsMessage: action.smsMessage,
260
+ userId: workflow.userId,
261
+ workflowName: workflow.name,
262
+ },
263
+ executeAt,
264
+ },
265
+ });
266
+ }
267
+ } catch (error) {
268
+ console.error('Erreur lors de l\'envoi du SMS:', error);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Exécute l'action "Changer le statut"
274
+ */
275
+ async function executeChangeStatusAction(
276
+ action: any,
277
+ workflow: any,
278
+ contactId: string,
279
+ executeAt: Date,
280
+ ) {
281
+ if (!action.newStatusId) {
282
+ return;
283
+ }
284
+
285
+ try {
286
+ // Si le délai est 0, changer immédiatement
287
+ if (executeAt <= new Date()) {
288
+ await changeStatusImmediate(contactId, action.newStatusId);
289
+ } else {
290
+ // Planifier l'action pour exécution différée
291
+ await prisma.scheduledWorkflowAction.create({
292
+ data: {
293
+ workflowId: workflow.id,
294
+ actionId: action.id,
295
+ contactId: contactId,
296
+ actionType: 'CHANGE_STATUS',
297
+ actionData: {
298
+ newStatusId: action.newStatusId,
299
+ workflowName: workflow.name,
300
+ },
301
+ executeAt,
302
+ },
303
+ });
304
+ }
305
+ } catch (error) {
306
+ console.error('Erreur lors du changement de statut:', error);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Exécute l'action "Créer une tâche"
312
+ */
313
+ async function executeCreateTaskAction(
314
+ action: any,
315
+ workflow: any,
316
+ contactId: string,
317
+ executeAt: Date,
318
+ ) {
319
+ if (!action.taskTitle) {
320
+ return;
321
+ }
322
+
323
+ try {
324
+ // Si le délai est 0, créer immédiatement
325
+ if (executeAt <= new Date()) {
326
+ await createTaskImmediate(action, workflow, contactId, executeAt);
327
+ } else {
328
+ // Planifier l'action pour exécution différée
329
+ await prisma.scheduledWorkflowAction.create({
330
+ data: {
331
+ workflowId: workflow.id,
332
+ actionId: action.id,
333
+ contactId: contactId,
334
+ actionType: 'CREATE_TASK',
335
+ actionData: {
336
+ taskTitle: action.taskTitle,
337
+ taskDescription: action.taskDescription || '',
338
+ taskPriority: 'MEDIUM',
339
+ userId: workflow.userId,
340
+ workflowName: workflow.name,
341
+ },
342
+ executeAt,
343
+ },
344
+ });
345
+ }
346
+ } catch (error) {
347
+ console.error('Erreur lors de la création de la tâche:', error);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Fonctions d'exécution immédiate (utilisées aussi par le cron)
353
+ */
354
+
355
+ /**
356
+ * Envoie un email immédiatement
357
+ */
358
+ export async function sendEmailImmediate(
359
+ workflow: any,
360
+ contact: any,
361
+ template: any,
362
+ subject: string,
363
+ content: string,
364
+ ) {
365
+ try {
366
+ if (!workflow.user.smtpConfig) {
367
+ throw new Error('Configuration SMTP non trouvée');
368
+ }
369
+
370
+ // Déchiffrer le mot de passe SMTP
371
+ let password: string;
372
+ try {
373
+ password = decrypt(workflow.user.smtpConfig.password);
374
+ } catch (error) {
375
+ password = workflow.user.smtpConfig.password;
376
+ }
377
+
378
+ // Créer le transporteur SMTP
379
+ const transporter = nodemailer.createTransport({
380
+ host: workflow.user.smtpConfig.host,
381
+ port: workflow.user.smtpConfig.port,
382
+ secure: workflow.user.smtpConfig.secure,
383
+ auth: {
384
+ user: workflow.user.smtpConfig.username,
385
+ pass: password,
386
+ },
387
+ });
388
+
389
+ await transporter.sendMail({
390
+ from: workflow.user.smtpConfig.fromName
391
+ ? `"${workflow.user.smtpConfig.fromName}" <${workflow.user.smtpConfig.fromEmail}>`
392
+ : workflow.user.smtpConfig.fromEmail,
393
+ to: contact.email,
394
+ subject,
395
+ html: content,
396
+ });
397
+
398
+ // Créer une interaction pour tracer l'email envoyé
399
+ await prisma.interaction.create({
400
+ data: {
401
+ contactId: contact.id,
402
+ type: 'EMAIL',
403
+ title: `Email automatique: ${template.name}`,
404
+ content: `Email envoyé automatiquement via le workflow "${workflow.name}"`,
405
+ userId: workflow.userId,
406
+ date: new Date(),
407
+ },
408
+ });
409
+ } catch (error) {
410
+ console.error('Erreur lors de l\'envoi de l\'email:', error);
411
+ throw error;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Envoie un SMS immédiatement
417
+ */
418
+ export async function sendSMSImmediate(workflow: any, contact: any, message: string) {
419
+ try {
420
+ // TODO: Implémenter l'envoi de SMS (nécessite une API SMS)
421
+ console.log(`SMS à envoyer à ${contact.phone}: ${message}`);
422
+
423
+ // Créer une interaction pour tracer le SMS
424
+ await prisma.interaction.create({
425
+ data: {
426
+ contactId: contact.id,
427
+ type: 'SMS',
428
+ title: 'SMS automatique',
429
+ content: message,
430
+ userId: workflow.userId,
431
+ date: new Date(),
432
+ },
433
+ });
434
+ } catch (error) {
435
+ console.error('Erreur lors de l\'envoi du SMS:', error);
436
+ throw error;
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Change le statut immédiatement
442
+ */
443
+ export async function changeStatusImmediate(contactId: string, newStatusId: string) {
444
+ try {
445
+ await prisma.contact.update({
446
+ where: { id: contactId },
447
+ data: { statusId: newStatusId },
448
+ });
449
+ } catch (error) {
450
+ console.error('Erreur lors du changement de statut:', error);
451
+ throw error;
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Crée une tâche immédiatement
457
+ */
458
+ export async function createTaskImmediate(
459
+ action: any,
460
+ workflow: any,
461
+ contactId: string,
462
+ scheduledAt: Date,
463
+ ) {
464
+ try {
465
+ await prisma.task.create({
466
+ data: {
467
+ type: 'OTHER',
468
+ title: action.taskTitle,
469
+ description: action.taskDescription || '',
470
+ priority: 'MEDIUM',
471
+ scheduledAt,
472
+ contactId: contactId,
473
+ assignedUserId: workflow.userId,
474
+ createdById: workflow.userId,
475
+ },
476
+ });
477
+ } catch (error) {
478
+ console.error('Erreur lors de la création de la tâche:', error);
479
+ throw error;
480
+ }
481
+ }
482
+
@@ -0,0 +1,91 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { NextRequest } from 'next/server';
3
+ import { auth } from './lib/auth';
4
+ import { isAdmin } from './lib/roles';
5
+ import { prisma } from './lib/prisma';
6
+
7
+ // Routes qui nécessitent une authentification
8
+ const protectedRoutes = [
9
+ '/dashboard',
10
+ '/contacts',
11
+ '/settings',
12
+ '/users',
13
+ '/agenda',
14
+ '/automatisation',
15
+ '/templates',
16
+ '/closing',
17
+ ];
18
+
19
+ // Routes réservées aux admins
20
+ const adminRoutes = ['/users'];
21
+
22
+ // Routes d'authentification
23
+ const authRoutes = ['/signin'];
24
+
25
+ export async function proxy(request: NextRequest) {
26
+ const { pathname } = request.nextUrl;
27
+
28
+ // Vérifier la session en utilisant Better Auth
29
+ const session = await auth.api.getSession({
30
+ headers: request.headers,
31
+ });
32
+
33
+ let isAuthenticated = !!session;
34
+ let isActiveUser = true;
35
+
36
+ // Récupérer le rôle depuis la session ou depuis la base de données
37
+ let userRole: string | null = null;
38
+ if (session && session.user?.id) {
39
+ const dbUser = await prisma.user.findUnique({
40
+ where: { id: session.user.id },
41
+ select: { role: true, active: true },
42
+ });
43
+
44
+ userRole = (session.user as any).role || dbUser?.role || null;
45
+ isActiveUser = dbUser?.active ?? true;
46
+ if (!isActiveUser) {
47
+ isAuthenticated = false;
48
+ }
49
+ }
50
+
51
+ // Vérifier si la route actuelle est protégée
52
+ const isProtectedRoute = protectedRoutes.some((route) => pathname.startsWith(route));
53
+
54
+ // Vérifier si la route est réservée aux admins
55
+ const isAdminRoute = adminRoutes.some((route) => pathname.startsWith(route));
56
+
57
+ // Vérifier si la route actuelle est une route d'auth
58
+ const isAuthRoute = authRoutes.some((route) => pathname.startsWith(route));
59
+
60
+ // Si l'utilisateur n'est pas connecté et tente d'accéder à une route protégée
61
+ if (!isAuthenticated && isProtectedRoute) {
62
+ const signInUrl = new URL('/signin', request.url);
63
+ signInUrl.searchParams.set('callbackUrl', pathname);
64
+ return NextResponse.redirect(signInUrl);
65
+ }
66
+
67
+ // Si l'utilisateur est connecté mais n'est pas admin et tente d'accéder à une route admin
68
+ if (isAuthenticated && isAdminRoute && !isAdmin(userRole || undefined)) {
69
+ return NextResponse.redirect(new URL('/dashboard', request.url));
70
+ }
71
+
72
+ // Si l'utilisateur est connecté et tente d'accéder aux pages d'auth
73
+ if (isAuthenticated && isAuthRoute) {
74
+ return NextResponse.redirect(new URL('/dashboard', request.url));
75
+ }
76
+
77
+ return NextResponse.next();
78
+ }
79
+
80
+ export const config = {
81
+ matcher: [
82
+ /*
83
+ * Match all request paths except for the ones starting with:
84
+ * - api (API routes)
85
+ * - _next/static (static files)
86
+ * - _next/image (image optimization files)
87
+ * - favicon.ico (favicon file)
88
+ */
89
+ '/((?!api|_next/static|_next/image|favicon.ico).*)',
90
+ ],
91
+ };
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "crons": [
3
+ {
4
+ "path": "/api/workflows/process",
5
+ "schedule": "* * * * *"
6
+ }
7
+ ]
8
+ }