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,282 @@
1
+ import { EmailTemplate } from '@/components/email-template';
2
+ import { InvitationEmailTemplate } from '@/components/invitation-email-template';
3
+ import { ResetPasswordEmailTemplate } from '@/components/reset-password-email-template';
4
+ import { prisma } from '@/lib/prisma';
5
+ import { decrypt } from '@/lib/encryption';
6
+ import { auth } from '@/lib/auth';
7
+ import nodemailer from 'nodemailer';
8
+ import { render } from '@react-email/render';
9
+ import React from 'react';
10
+
11
+ const isDevelopment = process.env.NODE_ENV === 'development';
12
+
13
+ function htmlToText(html: string): string {
14
+ if (!html) return '';
15
+ return html
16
+ .replace(/<br\s*\/?>/gi, '\n')
17
+ .replace(/<\/p>/gi, '\n\n')
18
+ .replace(/<[^>]+>/g, '')
19
+ .replace(/\n{3,}/g, '\n\n')
20
+ .trim();
21
+ }
22
+
23
+ async function getAdminSmtpConfig(userId: string) {
24
+ try {
25
+ // Récupérer la configuration SMTP de l'administrateur connecté
26
+ const user = await prisma.user.findUnique({
27
+ where: { id: userId },
28
+ include: {
29
+ smtpConfig: true,
30
+ },
31
+ });
32
+
33
+ if (!user) {
34
+ console.error('❌ Utilisateur non trouvé:', userId);
35
+ return { config: null, error: 'Utilisateur non trouvé' };
36
+ }
37
+
38
+ if (!user.smtpConfig) {
39
+ console.error("❌ Aucune configuration SMTP trouvée pour l'utilisateur:", user.email);
40
+ return {
41
+ config: null,
42
+ error:
43
+ 'Vous devez configurer votre SMTP dans les paramètres avant de pouvoir envoyer des emails.',
44
+ };
45
+ }
46
+
47
+ console.log('✅ Configuration SMTP trouvée pour:', user.email);
48
+ return { config: user.smtpConfig, error: null };
49
+ } catch (error) {
50
+ console.error('Erreur lors de la récupération de la configuration SMTP:', error);
51
+ return {
52
+ config: null,
53
+ error: 'Erreur lors de la récupération de la configuration SMTP.',
54
+ };
55
+ }
56
+ }
57
+
58
+ async function getAnyAdminSmtpConfig() {
59
+ 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
+ const smtpConfig = await prisma.smtpConfig.findFirst({
63
+ where: {
64
+ user: {
65
+ role: 'ADMIN',
66
+ },
67
+ },
68
+ include: {
69
+ user: {
70
+ select: {
71
+ email: true,
72
+ name: true,
73
+ },
74
+ },
75
+ },
76
+ orderBy: {
77
+ createdAt: 'asc',
78
+ },
79
+ });
80
+
81
+ 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
+ return {
92
+ config: null,
93
+ error: 'Aucune configuration SMTP trouvée. Veuillez configurer SMTP dans les paramètres.',
94
+ };
95
+ }
96
+
97
+ console.log("✅ Configuration SMTP trouvée pour l'admin:", smtpConfig.user.email);
98
+ return { config: smtpConfig, error: null };
99
+ } catch (error) {
100
+ console.error('Erreur lors de la récupération de la configuration SMTP admin:', error);
101
+ return {
102
+ config: null,
103
+ error: 'Erreur lors de la récupération de la configuration SMTP.',
104
+ };
105
+ }
106
+ }
107
+
108
+ export async function POST(request: Request) {
109
+ try {
110
+ const body = await request.json();
111
+ const { to, subject, template, ...emailData } = body;
112
+
113
+ // Récupérer la session de l'utilisateur connecté (optionnel pour reset-password)
114
+ const session = await auth.api.getSession({
115
+ headers: request.headers,
116
+ });
117
+
118
+ // Pour le reset password, on n'a pas besoin de session
119
+ const isResetPassword = template === 'reset-password';
120
+
121
+ if (!isResetPassword && (!session || !session.user?.id)) {
122
+ console.error('❌ Utilisateur non authentifié');
123
+ return Response.json({ error: 'Non authentifié' }, { status: 401 });
124
+ }
125
+
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
+ let smtpConfig, smtpError;
136
+ if (isResetPassword) {
137
+ // Pour le reset password, utiliser n'importe quelle config SMTP d'admin
138
+ const result = await getAnyAdminSmtpConfig();
139
+ smtpConfig = result.config;
140
+ smtpError = result.error;
141
+ } else {
142
+ // Pour les autres emails, utiliser la config de l'utilisateur connecté
143
+ const result = await getAdminSmtpConfig(session!.user.id);
144
+ smtpConfig = result.config;
145
+ smtpError = result.error;
146
+ }
147
+
148
+ if (!smtpConfig) {
149
+ const errorMsg =
150
+ smtpError ||
151
+ 'Aucune configuration SMTP trouvée. Veuillez configurer SMTP dans les paramètres.';
152
+ console.error('❌', errorMsg);
153
+ return Response.json({ error: errorMsg }, { status: 400 });
154
+ }
155
+
156
+ // Sélectionner le template approprié et le rendre en HTML
157
+ let emailComponent: React.ReactElement;
158
+ if (template === 'invitation') {
159
+ emailComponent = React.createElement(InvitationEmailTemplate, {
160
+ name: emailData.name || 'Utilisateur',
161
+ invitationUrl: emailData.invitationUrl || '',
162
+ signature: smtpConfig.signature,
163
+ });
164
+ } else if (template === 'reset-password') {
165
+ emailComponent = React.createElement(ResetPasswordEmailTemplate, {
166
+ code: emailData.code || '',
167
+ signature: smtpConfig.signature,
168
+ });
169
+ } else {
170
+ emailComponent = React.createElement(EmailTemplate, {
171
+ firstName: emailData.firstName || 'Utilisateur',
172
+ signature: smtpConfig.signature,
173
+ });
174
+ }
175
+
176
+ // Rendre le composant React en HTML avec @react-email/render
177
+ const emailHtml = await render(emailComponent);
178
+ const emailText = htmlToText(emailHtml);
179
+
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
+ let password: string;
206
+ try {
207
+ 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
+ );
213
+ password = smtpConfig.password;
214
+ }
215
+
216
+ // Créer le transporteur SMTP
217
+ const transporter = nodemailer.createTransport({
218
+ host: smtpConfig.host,
219
+ port: smtpConfig.port,
220
+ secure: smtpConfig.secure,
221
+ auth: {
222
+ user: smtpConfig.username,
223
+ pass: password,
224
+ },
225
+ });
226
+
227
+ // Envoyer l'email
228
+ const recipients = Array.isArray(to) ? to : [to];
229
+ const mailOptions = {
230
+ from: smtpConfig.fromName
231
+ ? `"${smtpConfig.fromName}" <${smtpConfig.fromEmail}>`
232
+ : smtpConfig.fromEmail,
233
+ to: recipients,
234
+ subject,
235
+ text: emailText,
236
+ html: emailHtml,
237
+ };
238
+
239
+ console.log("📤 Envoi de l'email via SMTP...", {
240
+ from: mailOptions.from,
241
+ to: recipients,
242
+ subject,
243
+ });
244
+
245
+ const info = await transporter.sendMail(mailOptions);
246
+
247
+ console.log('✅ Email envoyé avec succès:', info.messageId);
248
+
249
+ return Response.json({
250
+ id: info.messageId,
251
+ message: 'Email envoyé avec succès',
252
+ });
253
+ } 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
+ });
261
+
262
+ // Gérer les erreurs spécifiques de nodemailer
263
+ if (error.code === 'EAUTH' || error.code === 'ECONNECTION') {
264
+ return Response.json(
265
+ {
266
+ error:
267
+ "Erreur d'authentification SMTP. Vérifiez votre configuration SMTP dans les paramètres.",
268
+ details: error.message,
269
+ },
270
+ { status: 400 },
271
+ );
272
+ }
273
+
274
+ return Response.json(
275
+ {
276
+ error: error.message || "Erreur lors de l'envoi de l'email",
277
+ details: error.code || 'UNKNOWN_ERROR',
278
+ },
279
+ { status: 500 },
280
+ );
281
+ }
282
+ }
@@ -0,0 +1,95 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { compare } from 'bcryptjs';
4
+ import { auth, hashPassword } from '@/lib/auth';
5
+
6
+ export async function POST(request: NextRequest) {
7
+ try {
8
+ // Vérifier l'authentification
9
+ const session = await auth.api.getSession({
10
+ headers: request.headers,
11
+ });
12
+
13
+ if (!session) {
14
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
15
+ }
16
+
17
+ const body = await request.json();
18
+ const { currentPassword, newPassword, confirmPassword } = body;
19
+
20
+ // Validation
21
+ if (!currentPassword || !newPassword || !confirmPassword) {
22
+ return NextResponse.json({ error: 'Tous les champs sont requis' }, { status: 400 });
23
+ }
24
+
25
+ if (newPassword.length < 6) {
26
+ return NextResponse.json(
27
+ { error: 'Le nouveau mot de passe doit contenir au moins 6 caractères' },
28
+ { status: 400 },
29
+ );
30
+ }
31
+
32
+ if (newPassword !== confirmPassword) {
33
+ return NextResponse.json(
34
+ { error: 'Les nouveaux mots de passe ne correspondent pas' },
35
+ { status: 400 },
36
+ );
37
+ }
38
+
39
+ if (currentPassword === newPassword) {
40
+ return NextResponse.json(
41
+ { error: "Le nouveau mot de passe doit être différent de l'actuel" },
42
+ { status: 400 },
43
+ );
44
+ }
45
+
46
+ // Récupérer l'utilisateur avec son compte
47
+ const user = await prisma.user.findUnique({
48
+ where: { id: session.user.id },
49
+ include: {
50
+ accounts: {
51
+ where: { providerId: 'credential' },
52
+ },
53
+ },
54
+ });
55
+
56
+ if (!user || user.accounts.length === 0) {
57
+ return NextResponse.json({ error: 'Compte non trouvé' }, { status: 404 });
58
+ }
59
+
60
+ const account = user.accounts[0];
61
+
62
+ // Vérifier le mot de passe actuel
63
+ if (!account.password) {
64
+ return NextResponse.json(
65
+ { error: 'Aucun mot de passe défini pour ce compte' },
66
+ { status: 400 },
67
+ );
68
+ }
69
+
70
+ const isCurrentPasswordValid = await compare(currentPassword, account.password);
71
+
72
+ if (!isCurrentPasswordValid) {
73
+ return NextResponse.json({ error: 'Mot de passe actuel incorrect' }, { status: 400 });
74
+ }
75
+
76
+ // Hasher le nouveau mot de passe
77
+ const hashedPassword = await hashPassword(newPassword);
78
+
79
+ // Mettre à jour le mot de passe
80
+ await prisma.account.update({
81
+ where: { id: account.id },
82
+ data: {
83
+ password: hashedPassword,
84
+ },
85
+ });
86
+
87
+ return NextResponse.json({
88
+ success: true,
89
+ message: 'Mot de passe modifié avec succès',
90
+ });
91
+ } catch (error: any) {
92
+ console.error('Erreur lors du changement de mot de passe:', error);
93
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
94
+ }
95
+ }
@@ -0,0 +1,84 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+
5
+ // PUT /api/settings/closing-reasons/[id] - Mettre à jour un motif de fermeture (admin)
6
+ export async function PUT(
7
+ request: NextRequest,
8
+ { params }: { params: Promise<{ id: string }> },
9
+ ) {
10
+ try {
11
+ await requireAdmin(request.headers);
12
+
13
+ const { id } = await params;
14
+ const body = await request.json();
15
+ const { name } = body;
16
+
17
+ if (!name || !name.trim()) {
18
+ return NextResponse.json(
19
+ { error: 'Le nom du motif de fermeture est obligatoire.' },
20
+ { status: 400 },
21
+ );
22
+ }
23
+
24
+ const reason = await prisma.closingReason.update({
25
+ where: { id },
26
+ data: {
27
+ name: name.trim(),
28
+ },
29
+ });
30
+
31
+ return NextResponse.json({
32
+ success: true,
33
+ reason,
34
+ message: 'Motif de fermeture mis à jour avec succès.',
35
+ });
36
+ } catch (error: any) {
37
+ console.error('Erreur lors de la mise à jour du motif de fermeture:', error);
38
+
39
+ if (error.message === 'Non authentifié') {
40
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
41
+ }
42
+
43
+ if (error.message === 'Permissions insuffisantes') {
44
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
45
+ }
46
+
47
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
48
+ }
49
+ }
50
+
51
+ // DELETE /api/settings/closing-reasons/[id] - Supprimer un motif de fermeture (admin)
52
+ export async function DELETE(
53
+ request: NextRequest,
54
+ { params }: { params: Promise<{ id: string }> },
55
+ ) {
56
+ try {
57
+ await requireAdmin(request.headers);
58
+
59
+ const { id } = await params;
60
+
61
+ await prisma.closingReason.delete({
62
+ where: { id },
63
+ });
64
+
65
+ return NextResponse.json({
66
+ success: true,
67
+ message: 'Motif de fermeture supprimé avec succès.',
68
+ });
69
+ } catch (error: any) {
70
+ console.error('Erreur lors de la suppression du motif de fermeture:', error);
71
+
72
+ if (error.message === 'Non authentifié') {
73
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
74
+ }
75
+
76
+ if (error.message === 'Permissions insuffisantes') {
77
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
78
+ }
79
+
80
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
81
+ }
82
+ }
83
+
84
+
@@ -0,0 +1,74 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+
5
+ // GET /api/settings/closing-reasons - Récupérer tous les motifs de fermeture (admin)
6
+ export async function GET(request: NextRequest) {
7
+ try {
8
+ await requireAdmin(request.headers);
9
+
10
+ const reasons = await prisma.closingReason.findMany({
11
+ orderBy: { name: 'asc' },
12
+ });
13
+
14
+ return NextResponse.json(reasons);
15
+ } catch (error: any) {
16
+ 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
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
27
+ }
28
+ }
29
+
30
+ // POST /api/settings/closing-reasons - Créer un motif de fermeture (admin)
31
+ export async function POST(request: NextRequest) {
32
+ try {
33
+ await requireAdmin(request.headers);
34
+
35
+ const body = await request.json();
36
+ const { name } = body;
37
+
38
+ if (!name || !name.trim()) {
39
+ return NextResponse.json(
40
+ { error: 'Le nom du motif de fermeture est obligatoire.' },
41
+ { status: 400 },
42
+ );
43
+ }
44
+
45
+ const reason = await prisma.closingReason.create({
46
+ data: {
47
+ name: name.trim(),
48
+ },
49
+ });
50
+
51
+ return NextResponse.json(
52
+ {
53
+ success: true,
54
+ reason,
55
+ message: 'Motif de fermeture créé avec succès.',
56
+ },
57
+ { status: 201 },
58
+ );
59
+ } catch (error: any) {
60
+ 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
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
71
+ }
72
+ }
73
+
74
+
@@ -0,0 +1,121 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+
5
+ // GET /api/settings/company - Récupérer les informations de l'entreprise
6
+ export async function GET(request: NextRequest) {
7
+ try {
8
+ await requireAdmin(request.headers);
9
+
10
+ // Récupérer ou créer l'enregistrement de l'entreprise
11
+ let company = await prisma.company.findUnique({
12
+ where: { id: 'company' },
13
+ });
14
+
15
+ // Si l'entreprise n'existe pas, la créer
16
+ if (!company) {
17
+ company = await prisma.company.create({
18
+ data: {
19
+ id: 'company',
20
+ },
21
+ });
22
+ }
23
+
24
+ return NextResponse.json(company);
25
+ } catch (error: any) {
26
+ 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
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
37
+ }
38
+ }
39
+
40
+ // PUT /api/settings/company - Mettre à jour les informations de l'entreprise
41
+ export async function PUT(request: NextRequest) {
42
+ try {
43
+ await requireAdmin(request.headers);
44
+
45
+ const body = await request.json();
46
+ const {
47
+ name,
48
+ address,
49
+ city,
50
+ postalCode,
51
+ country,
52
+ phone,
53
+ email,
54
+ website,
55
+ siret,
56
+ vatNumber,
57
+ logo,
58
+ } = body;
59
+
60
+ // Vérifier si l'entreprise existe
61
+ let company = await prisma.company.findUnique({
62
+ where: { id: 'company' },
63
+ });
64
+
65
+ // Si l'entreprise n'existe pas, la créer
66
+ if (!company) {
67
+ company = await prisma.company.create({
68
+ data: {
69
+ id: 'company',
70
+ name,
71
+ address,
72
+ city,
73
+ postalCode,
74
+ country,
75
+ phone,
76
+ email,
77
+ website,
78
+ siret,
79
+ vatNumber,
80
+ logo,
81
+ },
82
+ });
83
+ } else {
84
+ // Mettre à jour l'entreprise
85
+ company = await prisma.company.update({
86
+ where: { id: 'company' },
87
+ data: {
88
+ name: name !== undefined ? name : company.name,
89
+ address: address !== undefined ? address : company.address,
90
+ city: city !== undefined ? city : company.city,
91
+ postalCode: postalCode !== undefined ? postalCode : company.postalCode,
92
+ country: country !== undefined ? country : company.country,
93
+ phone: phone !== undefined ? phone : company.phone,
94
+ email: email !== undefined ? email : company.email,
95
+ website: website !== undefined ? website : company.website,
96
+ siret: siret !== undefined ? siret : company.siret,
97
+ vatNumber: vatNumber !== undefined ? vatNumber : company.vatNumber,
98
+ logo: logo !== undefined ? logo : company.logo,
99
+ },
100
+ });
101
+ }
102
+
103
+ return NextResponse.json({
104
+ success: true,
105
+ company,
106
+ message: "Informations de l'entreprise mises à jour avec succès",
107
+ });
108
+ } catch (error: any) {
109
+ 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
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
120
+ }
121
+ }