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.
- package/bin/create-crm-tmp.js +93 -0
- package/package.json +25 -0
- package/template/.prettierignore +33 -0
- package/template/.prettierrc.json +25 -0
- package/template/README.md +173 -0
- package/template/eslint.config.mjs +18 -0
- package/template/exemple-contacts.csv +11 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +64 -0
- package/template/postcss.config.mjs +7 -0
- package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
- package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +582 -0
- package/template/prisma.config.ts +14 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
- package/template/src/app/(auth)/layout.tsx +3 -0
- package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
- package/template/src/app/(auth)/reset-password/page.tsx +146 -0
- package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
- package/template/src/app/(auth)/signin/page.tsx +166 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
- package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
- package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
- package/template/src/app/(dashboard)/layout.tsx +30 -0
- package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
- package/template/src/app/(dashboard)/templates/page.tsx +567 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
- package/template/src/app/(dashboard)/users/page.tsx +457 -0
- package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
- package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
- package/template/src/app/api/audit-logs/route.ts +57 -0
- package/template/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/src/app/api/auth/check-active/route.ts +31 -0
- package/template/src/app/api/auth/google/callback/route.ts +94 -0
- package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
- package/template/src/app/api/auth/google/route.ts +34 -0
- package/template/src/app/api/auth/google/status/route.ts +32 -0
- package/template/src/app/api/closing-reasons/route.ts +27 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
- package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
- package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
- package/template/src/app/api/contacts/[id]/route.ts +322 -0
- package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
- package/template/src/app/api/contacts/export/route.ts +270 -0
- package/template/src/app/api/contacts/import/route.ts +381 -0
- package/template/src/app/api/contacts/route.ts +283 -0
- package/template/src/app/api/dashboard/stats/route.ts +299 -0
- package/template/src/app/api/email/track/[id]/route.ts +68 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
- package/template/src/app/api/invite/complete/route.ts +88 -0
- package/template/src/app/api/invite/validate/route.ts +55 -0
- package/template/src/app/api/reminders/route.ts +95 -0
- package/template/src/app/api/reset-password/complete/route.ts +73 -0
- package/template/src/app/api/reset-password/request/route.ts +84 -0
- package/template/src/app/api/reset-password/validate/route.ts +49 -0
- package/template/src/app/api/reset-password/verify/route.ts +74 -0
- package/template/src/app/api/roles/[id]/route.ts +183 -0
- package/template/src/app/api/roles/route.ts +140 -0
- package/template/src/app/api/send/route.ts +282 -0
- package/template/src/app/api/settings/change-password/route.ts +95 -0
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
- package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
- package/template/src/app/api/settings/company/route.ts +121 -0
- package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
- package/template/src/app/api/settings/google-ads/route.ts +122 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
- package/template/src/app/api/settings/google-sheet/route.ts +254 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
- package/template/src/app/api/settings/meta-leads/route.ts +132 -0
- package/template/src/app/api/settings/profile/route.ts +42 -0
- package/template/src/app/api/settings/smtp/route.ts +130 -0
- package/template/src/app/api/settings/smtp/test/route.ts +121 -0
- package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
- package/template/src/app/api/settings/statuses/route.ts +83 -0
- package/template/src/app/api/statuses/route.ts +25 -0
- package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
- package/template/src/app/api/tasks/[id]/route.ts +728 -0
- package/template/src/app/api/tasks/meet/route.ts +240 -0
- package/template/src/app/api/tasks/route.ts +417 -0
- package/template/src/app/api/templates/[id]/route.ts +140 -0
- package/template/src/app/api/templates/route.ts +91 -0
- package/template/src/app/api/users/[id]/route.ts +168 -0
- package/template/src/app/api/users/list/route.ts +45 -0
- package/template/src/app/api/users/me/route.ts +48 -0
- package/template/src/app/api/users/route.ts +250 -0
- package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
- package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
- package/template/src/app/api/workflows/[id]/route.ts +192 -0
- package/template/src/app/api/workflows/process/route.ts +293 -0
- package/template/src/app/api/workflows/route.ts +124 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +1416 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/components/dashboard/activity-chart.tsx +67 -0
- package/template/src/components/dashboard/contacts-chart.tsx +63 -0
- package/template/src/components/dashboard/recent-activity.tsx +164 -0
- package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
- package/template/src/components/dashboard/stat-card.tsx +61 -0
- package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
- package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
- package/template/src/components/editor.tsx +856 -0
- package/template/src/components/email-template.tsx +35 -0
- package/template/src/components/header.tsx +320 -0
- package/template/src/components/invitation-email-template.tsx +79 -0
- package/template/src/components/meet-cancellation-email-template.tsx +120 -0
- package/template/src/components/meet-confirmation-email-template.tsx +156 -0
- package/template/src/components/meet-update-email-template.tsx +209 -0
- package/template/src/components/page-header.tsx +61 -0
- package/template/src/components/reset-password-email-template.tsx +79 -0
- package/template/src/components/sidebar.tsx +294 -0
- package/template/src/components/skeleton.tsx +380 -0
- package/template/src/components/ui/commands.tsx +396 -0
- package/template/src/components/ui/components.tsx +150 -0
- package/template/src/components/ui/theme.tsx +5 -0
- package/template/src/components/view-as-banner.tsx +45 -0
- package/template/src/components/view-as-modal.tsx +186 -0
- package/template/src/contexts/mobile-menu-context.tsx +31 -0
- package/template/src/contexts/sidebar-context.tsx +107 -0
- package/template/src/contexts/task-reminder-context.tsx +239 -0
- package/template/src/contexts/view-as-context.tsx +84 -0
- package/template/src/hooks/use-user-role.ts +82 -0
- package/template/src/lib/audit-log.ts +45 -0
- package/template/src/lib/auth-client.ts +16 -0
- package/template/src/lib/auth.ts +35 -0
- package/template/src/lib/check-permission.ts +193 -0
- package/template/src/lib/contact-duplicate.ts +112 -0
- package/template/src/lib/contact-interactions.ts +371 -0
- package/template/src/lib/encryption.ts +99 -0
- package/template/src/lib/google-calendar.ts +300 -0
- package/template/src/lib/google-drive.ts +372 -0
- package/template/src/lib/permissions.ts +412 -0
- package/template/src/lib/prisma.ts +32 -0
- package/template/src/lib/roles.ts +120 -0
- package/template/src/lib/template-variables.ts +76 -0
- package/template/src/lib/utils.ts +46 -0
- package/template/src/lib/workflow-executor.ts +482 -0
- package/template/src/proxy.ts +91 -0
- package/template/tsconfig.json +34 -0
- 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
|
+
}
|