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,299 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { headers } from 'next/headers';
4
+ import { prisma } from '@/lib/prisma';
5
+
6
+ export async function GET(req: NextRequest) {
7
+ try {
8
+ const session = await auth.api.getSession({
9
+ headers: await headers(),
10
+ });
11
+
12
+ if (!session) {
13
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
14
+ }
15
+
16
+ const userId = session.user.id;
17
+ const userRole = session.user.role;
18
+
19
+ // Déterminer les contacts visibles selon le rôle
20
+ const contactFilter =
21
+ userRole === 'ADMIN'
22
+ ? {}
23
+ : userRole === 'MANAGER'
24
+ ? {}
25
+ : userRole === 'COMMERCIAL'
26
+ ? { assignedCommercialId: userId }
27
+ : userRole === 'TELEPRO'
28
+ ? { assignedTeleproId: userId }
29
+ : { createdById: userId };
30
+
31
+ // 1. Total des contacts
32
+ const totalContacts = await prisma.contact.count({
33
+ where: contactFilter,
34
+ });
35
+
36
+ // 2. Contacts créés ce mois
37
+ const startOfMonth = new Date();
38
+ startOfMonth.setDate(1);
39
+ startOfMonth.setHours(0, 0, 0, 0);
40
+
41
+ const contactsThisMonth = await prisma.contact.count({
42
+ where: {
43
+ ...contactFilter,
44
+ createdAt: { gte: startOfMonth },
45
+ },
46
+ });
47
+
48
+ const lastMonthStart = new Date(startOfMonth);
49
+ lastMonthStart.setMonth(lastMonthStart.getMonth() - 1);
50
+
51
+ const contactsLastMonth = await prisma.contact.count({
52
+ where: {
53
+ ...contactFilter,
54
+ createdAt: {
55
+ gte: lastMonthStart,
56
+ lt: startOfMonth,
57
+ },
58
+ },
59
+ });
60
+
61
+ const contactsGrowth =
62
+ contactsLastMonth > 0
63
+ ? ((contactsThisMonth - contactsLastMonth) / contactsLastMonth) * 100
64
+ : 0;
65
+
66
+ // 3. Contacts par mois (12 derniers mois)
67
+ const monthsData = [];
68
+ for (let i = 11; i >= 0; i--) {
69
+ const monthStart = new Date();
70
+ monthStart.setMonth(monthStart.getMonth() - i);
71
+ monthStart.setDate(1);
72
+ monthStart.setHours(0, 0, 0, 0);
73
+
74
+ const monthEnd = new Date(monthStart);
75
+ monthEnd.setMonth(monthEnd.getMonth() + 1);
76
+
77
+ const count = await prisma.contact.count({
78
+ where: {
79
+ ...contactFilter,
80
+ createdAt: {
81
+ gte: monthStart,
82
+ lt: monthEnd,
83
+ },
84
+ },
85
+ });
86
+
87
+ monthsData.push({
88
+ month: monthStart.toLocaleString('fr-FR', { month: 'short' }),
89
+ count,
90
+ });
91
+ }
92
+
93
+ // 4. Répartition par statut (pour le radar chart)
94
+ const statusDistribution = await prisma.contact.groupBy({
95
+ by: ['statusId'],
96
+ where: contactFilter,
97
+ _count: true,
98
+ });
99
+
100
+ const statuses = await prisma.status.findMany();
101
+ const statusData = statuses.map((status) => ({
102
+ name: status.name,
103
+ value: statusDistribution.find((s) => s.statusId === status.id)?._count || 0,
104
+ }));
105
+
106
+ // 5. Tâches à venir (Top tasks)
107
+ const upcomingTasks = await prisma.task.findMany({
108
+ where: {
109
+ assignedUserId: userId,
110
+ completed: false,
111
+ scheduledAt: { gte: new Date() },
112
+ },
113
+ include: {
114
+ contact: true,
115
+ assignedUser: true,
116
+ },
117
+ orderBy: { scheduledAt: 'asc' },
118
+ take: 6,
119
+ });
120
+
121
+ // 6. Interactions récentes
122
+ const recentInteractions = await prisma.interaction.findMany({
123
+ where: {
124
+ userId,
125
+ },
126
+ include: {
127
+ contact: true,
128
+ user: true,
129
+ },
130
+ orderBy: { createdAt: 'desc' },
131
+ take: 5,
132
+ });
133
+
134
+ // 7. Statistiques des tâches
135
+ const totalTasks = await prisma.task.count({
136
+ where: { assignedUserId: userId },
137
+ });
138
+
139
+ const completedTasks = await prisma.task.count({
140
+ where: {
141
+ assignedUserId: userId,
142
+ completed: true,
143
+ },
144
+ });
145
+
146
+ const pendingTasks = totalTasks - completedTasks;
147
+
148
+ // 8. Tâches par type ce mois
149
+ const tasksThisMonthByType = await prisma.task.groupBy({
150
+ by: ['type'],
151
+ where: {
152
+ assignedUserId: userId,
153
+ createdAt: { gte: startOfMonth },
154
+ },
155
+ _count: true,
156
+ });
157
+
158
+ const tasksByType = tasksThisMonthByType.map((t) => ({
159
+ type: t.type,
160
+ count: t._count,
161
+ }));
162
+
163
+ // 9. Interactions par type ce mois
164
+ const interactionsThisMonth = await prisma.interaction.groupBy({
165
+ by: ['type'],
166
+ where: {
167
+ userId,
168
+ createdAt: { gte: startOfMonth },
169
+ },
170
+ _count: true,
171
+ });
172
+
173
+ const interactionsByType = interactionsThisMonth.map((i) => ({
174
+ type: i.type,
175
+ count: i._count,
176
+ }));
177
+
178
+ // 10. Activité des 7 derniers jours
179
+ const last7Days = [];
180
+ for (let i = 6; i >= 0; i--) {
181
+ const dayStart = new Date();
182
+ dayStart.setDate(dayStart.getDate() - i);
183
+ dayStart.setHours(0, 0, 0, 0);
184
+
185
+ const dayEnd = new Date(dayStart);
186
+ dayEnd.setDate(dayEnd.getDate() + 1);
187
+
188
+ const interactionsCount = await prisma.interaction.count({
189
+ where: {
190
+ userId,
191
+ createdAt: {
192
+ gte: dayStart,
193
+ lt: dayEnd,
194
+ },
195
+ },
196
+ });
197
+
198
+ const tasksCount = await prisma.task.count({
199
+ where: {
200
+ assignedUserId: userId,
201
+ createdAt: {
202
+ gte: dayStart,
203
+ lt: dayEnd,
204
+ },
205
+ },
206
+ });
207
+
208
+ last7Days.push({
209
+ date: dayStart.toLocaleDateString('fr-FR', {
210
+ day: 'numeric',
211
+ month: 'short',
212
+ }),
213
+ interactions: interactionsCount,
214
+ tasks: tasksCount,
215
+ });
216
+ }
217
+
218
+ // 11. Top contacts (ceux avec le plus d'interactions)
219
+ const topContacts = await prisma.contact.findMany({
220
+ where: contactFilter,
221
+ include: {
222
+ _count: {
223
+ select: { interactions: true },
224
+ },
225
+ status: true,
226
+ assignedCommercial: true,
227
+ assignedTelepro: true,
228
+ },
229
+ orderBy: {
230
+ createdAt: 'desc',
231
+ },
232
+ take: 4,
233
+ });
234
+
235
+ return NextResponse.json({
236
+ overview: {
237
+ totalContacts,
238
+ contactsThisMonth,
239
+ contactsGrowth: Math.round(contactsGrowth * 10) / 10,
240
+ monthsData,
241
+ },
242
+ statusDistribution: statusData,
243
+ tasks: {
244
+ total: totalTasks,
245
+ completed: completedTasks,
246
+ pending: pendingTasks,
247
+ upcoming: upcomingTasks.map((task) => ({
248
+ id: task.id,
249
+ title: task.title || 'Sans titre',
250
+ description: task.description,
251
+ type: task.type,
252
+ scheduledAt: task.scheduledAt,
253
+ contact: task.contact
254
+ ? {
255
+ id: task.contact.id,
256
+ name:
257
+ `${task.contact.firstName || ''} ${task.contact.lastName || ''}`.trim() ||
258
+ task.contact.phone,
259
+ }
260
+ : null,
261
+ priority: task.priority,
262
+ })),
263
+ byType: tasksByType,
264
+ },
265
+ interactions: {
266
+ recent: recentInteractions.map((interaction) => ({
267
+ id: interaction.id,
268
+ type: interaction.type,
269
+ title: interaction.title,
270
+ content: interaction.content,
271
+ date: interaction.date || interaction.createdAt,
272
+ contact: {
273
+ id: interaction.contact.id,
274
+ name:
275
+ `${interaction.contact.firstName || ''} ${interaction.contact.lastName || ''}`.trim() ||
276
+ interaction.contact.phone,
277
+ },
278
+ })),
279
+ byType: interactionsByType,
280
+ },
281
+ activity: {
282
+ last7Days,
283
+ },
284
+ topContacts: topContacts.map((contact) => ({
285
+ id: contact.id,
286
+ name: `${contact.firstName || ''} ${contact.lastName || ''}`.trim() || contact.phone,
287
+ phone: contact.phone,
288
+ email: contact.email,
289
+ status: contact.status?.name || 'Non défini',
290
+ interactionsCount: contact._count.interactions,
291
+ assignedCommercial: contact.assignedCommercial?.name,
292
+ assignedTelepro: contact.assignedTelepro?.name,
293
+ })),
294
+ });
295
+ } catch (error) {
296
+ console.error('Erreur lors de la récupération des statistiques:', error);
297
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
298
+ }
299
+ }
@@ -0,0 +1,68 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+
4
+ // GET /api/email/track/[id] - Tracker l'ouverture d'un email
5
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
6
+ try {
7
+ const { id } = await params;
8
+
9
+ // Vérifier que le tracking existe
10
+ const emailTracking = await prisma.emailTracking.findUnique({
11
+ where: { id },
12
+ });
13
+
14
+ if (!emailTracking) {
15
+ // Retourner une image transparente 1x1 même si le tracking n'existe pas
16
+ return new NextResponse(
17
+ Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64'),
18
+ {
19
+ status: 200,
20
+ headers: {
21
+ 'Content-Type': 'image/gif',
22
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
23
+ Pragma: 'no-cache',
24
+ Expires: '0',
25
+ },
26
+ },
27
+ );
28
+ }
29
+
30
+ // Mettre à jour le tracking
31
+ const now = new Date();
32
+ await prisma.emailTracking.update({
33
+ where: { id },
34
+ data: {
35
+ openCount: {
36
+ increment: 1,
37
+ },
38
+ firstOpenedAt: emailTracking.firstOpenedAt || now,
39
+ lastOpenedAt: now,
40
+ },
41
+ });
42
+
43
+ // Retourner une image transparente 1x1 pixel (GIF)
44
+ return new NextResponse(
45
+ Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64'),
46
+ {
47
+ status: 200,
48
+ headers: {
49
+ 'Content-Type': 'image/gif',
50
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
51
+ Pragma: 'no-cache',
52
+ Expires: '0',
53
+ },
54
+ },
55
+ );
56
+ } catch (error: any) {
57
+ // Retourner quand même une image transparente pour ne pas casser l'affichage
58
+ return new NextResponse(
59
+ Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64'),
60
+ {
61
+ status: 200,
62
+ headers: {
63
+ 'Content-Type': 'image/gif',
64
+ },
65
+ },
66
+ );
67
+ }
68
+ }