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
@@ -8,8 +8,6 @@ import nodemailer from 'nodemailer';
8
8
  import { render } from '@react-email/render';
9
9
  import React from 'react';
10
10
 
11
- const isDevelopment = process.env.NODE_ENV === 'development';
12
-
13
11
  function htmlToText(html: string): string {
14
12
  if (!html) return '';
15
13
  return html
@@ -22,7 +20,6 @@ function htmlToText(html: string): string {
22
20
 
23
21
  async function getAdminSmtpConfig(userId: string) {
24
22
  try {
25
- // Récupérer la configuration SMTP de l'administrateur connecté
26
23
  const user = await prisma.user.findUnique({
27
24
  where: { id: userId },
28
25
  include: {
@@ -31,12 +28,10 @@ async function getAdminSmtpConfig(userId: string) {
31
28
  });
32
29
 
33
30
  if (!user) {
34
- console.error('❌ Utilisateur non trouvé:', userId);
35
31
  return { config: null, error: 'Utilisateur non trouvé' };
36
32
  }
37
33
 
38
34
  if (!user.smtpConfig) {
39
- console.error("❌ Aucune configuration SMTP trouvée pour l'utilisateur:", user.email);
40
35
  return {
41
36
  config: null,
42
37
  error:
@@ -44,7 +39,6 @@ async function getAdminSmtpConfig(userId: string) {
44
39
  };
45
40
  }
46
41
 
47
- console.log('✅ Configuration SMTP trouvée pour:', user.email);
48
42
  return { config: user.smtpConfig, error: null };
49
43
  } catch (error) {
50
44
  console.error('Erreur lors de la récupération de la configuration SMTP:', error);
@@ -57,8 +51,6 @@ async function getAdminSmtpConfig(userId: string) {
57
51
 
58
52
  async function getAnyAdminSmtpConfig() {
59
53
  try {
60
- // Récupérer la première configuration SMTP d'un administrateur
61
- // On cherche directement dans SmtpConfig et on joint avec User pour vérifier le rôle
62
54
  const smtpConfig = await prisma.smtpConfig.findFirst({
63
55
  where: {
64
56
  user: {
@@ -79,22 +71,12 @@ async function getAnyAdminSmtpConfig() {
79
71
  });
80
72
 
81
73
  if (!smtpConfig) {
82
- console.error('❌ Aucune configuration SMTP trouvée pour un administrateur');
83
-
84
- // Log supplémentaire pour debug : vérifier s'il y a des admins
85
- const adminCount = await prisma.user.count({
86
- where: { role: 'ADMIN' },
87
- });
88
- const smtpConfigCount = await prisma.smtpConfig.count();
89
- console.error('Debug - Admins:', adminCount, 'Configs SMTP:', smtpConfigCount);
90
-
91
74
  return {
92
75
  config: null,
93
76
  error: 'Aucune configuration SMTP trouvée. Veuillez configurer SMTP dans les paramètres.',
94
77
  };
95
78
  }
96
79
 
97
- console.log("✅ Configuration SMTP trouvée pour l'admin:", smtpConfig.user.email);
98
80
  return { config: smtpConfig, error: null };
99
81
  } catch (error) {
100
82
  console.error('Erreur lors de la récupération de la configuration SMTP admin:', error);
@@ -110,36 +92,22 @@ export async function POST(request: Request) {
110
92
  const body = await request.json();
111
93
  const { to, subject, template, ...emailData } = body;
112
94
 
113
- // Récupérer la session de l'utilisateur connecté (optionnel pour reset-password)
114
95
  const session = await auth.api.getSession({
115
96
  headers: request.headers,
116
97
  });
117
98
 
118
- // Pour le reset password, on n'a pas besoin de session
119
99
  const isResetPassword = template === 'reset-password';
120
100
 
121
101
  if (!isResetPassword && (!session || !session.user?.id)) {
122
- console.error('❌ Utilisateur non authentifié');
123
102
  return Response.json({ error: 'Non authentifié' }, { status: 401 });
124
103
  }
125
104
 
126
- console.log("📧 Tentative d'envoi d'email:", {
127
- to,
128
- subject,
129
- template,
130
- isDevelopment,
131
- userId: session?.user?.id || 'none (reset-password)',
132
- });
133
-
134
- // Récupérer la configuration SMTP
135
105
  let smtpConfig, smtpError;
136
106
  if (isResetPassword) {
137
- // Pour le reset password, utiliser n'importe quelle config SMTP d'admin
138
107
  const result = await getAnyAdminSmtpConfig();
139
108
  smtpConfig = result.config;
140
109
  smtpError = result.error;
141
110
  } else {
142
- // Pour les autres emails, utiliser la config de l'utilisateur connecté
143
111
  const result = await getAdminSmtpConfig(session!.user.id);
144
112
  smtpConfig = result.config;
145
113
  smtpError = result.error;
@@ -149,11 +117,9 @@ export async function POST(request: Request) {
149
117
  const errorMsg =
150
118
  smtpError ||
151
119
  'Aucune configuration SMTP trouvée. Veuillez configurer SMTP dans les paramètres.';
152
- console.error('❌', errorMsg);
153
120
  return Response.json({ error: errorMsg }, { status: 400 });
154
121
  }
155
122
 
156
- // Sélectionner le template approprié et le rendre en HTML
157
123
  let emailComponent: React.ReactElement;
158
124
  if (template === 'invitation') {
159
125
  emailComponent = React.createElement(InvitationEmailTemplate, {
@@ -173,47 +139,16 @@ export async function POST(request: Request) {
173
139
  });
174
140
  }
175
141
 
176
- // Rendre le composant React en HTML avec @react-email/render
177
142
  const emailHtml = await render(emailComponent);
178
143
  const emailText = htmlToText(emailHtml);
179
144
 
180
- // Logger les informations de l'email (même en production pour le debug)
181
- if (isDevelopment) {
182
- console.log("📧 [DEV MODE] Envoi de l'email:");
183
- console.log({
184
- from: smtpConfig.fromName
185
- ? `"${smtpConfig.fromName}" <${smtpConfig.fromEmail}>`
186
- : smtpConfig.fromEmail,
187
- to,
188
- subject,
189
- template,
190
- data: { ...emailData },
191
- });
192
-
193
- // Afficher le lien d'invitation dans la console si c'est une invitation
194
- if (template === 'invitation' && emailData.invitationUrl) {
195
- console.log("🔗 Lien d'invitation:", emailData.invitationUrl);
196
- }
197
-
198
- // Afficher le code de réinitialisation dans la console
199
- if (template === 'reset-password' && emailData.code) {
200
- console.log('🔑 Code de réinitialisation:', emailData.code);
201
- }
202
- }
203
-
204
- // Déchiffrer le mot de passe SMTP
205
145
  let password: string;
206
146
  try {
207
147
  password = decrypt(smtpConfig.password);
208
- } catch (error) {
209
- // Si le déchiffrement échoue, utiliser le mot de passe tel quel (ancien format non chiffré)
210
- console.warn(
211
- '⚠️ Impossible de déchiffrer le mot de passe SMTP, utilisation du mot de passe brut',
212
- );
148
+ } catch {
213
149
  password = smtpConfig.password;
214
150
  }
215
151
 
216
- // Créer le transporteur SMTP
217
152
  const transporter = nodemailer.createTransport({
218
153
  host: smtpConfig.host,
219
154
  port: smtpConfig.port,
@@ -224,7 +159,6 @@ export async function POST(request: Request) {
224
159
  },
225
160
  });
226
161
 
227
- // Envoyer l'email
228
162
  const recipients = Array.isArray(to) ? to : [to];
229
163
  const mailOptions = {
230
164
  from: smtpConfig.fromName
@@ -236,30 +170,15 @@ export async function POST(request: Request) {
236
170
  html: emailHtml,
237
171
  };
238
172
 
239
- console.log("📤 Envoi de l'email via SMTP...", {
240
- from: mailOptions.from,
241
- to: recipients,
242
- subject,
243
- });
244
-
245
173
  const info = await transporter.sendMail(mailOptions);
246
174
 
247
- console.log('✅ Email envoyé avec succès:', info.messageId);
248
-
249
175
  return Response.json({
250
176
  id: info.messageId,
251
177
  message: 'Email envoyé avec succès',
252
178
  });
253
179
  } catch (error: any) {
254
- console.error("Erreur lors de l'envoi de l'email:", error);
255
- console.error("Détails de l'erreur:", {
256
- message: error.message,
257
- code: error.code,
258
- command: error.command,
259
- response: error.response,
260
- });
180
+ console.error("Erreur lors de l'envoi de l'email:", error);
261
181
 
262
- // Gérer les erreurs spécifiques de nodemailer
263
182
  if (error.code === 'EAUTH' || error.code === 'ECONNECTION') {
264
183
  return Response.json(
265
184
  {
@@ -273,8 +192,13 @@ export async function POST(request: Request) {
273
192
 
274
193
  return Response.json(
275
194
  {
276
- error: error.message || "Erreur lors de l'envoi de l'email",
277
- details: error.code || 'UNKNOWN_ERROR',
195
+ error:
196
+ process.env.NODE_ENV === 'development'
197
+ ? error.message || "Erreur lors de l'envoi de l'email"
198
+ : "Erreur lors de l'envoi de l'email",
199
+ ...(process.env.NODE_ENV === 'development' && {
200
+ details: error.code || 'UNKNOWN_ERROR',
201
+ }),
278
202
  },
279
203
  { status: 500 },
280
204
  );
@@ -1,11 +1,15 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
6
  // PUT /api/settings/closing-reasons/[id] - Mettre à jour un motif de fermeture (admin)
6
7
  export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
7
8
  try {
8
- await requireAdmin(request.headers);
9
+ const session = await auth.api.getSession({ headers: request.headers });
10
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
11
+ const canManage = await checkPermission('settings.closing_reasons.manage');
12
+ if (!canManage) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
9
13
 
10
14
  const { id } = await params;
11
15
  const body = await request.json();
@@ -32,15 +36,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
32
36
  });
33
37
  } catch (error: any) {
34
38
  console.error('Erreur lors de la mise à jour du motif de fermeture:', error);
35
-
36
- if (error.message === 'Non authentifié') {
37
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
38
- }
39
-
40
- if (error.message === 'Permissions insuffisantes') {
41
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
42
- }
43
-
44
39
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
45
40
  }
46
41
  }
@@ -51,7 +46,10 @@ export async function DELETE(
51
46
  { params }: { params: Promise<{ id: string }> },
52
47
  ) {
53
48
  try {
54
- await requireAdmin(request.headers);
49
+ const session = await auth.api.getSession({ headers: request.headers });
50
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
51
+ const canManage = await checkPermission('settings.closing_reasons.manage');
52
+ if (!canManage) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
55
53
 
56
54
  const { id } = await params;
57
55
 
@@ -65,15 +63,6 @@ export async function DELETE(
65
63
  });
66
64
  } catch (error: any) {
67
65
  console.error('Erreur lors de la suppression du motif de fermeture:', error);
68
-
69
- if (error.message === 'Non authentifié') {
70
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
71
- }
72
-
73
- if (error.message === 'Permissions insuffisantes') {
74
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
75
- }
76
-
77
66
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
78
67
  }
79
68
  }
@@ -1,11 +1,15 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
6
  // GET /api/settings/closing-reasons - Récupérer tous les motifs de fermeture (admin)
6
7
  export async function GET(request: NextRequest) {
7
8
  try {
8
- await requireAdmin(request.headers);
9
+ const session = await auth.api.getSession({ headers: request.headers });
10
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
11
+ const canManage = await checkPermission('settings.closing_reasons.manage');
12
+ if (!canManage) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
9
13
 
10
14
  const reasons = await prisma.closingReason.findMany({
11
15
  orderBy: { name: 'asc' },
@@ -14,15 +18,6 @@ export async function GET(request: NextRequest) {
14
18
  return NextResponse.json(reasons);
15
19
  } catch (error: any) {
16
20
  console.error('Erreur lors de la récupération des motifs de fermeture:', error);
17
-
18
- if (error.message === 'Non authentifié') {
19
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
20
- }
21
-
22
- if (error.message === 'Permissions insuffisantes') {
23
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
24
- }
25
-
26
21
  return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
27
22
  }
28
23
  }
@@ -30,7 +25,10 @@ export async function GET(request: NextRequest) {
30
25
  // POST /api/settings/closing-reasons - Créer un motif de fermeture (admin)
31
26
  export async function POST(request: NextRequest) {
32
27
  try {
33
- await requireAdmin(request.headers);
28
+ const session = await auth.api.getSession({ headers: request.headers });
29
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
30
+ const canManage = await checkPermission('settings.closing_reasons.manage');
31
+ if (!canManage) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
34
32
 
35
33
  const body = await request.json();
36
34
  const { name } = body;
@@ -58,15 +56,6 @@ export async function POST(request: NextRequest) {
58
56
  );
59
57
  } catch (error: any) {
60
58
  console.error('Erreur lors de la création du motif de fermeture:', error);
61
-
62
- if (error.message === 'Non authentifié') {
63
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
64
- }
65
-
66
- if (error.message === 'Permissions insuffisantes') {
67
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
68
- }
69
-
70
59
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
71
60
  }
72
61
  }
@@ -1,20 +1,24 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
6
  // GET /api/settings/company - Récupérer les informations de l'entreprise
6
7
  export async function GET(request: NextRequest) {
7
8
  try {
8
- await requireAdmin(request.headers);
9
+ const session = await auth.api.getSession({ headers: request.headers });
10
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
11
+ const hasPermission = await checkPermission('settings.view');
12
+ if (!hasPermission) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
9
13
 
10
14
  // Récupérer ou créer l'enregistrement de l'entreprise
11
- let company = await prisma.company.findUnique({
15
+ let company = await prisma.organization.findUnique({
12
16
  where: { id: 'company' },
13
17
  });
14
18
 
15
19
  // Si l'entreprise n'existe pas, la créer
16
20
  if (!company) {
17
- company = await prisma.company.create({
21
+ company = await prisma.organization.create({
18
22
  data: {
19
23
  id: 'company',
20
24
  },
@@ -24,15 +28,6 @@ export async function GET(request: NextRequest) {
24
28
  return NextResponse.json(company);
25
29
  } catch (error: any) {
26
30
  console.error("Erreur lors de la récupération des informations de l'entreprise:", error);
27
-
28
- if (error.message === 'Non authentifié') {
29
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
30
- }
31
-
32
- if (error.message === 'Permissions insuffisantes') {
33
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
34
- }
35
-
36
31
  return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
37
32
  }
38
33
  }
@@ -40,11 +35,15 @@ export async function GET(request: NextRequest) {
40
35
  // PUT /api/settings/company - Mettre à jour les informations de l'entreprise
41
36
  export async function PUT(request: NextRequest) {
42
37
  try {
43
- await requireAdmin(request.headers);
38
+ const session = await auth.api.getSession({ headers: request.headers });
39
+ if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
40
+ const hasPermission = await checkPermission('settings.company.edit');
41
+ if (!hasPermission) return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
44
42
 
45
43
  const body = await request.json();
46
44
  const {
47
45
  name,
46
+ legalRepresentative,
48
47
  address,
49
48
  city,
50
49
  postalCode,
@@ -58,16 +57,17 @@ export async function PUT(request: NextRequest) {
58
57
  } = body;
59
58
 
60
59
  // Vérifier si l'entreprise existe
61
- let company = await prisma.company.findUnique({
60
+ let company = await prisma.organization.findUnique({
62
61
  where: { id: 'company' },
63
62
  });
64
63
 
65
64
  // Si l'entreprise n'existe pas, la créer
66
65
  if (!company) {
67
- company = await prisma.company.create({
66
+ company = await prisma.organization.create({
68
67
  data: {
69
68
  id: 'company',
70
69
  name,
70
+ legalRepresentative,
71
71
  address,
72
72
  city,
73
73
  postalCode,
@@ -82,10 +82,12 @@ export async function PUT(request: NextRequest) {
82
82
  });
83
83
  } else {
84
84
  // Mettre à jour l'entreprise
85
- company = await prisma.company.update({
85
+ company = await prisma.organization.update({
86
86
  where: { id: 'company' },
87
87
  data: {
88
88
  name: name !== undefined ? name : company.name,
89
+ legalRepresentative:
90
+ legalRepresentative !== undefined ? legalRepresentative : company.legalRepresentative,
89
91
  address: address !== undefined ? address : company.address,
90
92
  city: city !== undefined ? city : company.city,
91
93
  postalCode: postalCode !== undefined ? postalCode : company.postalCode,
@@ -107,15 +109,6 @@ export async function PUT(request: NextRequest) {
107
109
  });
108
110
  } catch (error: any) {
109
111
  console.error("Erreur lors de la mise à jour des informations de l'entreprise:", error);
110
-
111
- if (error.message === 'Non authentifié') {
112
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
113
- }
114
-
115
- if (error.message === 'Permissions insuffisantes') {
116
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
117
- }
118
-
119
112
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
120
113
  }
121
114
  }
@@ -1,11 +1,19 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
- // PUT /api/settings/google-ads/[id] - Mettre à jour une configuration (admin uniquement)
6
+ // PUT /api/settings/google-ads/[id] - Mettre à jour une configuration
6
7
  export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
7
8
  try {
8
- await requireAdmin(request.headers);
9
+ const session = await auth.api.getSession({ headers: request.headers });
10
+ if (!session) {
11
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
12
+ }
13
+ const hasPermission = await checkPermission('integrations.google_ads.manage');
14
+ if (!hasPermission) {
15
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
16
+ }
9
17
 
10
18
  const { id } = await params;
11
19
  const body = await request.json();
@@ -58,26 +66,24 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
58
66
  'Erreur lors de la mise à jour de la configuration Google Ads Lead Forms:',
59
67
  error,
60
68
  );
61
-
62
- if (error.message === 'Non authentifié') {
63
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
64
- }
65
-
66
- if (error.message === 'Permissions insuffisantes') {
67
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
68
- }
69
-
70
69
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
71
70
  }
72
71
  }
73
72
 
74
- // DELETE /api/settings/google-ads/[id] - Supprimer une configuration (admin uniquement)
73
+ // DELETE /api/settings/google-ads/[id] - Supprimer une configuration
75
74
  export async function DELETE(
76
75
  request: NextRequest,
77
76
  { params }: { params: Promise<{ id: string }> },
78
77
  ) {
79
78
  try {
80
- await requireAdmin(request.headers);
79
+ const session = await auth.api.getSession({ headers: request.headers });
80
+ if (!session) {
81
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
82
+ }
83
+ const hasPermission = await checkPermission('integrations.google_ads.manage');
84
+ if (!hasPermission) {
85
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
86
+ }
81
87
 
82
88
  const { id } = await params;
83
89
  const client = prisma as any;
@@ -103,15 +109,6 @@ export async function DELETE(
103
109
  'Erreur lors de la suppression de la configuration Google Ads Lead Forms:',
104
110
  error,
105
111
  );
106
-
107
- if (error.message === 'Non authentifié') {
108
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
109
- }
110
-
111
- if (error.message === 'Permissions insuffisantes') {
112
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
113
- }
114
-
115
112
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
116
113
  }
117
114
  }
@@ -1,11 +1,19 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
- // GET /api/settings/google-ads - Récupérer toutes les configurations Google Ads Leads (admin uniquement)
6
+ // GET /api/settings/google-ads - Récupérer toutes les configurations Google Ads Leads
6
7
  export async function GET(request: NextRequest) {
7
8
  try {
8
- await requireAdmin(request.headers);
9
+ const session = await auth.api.getSession({ headers: request.headers });
10
+ if (!session) {
11
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
12
+ }
13
+ const hasPermission = await checkPermission('integrations.google_ads.manage');
14
+ if (!hasPermission) {
15
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
16
+ }
9
17
 
10
18
  const client = prisma as any;
11
19
 
@@ -44,23 +52,21 @@ export async function GET(request: NextRequest) {
44
52
  'Erreur lors de la récupération des configurations Google Ads Lead Forms:',
45
53
  error,
46
54
  );
47
-
48
- if (error.message === 'Non authentifié') {
49
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
50
- }
51
-
52
- if (error.message === 'Permissions insuffisantes') {
53
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
54
- }
55
-
56
55
  return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
57
56
  }
58
57
  }
59
58
 
60
- // POST /api/settings/google-ads - Créer une nouvelle configuration (admin uniquement)
59
+ // POST /api/settings/google-ads - Créer une nouvelle configuration
61
60
  export async function POST(request: NextRequest) {
62
61
  try {
63
- await requireAdmin(request.headers);
62
+ const session = await auth.api.getSession({ headers: request.headers });
63
+ if (!session) {
64
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
65
+ }
66
+ const hasPermission = await checkPermission('integrations.google_ads.manage');
67
+ if (!hasPermission) {
68
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
69
+ }
64
70
 
65
71
  const body = await request.json();
66
72
  const { name, webhookKey, active = true, defaultStatusId, defaultAssignedUserId } = body;
@@ -108,15 +114,6 @@ export async function POST(request: NextRequest) {
108
114
  });
109
115
  } catch (error: any) {
110
116
  console.error('Erreur lors de la création de la configuration Google Ads Lead Forms:', error);
111
-
112
- if (error.message === 'Non authentifié') {
113
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
114
- }
115
-
116
- if (error.message === 'Permissions insuffisantes') {
117
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
118
- }
119
-
120
117
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
121
118
  }
122
119
  }
@@ -1,6 +1,7 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { prisma } from '@/lib/prisma';
3
- import { requireAdmin } from '@/lib/roles';
3
+ import { checkPermission } from '@/lib/check-permission';
4
+ import { auth } from '@/lib/auth';
4
5
 
5
6
  function extractSpreadsheetId(sheetUrlOrId: string): string {
6
7
  if (!sheetUrlOrId) return sheetUrlOrId;
@@ -17,10 +18,17 @@ function extractSpreadsheetId(sheetUrlOrId: string): string {
17
18
  return sheetUrlOrId;
18
19
  }
19
20
 
20
- // PUT /api/settings/google-sheet/[id] - Mettre à jour une configuration (admin uniquement)
21
+ // PUT /api/settings/google-sheet/[id] - Mettre à jour une configuration
21
22
  export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
22
23
  try {
23
- await requireAdmin(request.headers);
24
+ const session = await auth.api.getSession({ headers: request.headers });
25
+ if (!session) {
26
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
27
+ }
28
+ const hasPermission = await checkPermission('integrations.google_sheets.manage');
29
+ if (!hasPermission) {
30
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
31
+ }
24
32
 
25
33
  const { id } = await params;
26
34
  const body = await request.json();
@@ -174,26 +182,24 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
174
182
  });
175
183
  } catch (error: any) {
176
184
  console.error('Erreur lors de la mise à jour de la configuration Google Sheets:', error);
177
-
178
- if (error.message === 'Non authentifié') {
179
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
180
- }
181
-
182
- if (error.message === 'Permissions insuffisantes') {
183
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
184
- }
185
-
186
185
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
187
186
  }
188
187
  }
189
188
 
190
- // DELETE /api/settings/google-sheet/[id] - Supprimer une configuration (admin uniquement)
189
+ // DELETE /api/settings/google-sheet/[id] - Supprimer une configuration
191
190
  export async function DELETE(
192
191
  request: NextRequest,
193
192
  { params }: { params: Promise<{ id: string }> },
194
193
  ) {
195
194
  try {
196
- await requireAdmin(request.headers);
195
+ const session = await auth.api.getSession({ headers: request.headers });
196
+ if (!session) {
197
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
198
+ }
199
+ const hasPermission = await checkPermission('integrations.google_sheets.manage');
200
+ if (!hasPermission) {
201
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
202
+ }
197
203
 
198
204
  const { id } = await params;
199
205
  const client = prisma as any;
@@ -216,15 +222,6 @@ export async function DELETE(
216
222
  });
217
223
  } catch (error: any) {
218
224
  console.error('Erreur lors de la suppression de la configuration Google Sheets:', error);
219
-
220
- if (error.message === 'Non authentifié') {
221
- return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
222
- }
223
-
224
- if (error.message === 'Permissions insuffisantes') {
225
- return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
226
- }
227
-
228
225
  return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
229
226
  }
230
227
  }