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.
- package/package.json +1 -1
- package/template/.prettierignore +2 -0
- package/template/README.md +53 -67
- package/template/components.json +22 -0
- package/template/exemple-contacts.csv +54 -0
- package/template/next.config.ts +27 -1
- package/template/package.json +64 -27
- package/template/prisma/schema.prisma +821 -72
- package/template/skills-lock.json +25 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +21 -24
- package/template/src/app/(auth)/reset-password/complete/page.tsx +12 -21
- package/template/src/app/(auth)/reset-password/page.tsx +12 -8
- package/template/src/app/(auth)/reset-password/verify/page.tsx +12 -8
- package/template/src/app/(auth)/signin/page.tsx +20 -17
- package/template/src/app/(dashboard)/agenda/page.tsx +2231 -2188
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +680 -323
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
- package/template/src/app/(dashboard)/automatisation/page.tsx +473 -180
- package/template/src/app/(dashboard)/closing/page.tsx +500 -468
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +5035 -4126
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1703 -0
- package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3776 -2064
- package/template/src/app/(dashboard)/dashboard/page.tsx +37 -519
- package/template/src/app/(dashboard)/error.tsx +37 -0
- package/template/src/app/(dashboard)/layout.tsx +1 -1
- package/template/src/app/(dashboard)/loading.tsx +5 -0
- package/template/src/app/(dashboard)/settings/loading.tsx +19 -0
- package/template/src/app/(dashboard)/settings/page.tsx +2685 -2489
- package/template/src/app/(dashboard)/templates/page.tsx +500 -300
- package/template/src/app/(dashboard)/users/list/page.tsx +356 -350
- package/template/src/app/(dashboard)/users/page.tsx +279 -310
- package/template/src/app/(dashboard)/users/permissions/page.tsx +104 -99
- package/template/src/app/(dashboard)/users/roles/page.tsx +164 -137
- package/template/src/app/api/audit-logs/route.ts +1 -1
- package/template/src/app/api/auth/google/callback/route.ts +8 -5
- package/template/src/app/api/auth/google/disconnect/route.ts +2 -2
- package/template/src/app/api/companies/[id]/activities/route.ts +131 -0
- package/template/src/app/api/companies/[id]/route.ts +195 -0
- package/template/src/app/api/companies/export/route.ts +206 -0
- package/template/src/app/api/companies/route.ts +166 -0
- package/template/src/app/api/contact-views/[id]/pin/route.ts +69 -0
- package/template/src/app/api/contact-views/[id]/route.ts +197 -0
- package/template/src/app/api/contact-views/route.ts +146 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +77 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +7 -17
- package/template/src/app/api/contacts/[id]/files/route.ts +83 -44
- package/template/src/app/api/contacts/[id]/interactions/route.ts +37 -0
- package/template/src/app/api/contacts/[id]/kyc/route.ts +71 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +38 -29
- package/template/src/app/api/contacts/[id]/route.ts +111 -20
- package/template/src/app/api/contacts/[id]/send-email/route.ts +6 -0
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +61 -0
- package/template/src/app/api/contacts/export/route.ts +12 -17
- package/template/src/app/api/contacts/import/route.ts +22 -19
- package/template/src/app/api/contacts/import-preview/route.ts +139 -0
- package/template/src/app/api/contacts/route.ts +202 -49
- package/template/src/app/api/dashboard/stats/route.ts +9 -292
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +203 -185
- package/template/src/app/api/invite/complete/route.ts +20 -23
- package/template/src/app/api/reminders/route.ts +1 -0
- package/template/src/app/api/reset-password/complete/route.ts +11 -13
- package/template/src/app/api/send/route.ts +9 -85
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +10 -21
- package/template/src/app/api/settings/closing-reasons/route.ts +10 -21
- package/template/src/app/api/settings/company/route.ts +19 -26
- package/template/src/app/api/settings/google-ads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-ads/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +20 -23
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +23 -32
- package/template/src/app/api/settings/google-sheet/preview/route.ts +104 -0
- package/template/src/app/api/settings/google-sheet/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +20 -23
- package/template/src/app/api/settings/meta-leads/route.ts +20 -23
- package/template/src/app/api/settings/statuses/[id]/route.ts +33 -23
- package/template/src/app/api/settings/statuses/route.ts +24 -22
- package/template/src/app/api/statuses/route.ts +2 -5
- package/template/src/app/api/tasks/[id]/attendees/route.ts +14 -7
- package/template/src/app/api/tasks/[id]/route.ts +161 -137
- package/template/src/app/api/tasks/meet/route.ts +11 -8
- package/template/src/app/api/tasks/route.ts +155 -95
- package/template/src/app/api/templates/[id]/route.ts +22 -13
- package/template/src/app/api/templates/route.ts +22 -5
- package/template/src/app/api/users/[id]/resend-invite/route.ts +95 -0
- package/template/src/app/api/users/[id]/route.ts +16 -1
- package/template/src/app/api/users/commercials/route.ts +38 -0
- package/template/src/app/api/users/for-agenda/route.ts +1 -2
- package/template/src/app/api/users/route.ts +94 -55
- package/template/src/app/api/webhooks/google-ads/route.ts +20 -1
- package/template/src/app/api/webhooks/meta-leads/route.ts +18 -1
- package/template/src/app/api/workflows/[id]/route.ts +33 -6
- package/template/src/app/api/workflows/process/route.ts +509 -146
- package/template/src/app/api/workflows/route.ts +46 -4
- package/template/src/app/globals.css +210 -101
- package/template/src/app/layout.tsx +19 -8
- package/template/src/app/page.tsx +37 -7
- package/template/src/components/address-autocomplete.tsx +232 -0
- package/template/src/components/contacts/filter-bar.tsx +181 -0
- package/template/src/components/contacts/filter-builder.tsx +589 -0
- package/template/src/components/contacts/save-view-dialog.tsx +160 -0
- package/template/src/components/contacts/views-tab-bar.tsx +440 -0
- package/template/src/components/dashboard/activity-chart.tsx +31 -39
- package/template/src/components/dashboard/dashboard-content.tsx +79 -0
- package/template/src/components/dashboard/stat-card.tsx +40 -42
- package/template/src/components/dashboard/tasks-pie-chart.tsx +34 -37
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +78 -72
- package/template/src/components/date-picker.tsx +396 -0
- package/template/src/components/editor.tsx +27 -13
- package/template/src/components/email-template.tsx +4 -2
- package/template/src/components/global-search.tsx +358 -0
- package/template/src/components/header.tsx +57 -62
- package/template/src/components/invitation-email-template.tsx +4 -2
- package/template/src/components/lazy-editor.tsx +11 -0
- package/template/src/components/meet-cancellation-email-template.tsx +11 -3
- package/template/src/components/meet-confirmation-email-template.tsx +10 -3
- package/template/src/components/meet-update-email-template.tsx +10 -3
- package/template/src/components/page-header.tsx +19 -15
- package/template/src/components/protected-page.tsx +94 -0
- package/template/src/components/reset-password-email-template.tsx +4 -2
- package/template/src/components/sidebar.tsx +92 -94
- package/template/src/components/skeleton.tsx +128 -42
- package/template/src/components/ui/accordion.tsx +64 -0
- package/template/src/components/ui/alert-dialog.tsx +139 -0
- package/template/src/components/ui/button.tsx +60 -0
- package/template/src/components/view-as-banner.tsx +1 -1
- package/template/src/components/view-as-modal.tsx +21 -16
- package/template/src/config/nav-pages.ts +108 -0
- package/template/src/contexts/app-toast-context.tsx +174 -0
- package/template/src/contexts/sidebar-context.tsx +16 -47
- package/template/src/contexts/task-reminder-context.tsx +6 -6
- package/template/src/contexts/view-as-context.tsx +11 -16
- package/template/src/hooks/use-alert.tsx +65 -0
- package/template/src/hooks/use-confirm.tsx +87 -0
- package/template/src/hooks/use-contact-views.ts +140 -0
- package/template/src/hooks/use-contacts.ts +69 -0
- package/template/src/hooks/use-fetch.ts +17 -0
- package/template/src/hooks/use-focus-trap.ts +73 -0
- package/template/src/hooks/use-statuses.ts +22 -0
- package/template/src/lib/address-api.ts +155 -0
- package/template/src/lib/cache.ts +73 -0
- package/template/src/lib/check-permission.ts +12 -177
- package/template/src/lib/contact-interactions.ts +3 -1
- package/template/src/lib/contact-view-filters.ts +341 -0
- package/template/src/lib/dashboard-stats.ts +224 -0
- package/template/src/lib/date-utils.ts +49 -0
- package/template/src/lib/get-auth-user.ts +25 -0
- package/template/src/lib/google-calendar.ts +54 -12
- package/template/src/lib/google-drive.ts +796 -75
- package/template/src/lib/google-fetch.ts +63 -0
- package/template/src/lib/local-storage.ts +34 -0
- package/template/src/lib/permissions.ts +245 -47
- package/template/src/lib/prisma.ts +11 -11
- package/template/src/lib/roles.ts +14 -39
- package/template/src/lib/template-variables.ts +67 -33
- package/template/src/lib/utils.ts +26 -2
- package/template/src/lib/workflow-executor.ts +445 -229
- package/template/src/proxy.ts +34 -73
- package/template/src/types/contact-views.ts +351 -0
- package/template/src/types/yousign.ts +52 -0
- package/template/vercel.json +12 -0
- package/template/WORKFLOWS_CRON.md +0 -185
- package/template/prisma/migrations/20251126144728_init/migration.sql +0 -78
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +0 -5
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +0 -19
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +0 -22
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +0 -23
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +0 -75
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +0 -2
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +0 -45
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +0 -2
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +0 -27
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +0 -20
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +0 -18
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +0 -32
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +0 -20
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +0 -12
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +0 -21
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +0 -11
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +0 -12
- package/template/prisma/migrations/20251208094843_mg/migration.sql +0 -14
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +0 -14
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +0 -26
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +0 -2
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +0 -2
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +0 -3
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +0 -21
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +0 -2
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +0 -10
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +0 -26
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +0 -24
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +0 -11
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +0 -12
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +0 -25
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +0 -8
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +0 -2
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +0 -80
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +0 -32
- package/template/prisma/migrations/20251220000000_add_task_interaction_type/migration.sql +0 -4
- package/template/prisma/migrations/20251221000000_add_task_type/migration.sql +0 -3
- package/template/prisma/migrations/20251221000001_add_event_color/migration.sql +0 -23
- package/template/prisma/migrations/20260210114913_add_dashboard_widget/migration.sql +0 -20
- package/template/prisma/migrations/migration_lock.toml +0 -3
- package/template/src/app/(dashboard)/users/layout.tsx +0 -30
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -47
- package/template/src/app/api/dashboard/widgets/route.ts +0 -181
- package/template/src/components/dashboard/add-widget-dialog.tsx +0 -161
- package/template/src/components/dashboard/color-picker.tsx +0 -65
- package/template/src/components/dashboard/contacts-chart.tsx +0 -69
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +0 -121
- package/template/src/components/dashboard/recent-activity.tsx +0 -157
- package/template/src/components/dashboard/sales-analytics-chart.tsx +0 -77
- package/template/src/components/dashboard/status-distribution-chart.tsx +0 -82
- package/template/src/components/dashboard/top-contacts-list.tsx +0 -119
- package/template/src/components/dashboard/widget-wrapper.tsx +0 -39
- package/template/src/contexts/dashboard-theme-context.tsx +0 -58
- package/template/src/lib/dashboard-themes.ts +0 -140
- package/template/src/lib/default-widgets.ts +0 -14
- package/template/src/lib/widget-registry.ts +0 -177
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
// GET /api/users/commercials - Récupérer la liste des commerciaux (accessible à tous les utilisateurs authentifiés)
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const session = await auth.api.getSession({
|
|
9
|
+
headers: request.headers,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Récupérer tous les utilisateurs avec le rôle COMMERCIAL, ADMIN ou MANAGER
|
|
17
|
+
const users = await prisma.user.findMany({
|
|
18
|
+
where: {
|
|
19
|
+
active: true,
|
|
20
|
+
role: {
|
|
21
|
+
in: ['COMMERCIAL', 'ADMIN', 'MANAGER'],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
select: {
|
|
25
|
+
id: true,
|
|
26
|
+
name: true,
|
|
27
|
+
email: true,
|
|
28
|
+
role: true,
|
|
29
|
+
},
|
|
30
|
+
orderBy: { name: 'asc' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return NextResponse.json(users);
|
|
34
|
+
} catch (error: any) {
|
|
35
|
+
console.error('Erreur lors de la récupération des commerciaux:', error);
|
|
36
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -3,7 +3,7 @@ import { auth } from '@/lib/auth';
|
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
4
|
import { checkPermission } from '@/lib/check-permission';
|
|
5
5
|
|
|
6
|
-
// GET /api/users/for-agenda - Liste les utilisateurs
|
|
6
|
+
// GET /api/users/for-agenda - Liste les utilisateurs pour le filtre de l'agenda
|
|
7
7
|
export async function GET(request: NextRequest) {
|
|
8
8
|
try {
|
|
9
9
|
const session = await auth.api.getSession({
|
|
@@ -25,7 +25,6 @@ export async function GET(request: NextRequest) {
|
|
|
25
25
|
id: true,
|
|
26
26
|
name: true,
|
|
27
27
|
email: true,
|
|
28
|
-
eventColor: true,
|
|
29
28
|
},
|
|
30
29
|
orderBy: {
|
|
31
30
|
name: 'asc',
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { prisma } from '@/lib/prisma';
|
|
3
4
|
import { checkPermission } from '@/lib/check-permission';
|
|
4
5
|
import { auth } from '@/lib/auth';
|
|
5
6
|
import { logAudit } from '@/lib/audit-log';
|
|
7
|
+
import { resolveRoleFromCustomRoleName } from '@/lib/roles';
|
|
8
|
+
|
|
9
|
+
const createUserSchema = z.object({
|
|
10
|
+
name: z.string().trim().min(1, 'Le nom est requis'),
|
|
11
|
+
email: z.string().trim().email('Email invalide'),
|
|
12
|
+
customRoleId: z.string().trim().min(1, 'Le profil est requis'),
|
|
13
|
+
});
|
|
6
14
|
|
|
7
15
|
// GET /api/users - Liste tous les utilisateurs (admin seulement)
|
|
8
16
|
export async function GET(request: NextRequest) {
|
|
@@ -29,27 +37,68 @@ export async function GET(request: NextRequest) {
|
|
|
29
37
|
name: true,
|
|
30
38
|
},
|
|
31
39
|
},
|
|
40
|
+
accounts: {
|
|
41
|
+
where: { providerId: 'credential' },
|
|
42
|
+
select: { id: true },
|
|
43
|
+
},
|
|
32
44
|
},
|
|
33
45
|
orderBy: {
|
|
34
46
|
createdAt: 'desc',
|
|
35
47
|
},
|
|
36
48
|
});
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const emails: string[] = [];
|
|
51
|
+
for (const u of users) {
|
|
52
|
+
if (!u.emailVerified && u.accounts.length === 0) {
|
|
53
|
+
emails.push(u.email);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const verifications =
|
|
58
|
+
emails.length > 0
|
|
59
|
+
? await prisma.verification.findMany({
|
|
60
|
+
where: { identifier: { in: emails } },
|
|
61
|
+
select: { identifier: true, expiresAt: true },
|
|
62
|
+
orderBy: { expiresAt: 'desc' },
|
|
63
|
+
})
|
|
64
|
+
: [];
|
|
65
|
+
|
|
66
|
+
const latestTokenByEmail = new Map<string, Date>();
|
|
67
|
+
for (const v of verifications) {
|
|
68
|
+
if (!latestTokenByEmail.has(v.identifier)) {
|
|
69
|
+
latestTokenByEmail.set(v.identifier, v.expiresAt);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const usersWithRole = users.map((user: any) => {
|
|
75
|
+
let invitationStatus: 'completed' | 'pending' | 'expired' | null = null;
|
|
76
|
+
if (user.emailVerified || user.accounts.length > 0) {
|
|
77
|
+
invitationStatus = 'completed';
|
|
78
|
+
} else {
|
|
79
|
+
const expiresAt = latestTokenByEmail.get(user.email);
|
|
80
|
+
if (!expiresAt) {
|
|
81
|
+
invitationStatus = 'expired';
|
|
82
|
+
} else {
|
|
83
|
+
invitationStatus = expiresAt > now ? 'pending' : 'expired';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: user.id,
|
|
89
|
+
name: user.name,
|
|
90
|
+
email: user.email,
|
|
91
|
+
role: user.role || 'USER',
|
|
92
|
+
customRoleId: user.customRoleId,
|
|
93
|
+
customRole: user.customRole,
|
|
94
|
+
emailVerified: user.emailVerified,
|
|
95
|
+
active: user.active,
|
|
96
|
+
createdAt: user.createdAt,
|
|
97
|
+
updatedAt: user.updatedAt,
|
|
98
|
+
image: user.image,
|
|
99
|
+
invitationStatus,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
53
102
|
|
|
54
103
|
return NextResponse.json(usersWithRole);
|
|
55
104
|
} catch (error: any) {
|
|
@@ -75,17 +124,20 @@ export async function POST(request: NextRequest) {
|
|
|
75
124
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
76
125
|
}
|
|
77
126
|
|
|
78
|
-
const
|
|
79
|
-
const
|
|
127
|
+
const json = await request.json();
|
|
128
|
+
const parseResult = createUserSchema.safeParse(json);
|
|
80
129
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
130
|
+
if (!parseResult.success) {
|
|
131
|
+
return NextResponse.json(
|
|
132
|
+
{
|
|
133
|
+
error: 'Données invalides',
|
|
134
|
+
details: parseResult.error.flatten(),
|
|
135
|
+
},
|
|
136
|
+
{ status: 400 },
|
|
137
|
+
);
|
|
84
138
|
}
|
|
85
139
|
|
|
86
|
-
|
|
87
|
-
return NextResponse.json({ error: 'Le profil est requis' }, { status: 400 });
|
|
88
|
-
}
|
|
140
|
+
const { name, email, customRoleId } = parseResult.data;
|
|
89
141
|
|
|
90
142
|
// Vérifier si l'email existe déjà
|
|
91
143
|
const existingUser = await prisma.user.findUnique({
|
|
@@ -105,59 +157,38 @@ export async function POST(request: NextRequest) {
|
|
|
105
157
|
// Si l'utilisateur existe mais sans compte, on peut régénérer un token
|
|
106
158
|
}
|
|
107
159
|
|
|
160
|
+
const customRole = await prisma.customRole.findUnique({
|
|
161
|
+
where: { id: customRoleId },
|
|
162
|
+
select: { name: true },
|
|
163
|
+
});
|
|
164
|
+
const resolvedRole = resolveRoleFromCustomRoleName(customRole?.name);
|
|
165
|
+
|
|
108
166
|
let user;
|
|
109
167
|
if (existingUser && existingUser.accounts.length === 0) {
|
|
110
|
-
// Utilisateur existe déjà sans compte, on met à jour et régénère le token
|
|
111
168
|
user = await prisma.user.update({
|
|
112
169
|
where: { id: existingUser.id },
|
|
113
170
|
data: {
|
|
114
171
|
name,
|
|
115
172
|
customRoleId,
|
|
173
|
+
role: resolvedRole,
|
|
116
174
|
active: true,
|
|
117
175
|
},
|
|
118
176
|
});
|
|
119
177
|
} else {
|
|
120
|
-
// Générer une couleur aléatoire pour les événements
|
|
121
|
-
const colors = [
|
|
122
|
-
'#EF4444', // Rouge
|
|
123
|
-
'#3B82F6', // Bleu
|
|
124
|
-
'#10B981', // Vert
|
|
125
|
-
'#F59E0B', // Orange
|
|
126
|
-
'#8B5CF6', // Violet
|
|
127
|
-
'#EC4899', // Rose
|
|
128
|
-
'#06B6D4', // Cyan
|
|
129
|
-
'#84CC16', // Lime
|
|
130
|
-
'#F97316', // Orange foncé
|
|
131
|
-
'#6366F1', // Indigo
|
|
132
|
-
'#14B8A6', // Teal
|
|
133
|
-
'#A855F7', // Purple
|
|
134
|
-
];
|
|
135
|
-
const randomColor = colors[Math.floor(Math.random() * colors.length)];
|
|
136
|
-
|
|
137
|
-
// Créer l'utilisateur SANS mot de passe (sans Account)
|
|
138
178
|
user = await prisma.user.create({
|
|
139
179
|
data: {
|
|
140
180
|
id: crypto.randomUUID(),
|
|
141
181
|
name,
|
|
142
182
|
email,
|
|
143
|
-
role:
|
|
183
|
+
role: resolvedRole,
|
|
144
184
|
customRoleId,
|
|
145
|
-
emailVerified: false,
|
|
185
|
+
emailVerified: false,
|
|
146
186
|
active: true,
|
|
147
|
-
eventColor: randomColor,
|
|
148
187
|
},
|
|
149
188
|
});
|
|
150
189
|
}
|
|
151
190
|
|
|
152
|
-
|
|
153
|
-
let customRoleName: string | null = null;
|
|
154
|
-
if (customRoleId) {
|
|
155
|
-
const customRole = await prisma.customRole.findUnique({
|
|
156
|
-
where: { id: customRoleId },
|
|
157
|
-
select: { name: true },
|
|
158
|
-
});
|
|
159
|
-
customRoleName = customRole?.name || null;
|
|
160
|
-
}
|
|
191
|
+
const customRoleName = customRole?.name || null;
|
|
161
192
|
|
|
162
193
|
// Log d'audit : création ou réactivation d'utilisateur
|
|
163
194
|
await logAudit({
|
|
@@ -264,6 +295,14 @@ export async function POST(request: NextRequest) {
|
|
|
264
295
|
return NextResponse.json({ error: 'Cet email est déjà utilisé' }, { status: 400 });
|
|
265
296
|
}
|
|
266
297
|
|
|
267
|
-
return NextResponse.json(
|
|
298
|
+
return NextResponse.json(
|
|
299
|
+
{
|
|
300
|
+
error:
|
|
301
|
+
process.env.NODE_ENV === 'development'
|
|
302
|
+
? error.message || 'Erreur serveur'
|
|
303
|
+
: 'Erreur serveur',
|
|
304
|
+
},
|
|
305
|
+
{ status: 500 },
|
|
306
|
+
);
|
|
268
307
|
}
|
|
269
308
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import crypto from 'crypto';
|
|
2
3
|
import { prisma } from '@/lib/prisma';
|
|
3
4
|
import { handleContactDuplicate } from '@/lib/contact-duplicate';
|
|
4
5
|
import { normalizePhoneNumber } from '@/lib/utils';
|
|
@@ -18,7 +19,8 @@ interface GoogleAdsLeadNotification {
|
|
|
18
19
|
// POST /api/webhooks/google-ads - Réception des leads Google Ads (lead form extensions)
|
|
19
20
|
export async function POST(request: NextRequest) {
|
|
20
21
|
try {
|
|
21
|
-
const
|
|
22
|
+
const rawBody = await request.text();
|
|
23
|
+
const body = JSON.parse(rawBody);
|
|
22
24
|
|
|
23
25
|
const notification: GoogleAdsLeadNotification | undefined = body?.leadNotification;
|
|
24
26
|
|
|
@@ -27,6 +29,23 @@ export async function POST(request: NextRequest) {
|
|
|
27
29
|
return NextResponse.json({ received: true });
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// Sécurité HMAC (si configurée)
|
|
33
|
+
const webhookSecret = process.env.GOOGLE_ADS_WEBHOOK_SECRET;
|
|
34
|
+
if (webhookSecret) {
|
|
35
|
+
const signatureHeader = request.headers.get('x-goog-signature');
|
|
36
|
+
if (signatureHeader) {
|
|
37
|
+
const expectedSignature = crypto
|
|
38
|
+
.createHmac('sha256', webhookSecret)
|
|
39
|
+
.update(rawBody)
|
|
40
|
+
.digest('base64');
|
|
41
|
+
|
|
42
|
+
if (signatureHeader !== expectedSignature) {
|
|
43
|
+
console.error('Webhook Google Ads Lead Forms: Signature HMAC invalide');
|
|
44
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
const client = prisma as any;
|
|
31
50
|
|
|
32
51
|
// Récupérer toutes les configurations actives
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import crypto from 'crypto';
|
|
2
3
|
import { prisma } from '@/lib/prisma';
|
|
3
4
|
import { decrypt } from '@/lib/encryption';
|
|
4
5
|
import { handleContactDuplicate } from '@/lib/contact-duplicate';
|
|
@@ -50,12 +51,28 @@ export async function GET(request: NextRequest) {
|
|
|
50
51
|
// POST /api/webhooks/meta-leads - Réception des leads Meta
|
|
51
52
|
export async function POST(request: NextRequest) {
|
|
52
53
|
try {
|
|
53
|
-
const
|
|
54
|
+
const rawBody = await request.text();
|
|
55
|
+
const body = JSON.parse(rawBody);
|
|
54
56
|
|
|
55
57
|
if (body.object !== 'page' || !Array.isArray(body.entry)) {
|
|
56
58
|
return NextResponse.json({ received: true });
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// Sécurité HMAC (si configurée)
|
|
62
|
+
const webhookSecret = process.env.META_LEADS_WEBHOOK_SECRET;
|
|
63
|
+
if (webhookSecret) {
|
|
64
|
+
const signatureHeader = request.headers.get('x-hub-signature-256');
|
|
65
|
+
if (signatureHeader) {
|
|
66
|
+
const expectedSignature =
|
|
67
|
+
'sha256=' + crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
|
|
68
|
+
|
|
69
|
+
if (signatureHeader !== expectedSignature) {
|
|
70
|
+
console.error('Webhook Meta Lead Ads: Signature HMAC invalide');
|
|
71
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
// Récupérer toutes les configurations actives
|
|
60
77
|
const configs = await prisma.metaLeadConfig.findMany({
|
|
61
78
|
where: { active: true },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
5
|
|
|
5
6
|
// GET /api/workflows/[id] - Récupérer un workflow spécifique
|
|
6
7
|
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -13,6 +14,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|
|
13
14
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const canView = await checkPermission('workflows.view');
|
|
18
|
+
if (!canView) {
|
|
19
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
const { id } = await params;
|
|
17
23
|
|
|
18
24
|
const workflow = await prisma.workflow.findFirst({
|
|
@@ -56,6 +62,11 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
56
62
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
const canEdit = await checkPermission('workflows.edit');
|
|
66
|
+
if (!canEdit) {
|
|
67
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
const { id } = await params;
|
|
60
71
|
const body = await request.json();
|
|
61
72
|
const {
|
|
@@ -67,10 +78,13 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
67
78
|
triggerToStatusId,
|
|
68
79
|
triggerTimeDays,
|
|
69
80
|
triggerTimeHours,
|
|
81
|
+
triggerTimeReference,
|
|
82
|
+
triggerTaskType,
|
|
83
|
+
triggerTransactionFromStatus,
|
|
84
|
+
triggerTransactionToStatus,
|
|
70
85
|
actions = [],
|
|
71
86
|
} = body;
|
|
72
87
|
|
|
73
|
-
// Vérifier que le workflow appartient à l'utilisateur
|
|
74
88
|
const existingWorkflow = await prisma.workflow.findFirst({
|
|
75
89
|
where: {
|
|
76
90
|
id,
|
|
@@ -82,7 +96,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
82
96
|
return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
|
|
83
97
|
}
|
|
84
98
|
|
|
85
|
-
// Validation
|
|
86
99
|
if (!name || !triggerType) {
|
|
87
100
|
return NextResponse.json(
|
|
88
101
|
{ error: 'Le nom et le type de déclencheur sont requis' },
|
|
@@ -90,12 +103,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
90
103
|
);
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
// Supprimer les anciennes actions
|
|
94
106
|
await prisma.workflowAction.deleteMany({
|
|
95
107
|
where: { workflowId: id },
|
|
96
108
|
});
|
|
97
109
|
|
|
98
|
-
// Mettre à jour le workflow
|
|
99
110
|
const workflow = await prisma.workflow.update({
|
|
100
111
|
where: { id },
|
|
101
112
|
data: {
|
|
@@ -107,6 +118,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
107
118
|
triggerToStatusId: triggerToStatusId || null,
|
|
108
119
|
triggerTimeDays: triggerTimeDays || null,
|
|
109
120
|
triggerTimeHours: triggerTimeHours || null,
|
|
121
|
+
triggerTimeReference: triggerTimeReference || null,
|
|
122
|
+
triggerTaskType: triggerTaskType || null,
|
|
123
|
+
triggerTransactionFromStatus: triggerTransactionFromStatus || null,
|
|
124
|
+
triggerTransactionToStatus: triggerTransactionToStatus || null,
|
|
110
125
|
actions: {
|
|
111
126
|
create: actions.map((action: any, index: number) => ({
|
|
112
127
|
actionType: action.actionType,
|
|
@@ -118,8 +133,17 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
118
133
|
newStatusId: action.newStatusId || null,
|
|
119
134
|
taskTitle: action.taskTitle || null,
|
|
120
135
|
taskDescription: action.taskDescription || null,
|
|
136
|
+
taskType: action.taskType || null,
|
|
137
|
+
taskPriority: action.taskPriority || null,
|
|
138
|
+
taskAssignedUserId: action.taskAssignedUserId || null,
|
|
139
|
+
assignCommercialId: action.assignCommercialId || null,
|
|
140
|
+
assignTeleproId: action.assignTeleproId || null,
|
|
141
|
+
noteContent: action.noteContent || null,
|
|
142
|
+
notifyUserId: action.notifyUserId || null,
|
|
121
143
|
conditionOperator: action.conditionOperator || null,
|
|
122
144
|
conditionStatusId: action.conditionStatusId || null,
|
|
145
|
+
conditionOrigin: action.conditionOrigin || null,
|
|
146
|
+
conditionHasCompany: action.conditionHasCompany ?? null,
|
|
123
147
|
})),
|
|
124
148
|
},
|
|
125
149
|
},
|
|
@@ -158,9 +182,13 @@ export async function DELETE(
|
|
|
158
182
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
159
183
|
}
|
|
160
184
|
|
|
185
|
+
const canDelete = await checkPermission('workflows.delete');
|
|
186
|
+
if (!canDelete) {
|
|
187
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
188
|
+
}
|
|
189
|
+
|
|
161
190
|
const { id } = await params;
|
|
162
191
|
|
|
163
|
-
// Vérifier que le workflow appartient à l'utilisateur
|
|
164
192
|
const workflow = await prisma.workflow.findFirst({
|
|
165
193
|
where: {
|
|
166
194
|
id,
|
|
@@ -172,7 +200,6 @@ export async function DELETE(
|
|
|
172
200
|
return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
|
|
173
201
|
}
|
|
174
202
|
|
|
175
|
-
// Supprimer le workflow (les actions seront supprimées en cascade)
|
|
176
203
|
await prisma.workflow.delete({
|
|
177
204
|
where: { id },
|
|
178
205
|
});
|