create-crm-tmp 2.0.0 → 2.1.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 +56 -35
- package/package.json +1 -1
- package/template/README.md +230 -115
- package/template/eslint.config.mjs +13 -0
- package/template/next.config.ts +14 -0
- package/template/package.json +15 -2
- package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +132 -637
- package/template/src/app/(auth)/invite/[token]/page.tsx +10 -8
- package/template/src/app/(auth)/layout.tsx +1 -1
- package/template/src/app/(auth)/reset-password/complete/page.tsx +11 -8
- package/template/src/app/(auth)/reset-password/page.tsx +4 -4
- package/template/src/app/(auth)/reset-password/verify/page.tsx +4 -4
- package/template/src/app/(auth)/signin/page.tsx +14 -6
- package/template/src/app/(dashboard)/agenda/page.tsx +2243 -988
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +18 -104
- package/template/src/app/(dashboard)/automatisation/page.tsx +10 -26
- package/template/src/app/(dashboard)/closing/page.tsx +78 -62
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +2082 -1080
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +46 -47
- package/template/src/app/(dashboard)/contacts/page.tsx +1062 -780
- package/template/src/app/(dashboard)/dashboard/page.tsx +533 -37
- package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
- package/template/src/app/(dashboard)/layout.tsx +6 -2
- package/template/src/app/(dashboard)/settings/page.tsx +797 -2582
- package/template/src/app/(dashboard)/templates/page.tsx +55 -54
- package/template/src/app/(dashboard)/users/list/page.tsx +51 -48
- package/template/src/app/(dashboard)/users/page.tsx +1 -1
- package/template/src/app/(dashboard)/users/permissions/page.tsx +2 -2
- package/template/src/app/(dashboard)/users/roles/page.tsx +7 -5
- package/template/src/app/api/agenda/google-events/route.ts +92 -0
- package/template/src/app/api/auth/check-active/route.ts +3 -2
- package/template/src/app/api/auth/google/route.ts +2 -1
- package/template/src/app/api/auth/google/status/route.ts +7 -31
- package/template/src/app/api/companies/[id]/activities/route.ts +1 -3
- package/template/src/app/api/companies/[id]/route.ts +1 -2
- package/template/src/app/api/companies/route.ts +42 -12
- package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +9 -31
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +14 -32
- package/template/src/app/api/contacts/[id]/files/route.ts +112 -212
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
- package/template/src/app/api/contacts/[id]/interactions/route.ts +16 -16
- package/template/src/app/api/contacts/[id]/kyc/route.ts +21 -11
- package/template/src/app/api/contacts/[id]/meet/route.ts +19 -2
- package/template/src/app/api/contacts/[id]/route.ts +106 -34
- package/template/src/app/api/contacts/[id]/send-email/route.ts +27 -11
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +6 -0
- package/template/src/app/api/contacts/export/route.ts +9 -13
- package/template/src/app/api/contacts/import/route.ts +55 -25
- package/template/src/app/api/contacts/import-preview/route.ts +1 -1
- package/template/src/app/api/contacts/origins/route.ts +63 -0
- package/template/src/app/api/contacts/route.ts +153 -41
- package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +44 -0
- package/template/src/app/api/dashboard/widgets/route.ts +181 -0
- package/template/src/app/api/dev/reminders/test/route.ts +114 -0
- package/template/src/app/api/editor/upload-image/route.ts +61 -0
- package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
- package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +24 -556
- package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
- package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
- package/template/src/app/api/reminders/clear/route.ts +120 -0
- package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
- package/template/src/app/api/reminders/route.ts +164 -39
- package/template/src/app/api/reminders/state/route.ts +164 -0
- package/template/src/app/api/reset-password/request/route.ts +1 -1
- package/template/src/app/api/reset-password/verify/route.ts +1 -1
- package/template/src/app/api/send/route.ts +16 -4
- package/template/src/app/api/settings/google-ads/route.ts +14 -0
- package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
- package/template/src/app/api/settings/google-calendar/route.ts +124 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +28 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +37 -4
- package/template/src/app/api/settings/google-sheet/preview/route.ts +9 -3
- package/template/src/app/api/settings/google-sheet/route.ts +14 -0
- package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
- package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +0 -1
- package/template/src/app/api/settings/meta-leads/route.ts +14 -2
- package/template/src/app/api/settings/smtp/route.ts +53 -6
- package/template/src/app/api/tasks/[id]/attendees/route.ts +24 -8
- package/template/src/app/api/tasks/[id]/route.ts +234 -58
- package/template/src/app/api/tasks/meet/route.ts +27 -19
- package/template/src/app/api/tasks/route.ts +62 -17
- package/template/src/app/api/users/[id]/route.ts +20 -14
- package/template/src/app/api/users/list/route.ts +57 -19
- package/template/src/app/api/webhooks/google-ads/route.ts +34 -14
- package/template/src/app/api/webhooks/meta-leads/route.ts +32 -12
- package/template/src/app/api/workflows/[id]/route.ts +0 -4
- package/template/src/app/api/workflows/process/route.ts +22 -51
- package/template/src/app/api/workflows/route.ts +0 -4
- package/template/src/app/globals.css +342 -4
- package/template/src/app/layout.tsx +11 -3
- package/template/src/app/page.tsx +1 -1
- package/template/src/components/address-autocomplete.tsx +7 -6
- package/template/src/components/config-error-alert.tsx +46 -0
- package/template/src/components/contacts/filter-bar.tsx +12 -3
- package/template/src/components/contacts/filter-builder.tsx +28 -43
- package/template/src/components/contacts/save-view-dialog.tsx +1 -1
- package/template/src/components/contacts/views-tab-bar.tsx +15 -6
- package/template/src/components/dashboard/activity-chart.tsx +41 -28
- package/template/src/components/dashboard/add-widget-dialog.tsx +157 -0
- package/template/src/components/dashboard/color-picker.tsx +64 -0
- package/template/src/components/dashboard/contacts-chart.tsx +69 -0
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +121 -0
- package/template/src/components/dashboard/recent-activity.tsx +154 -0
- package/template/src/components/dashboard/stat-card.tsx +40 -40
- package/template/src/components/dashboard/status-distribution-chart.tsx +81 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +37 -34
- package/template/src/components/dashboard/top-contacts-list.tsx +113 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +72 -81
- package/template/src/components/dashboard/widget-wrapper.tsx +36 -0
- package/template/src/components/date-picker.tsx +9 -6
- package/template/src/components/editor/upload-editor-image.ts +42 -0
- package/template/src/components/editor.tsx +161 -22
- package/template/src/components/email-template.tsx +2 -2
- package/template/src/components/global-search.tsx +30 -28
- package/template/src/components/header.tsx +178 -80
- package/template/src/components/inactive-account-guard.tsx +58 -0
- package/template/src/components/integration-notifications-listener.tsx +12 -0
- package/template/src/components/invitation-email-template.tsx +2 -2
- package/template/src/components/meet-cancellation-email-template.tsx +3 -3
- package/template/src/components/meet-confirmation-email-template.tsx +3 -3
- package/template/src/components/meet-update-email-template.tsx +3 -3
- package/template/src/components/page-header.tsx +5 -5
- package/template/src/components/protected-page.tsx +1 -1
- package/template/src/components/reset-password-email-template.tsx +2 -2
- package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
- package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
- package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
- package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
- package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
- package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
- package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
- package/template/src/components/sidebar.tsx +45 -26
- package/template/src/components/skeleton.tsx +40 -43
- package/template/src/components/ui/accordion.tsx +2 -2
- package/template/src/components/ui/alert-dialog.tsx +1 -1
- package/template/src/components/ui/button.tsx +20 -9
- package/template/src/components/ui/components.tsx +1 -1
- package/template/src/components/ui/date-picker.tsx +422 -0
- package/template/src/components/ui/datetime-picker.tsx +338 -0
- package/template/src/components/ui/status-select.tsx +271 -0
- package/template/src/components/ui/tooltip.tsx +37 -0
- package/template/src/components/view-as-modal.tsx +13 -7
- package/template/src/contexts/app-toast-context.tsx +245 -57
- package/template/src/contexts/dashboard-theme-context.tsx +53 -0
- package/template/src/contexts/sidebar-context.tsx +22 -17
- package/template/src/contexts/task-reminder-context.tsx +134 -160
- package/template/src/contexts/view-as-context.tsx +33 -6
- package/template/src/hooks/use-focus-trap.ts +2 -2
- package/template/src/hooks/useIntegrationNotifications.ts +49 -0
- package/template/src/lib/auth.ts +8 -1
- package/template/src/lib/config-links.ts +14 -0
- package/template/src/lib/contact-duplicate.ts +79 -61
- package/template/src/lib/contact-interactions.ts +21 -21
- package/template/src/lib/contact-view-filters.ts +24 -64
- package/template/src/lib/contacts-list-url.ts +190 -0
- package/template/src/lib/dashboard-stats.ts +65 -7
- package/template/src/lib/dashboard-themes.ts +135 -0
- package/template/src/lib/date-utils.ts +127 -0
- package/template/src/lib/default-widgets.ts +12 -0
- package/template/src/lib/editor-html-image-dimensions.ts +172 -0
- package/template/src/lib/editor-image-limits.ts +19 -0
- package/template/src/lib/email-html-sanitize.ts +19 -0
- package/template/src/lib/encryption.ts +9 -6
- package/template/src/lib/fr-geography.ts +192 -0
- package/template/src/lib/google-calendar-agenda.ts +201 -0
- package/template/src/lib/google-calendar.ts +255 -5
- package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
- package/template/src/lib/google-sheet-sync-runner.ts +514 -0
- package/template/src/lib/integration-import-log.ts +21 -0
- package/template/src/lib/permissions.ts +40 -10
- package/template/src/lib/prisma.ts +4 -1
- package/template/src/lib/qstash.ts +65 -0
- package/template/src/lib/reminder-state-server.ts +80 -0
- package/template/src/lib/reminder-state.ts +29 -0
- package/template/src/lib/supabase-storage.ts +113 -0
- package/template/src/lib/template-variables.ts +164 -23
- package/template/src/lib/utils.ts +45 -0
- package/template/src/lib/widget-registry.ts +173 -0
- package/template/src/lib/workflow-executor.ts +16 -70
- package/template/src/proxy.ts +1 -0
- package/template/vercel.json +3 -10
- package/template/skills-lock.json +0 -25
- package/template/src/components/dashboard/dashboard-content.tsx +0 -79
- package/template/src/lib/google-drive.ts +0 -1101
- package/template/src/types/yousign.ts +0 -52
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
assignContactImmediate,
|
|
7
7
|
addNoteImmediate,
|
|
8
8
|
notifyUserImmediate,
|
|
9
|
+
getContactVariables,
|
|
9
10
|
} from '@/lib/workflow-executor';
|
|
10
11
|
import { decrypt } from '@/lib/encryption';
|
|
11
12
|
import nodemailer from 'nodemailer';
|
|
@@ -18,21 +19,22 @@ import { replaceTemplateVariables } from '@/lib/template-variables';
|
|
|
18
19
|
*/
|
|
19
20
|
export async function GET(request: NextRequest) {
|
|
20
21
|
try {
|
|
22
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
21
23
|
const cronSecret = process.env.CRON_SECRET;
|
|
24
|
+
const authHeader = request.headers.get('authorization');
|
|
22
25
|
|
|
23
|
-
if (cronSecret) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (!cronSecret && !isDevelopment) {
|
|
27
|
+
return NextResponse.json(
|
|
28
|
+
{ error: 'Unauthorized - CRON_SECRET manquant en production' },
|
|
29
|
+
{ status: 401 },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{ status: 401 },
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
33
|
+
if (cronSecret && authHeader !== `Bearer ${cronSecret}` && !isDevelopment) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: 'Unauthorized - Secret manquant ou invalide' },
|
|
36
|
+
{ status: 401 },
|
|
37
|
+
);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const now = new Date();
|
|
@@ -72,7 +74,9 @@ async function processScheduledActions(now: Date) {
|
|
|
72
74
|
contact: {
|
|
73
75
|
include: {
|
|
74
76
|
status: true,
|
|
75
|
-
company: { select: { name: true } },
|
|
77
|
+
company: { select: { name: true, address: true, city: true, postalCode: true } },
|
|
78
|
+
assignedCommercial: { select: { name: true } },
|
|
79
|
+
assignedTelepro: { select: { name: true } },
|
|
76
80
|
},
|
|
77
81
|
},
|
|
78
82
|
workflow: {
|
|
@@ -139,18 +143,7 @@ async function processScheduledActions(now: Date) {
|
|
|
139
143
|
break;
|
|
140
144
|
|
|
141
145
|
case 'ADD_NOTE': {
|
|
142
|
-
const variables =
|
|
143
|
-
firstName: contact.firstName || '',
|
|
144
|
-
lastName: contact.lastName || '',
|
|
145
|
-
civility: contact.civility || '',
|
|
146
|
-
email: contact.email || '',
|
|
147
|
-
phone: contact.phone || '',
|
|
148
|
-
secondaryPhone: contact.secondaryPhone || '',
|
|
149
|
-
address: contact.address || '',
|
|
150
|
-
city: contact.city || '',
|
|
151
|
-
postalCode: contact.postalCode || '',
|
|
152
|
-
companyName: contact.company?.name || '',
|
|
153
|
-
};
|
|
146
|
+
const variables = getContactVariables(contact);
|
|
154
147
|
const noteContent = replaceTemplateVariables(actionData.noteContent || '', variables);
|
|
155
148
|
await addNoteImmediate(
|
|
156
149
|
contact.id,
|
|
@@ -352,18 +345,7 @@ async function getExecuteWorkflowActions() {
|
|
|
352
345
|
(action.delayDays || 0) * 24 * 60 * 60 * 1000 + (action.delayHours || 0) * 60 * 60 * 1000;
|
|
353
346
|
const executeAt = new Date(Date.now() + delayMs);
|
|
354
347
|
|
|
355
|
-
const variables =
|
|
356
|
-
firstName: contact.firstName || '',
|
|
357
|
-
lastName: contact.lastName || '',
|
|
358
|
-
civility: contact.civility || '',
|
|
359
|
-
email: contact.email || '',
|
|
360
|
-
phone: contact.phone || '',
|
|
361
|
-
secondaryPhone: contact.secondaryPhone || '',
|
|
362
|
-
address: contact.address || '',
|
|
363
|
-
city: contact.city || '',
|
|
364
|
-
postalCode: contact.postalCode || '',
|
|
365
|
-
companyName: contact.company?.name || '',
|
|
366
|
-
};
|
|
348
|
+
const variables = getContactVariables(contact);
|
|
367
349
|
|
|
368
350
|
switch (action.actionType) {
|
|
369
351
|
case 'SEND_EMAIL':
|
|
@@ -527,7 +509,7 @@ async function getExecuteWorkflowActions() {
|
|
|
527
509
|
if (action.smsMessage && contact.phone) {
|
|
528
510
|
let message = action.smsMessage;
|
|
529
511
|
for (const [key, value] of Object.entries(variables)) {
|
|
530
|
-
message = message.
|
|
512
|
+
message = message.replaceAll(`{${key}}`, value as string);
|
|
531
513
|
}
|
|
532
514
|
if (executeAt <= new Date()) {
|
|
533
515
|
await mod.sendSMSImmediate(workflow, contact, message);
|
|
@@ -571,18 +553,7 @@ async function executeScheduledEmail(
|
|
|
571
553
|
throw new Error('Configuration SMTP non trouvée');
|
|
572
554
|
}
|
|
573
555
|
|
|
574
|
-
const variables =
|
|
575
|
-
firstName: contact.firstName || '',
|
|
576
|
-
lastName: contact.lastName || '',
|
|
577
|
-
civility: contact.civility || '',
|
|
578
|
-
email: contact.email || '',
|
|
579
|
-
phone: contact.phone || '',
|
|
580
|
-
secondaryPhone: contact.secondaryPhone || '',
|
|
581
|
-
address: contact.address || '',
|
|
582
|
-
city: contact.city || '',
|
|
583
|
-
postalCode: contact.postalCode || '',
|
|
584
|
-
companyName: contact.company?.name || '',
|
|
585
|
-
};
|
|
556
|
+
const variables = getContactVariables(contact);
|
|
586
557
|
|
|
587
558
|
const subject = replaceTemplateVariables(actionData.templateSubject || '', variables);
|
|
588
559
|
const content = replaceTemplateVariables(actionData.templateContent || '', variables);
|
|
@@ -638,7 +609,7 @@ async function executeScheduledSMS(
|
|
|
638
609
|
};
|
|
639
610
|
|
|
640
611
|
for (const [key, value] of Object.entries(variables)) {
|
|
641
|
-
message = message.
|
|
612
|
+
message = message.replaceAll(`{${key}}`, value);
|
|
642
613
|
}
|
|
643
614
|
|
|
644
615
|
console.log(`SMS à envoyer à ${contact.phone}: ${message}`);
|
|
@@ -83,8 +83,6 @@ export async function POST(request: NextRequest) {
|
|
|
83
83
|
triggerTimeHours,
|
|
84
84
|
triggerTimeReference,
|
|
85
85
|
triggerTaskType,
|
|
86
|
-
triggerTransactionFromStatus,
|
|
87
|
-
triggerTransactionToStatus,
|
|
88
86
|
actions = [],
|
|
89
87
|
} = body;
|
|
90
88
|
|
|
@@ -108,8 +106,6 @@ export async function POST(request: NextRequest) {
|
|
|
108
106
|
triggerTimeHours: triggerTimeHours || null,
|
|
109
107
|
triggerTimeReference: triggerTimeReference || null,
|
|
110
108
|
triggerTaskType: triggerTaskType || null,
|
|
111
|
-
triggerTransactionFromStatus: triggerTransactionFromStatus || null,
|
|
112
|
-
triggerTransactionToStatus: triggerTransactionToStatus || null,
|
|
113
109
|
actions: {
|
|
114
110
|
create: actions.map((action: any, index: number) => ({
|
|
115
111
|
actionType: action.actionType,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
|
+
@import 'react-grid-layout/css/styles.css';
|
|
2
3
|
|
|
3
4
|
@custom-variant dark (&:is(.dark *));
|
|
4
5
|
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
--radius-4xl: calc(var(--radius) + 16px);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
h1,
|
|
52
|
+
/* h1,
|
|
52
53
|
h2,
|
|
53
54
|
h3,
|
|
54
55
|
h4,
|
|
@@ -56,7 +57,7 @@ h5,
|
|
|
56
57
|
h6 {
|
|
57
58
|
font-family: var(--font-display), serif;
|
|
58
59
|
letter-spacing: -0.02em;
|
|
59
|
-
}
|
|
60
|
+
} */
|
|
60
61
|
|
|
61
62
|
/* LexKit Editor - Clean Framework-Agnostic Styles */
|
|
62
63
|
|
|
@@ -1487,9 +1488,12 @@ table td[data-lexical-table-cell-selection] {
|
|
|
1487
1488
|
|
|
1488
1489
|
:root {
|
|
1489
1490
|
--radius: 0.625rem;
|
|
1490
|
-
--duration-fast:
|
|
1491
|
-
--duration-normal:
|
|
1491
|
+
--duration-fast: 120ms;
|
|
1492
|
+
--duration-normal: 200ms;
|
|
1493
|
+
--duration-slow: 320ms;
|
|
1492
1494
|
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1495
|
+
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
|
|
1496
|
+
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
|
|
1493
1497
|
--shadow-card: 0 10px 30px -18px rgb(15 23 42 / 0.22);
|
|
1494
1498
|
--shadow-dropdown: 0 18px 45px -24px rgb(15 23 42 / 0.3);
|
|
1495
1499
|
--background: oklch(1 0 0);
|
|
@@ -1574,6 +1578,15 @@ table td[data-lexical-table-cell-selection] {
|
|
|
1574
1578
|
body {
|
|
1575
1579
|
@apply bg-background text-foreground;
|
|
1576
1580
|
}
|
|
1581
|
+
/* Eliminate 300ms tap-delay on all interactive elements */
|
|
1582
|
+
button,
|
|
1583
|
+
a,
|
|
1584
|
+
[role='button'],
|
|
1585
|
+
input,
|
|
1586
|
+
select,
|
|
1587
|
+
textarea {
|
|
1588
|
+
touch-action: manipulation;
|
|
1589
|
+
}
|
|
1577
1590
|
}
|
|
1578
1591
|
|
|
1579
1592
|
@layer utilities {
|
|
@@ -1619,3 +1632,328 @@ table td[data-lexical-table-cell-selection] {
|
|
|
1619
1632
|
scroll-margin-top: 6rem;
|
|
1620
1633
|
}
|
|
1621
1634
|
}
|
|
1635
|
+
|
|
1636
|
+
/* ===== DASHBOARD THEME UTILITIES ===== */
|
|
1637
|
+
|
|
1638
|
+
.dash-link {
|
|
1639
|
+
color: var(--dash-600);
|
|
1640
|
+
transition: color 150ms;
|
|
1641
|
+
}
|
|
1642
|
+
.dash-link:hover {
|
|
1643
|
+
color: var(--dash-700);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.dash-btn {
|
|
1647
|
+
background-color: var(--dash-500);
|
|
1648
|
+
color: white;
|
|
1649
|
+
transition: background-color 150ms;
|
|
1650
|
+
}
|
|
1651
|
+
.dash-btn:hover {
|
|
1652
|
+
background-color: var(--dash-600);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.dash-accent-bar {
|
|
1656
|
+
background-color: var(--dash-500);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
.dash-icon-box {
|
|
1660
|
+
background-color: var(--dash-100);
|
|
1661
|
+
transition: background-color 150ms;
|
|
1662
|
+
}
|
|
1663
|
+
.group:hover .dash-icon-box-gh {
|
|
1664
|
+
background-color: var(--dash-200);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
.dash-icon-color {
|
|
1668
|
+
color: var(--dash-600);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.dash-avatar {
|
|
1672
|
+
background-color: var(--dash-100);
|
|
1673
|
+
color: var(--dash-700);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.dash-pill-active {
|
|
1677
|
+
background-color: var(--dash-500);
|
|
1678
|
+
color: white;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.dash-hover-bg:hover {
|
|
1682
|
+
background-color: color-mix(in srgb, var(--dash-50) 30%, transparent);
|
|
1683
|
+
}
|
|
1684
|
+
.dash-hover-border:hover {
|
|
1685
|
+
border-color: var(--dash-200);
|
|
1686
|
+
}
|
|
1687
|
+
.dash-hover-border-left:hover {
|
|
1688
|
+
border-left-color: var(--dash-400);
|
|
1689
|
+
background-color: color-mix(in srgb, var(--dash-50) 30%, transparent);
|
|
1690
|
+
}
|
|
1691
|
+
.dash-hover-border-light:hover {
|
|
1692
|
+
border-color: var(--dash-100);
|
|
1693
|
+
}
|
|
1694
|
+
.dash-hover-text:hover {
|
|
1695
|
+
color: var(--dash-600);
|
|
1696
|
+
}
|
|
1697
|
+
.group:hover .dash-hover-text-gh {
|
|
1698
|
+
color: var(--dash-500);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
.dash-legend-bg {
|
|
1702
|
+
background-color: color-mix(in srgb, var(--dash-50) 80%, transparent);
|
|
1703
|
+
}
|
|
1704
|
+
.dash-legend-dot {
|
|
1705
|
+
background-color: var(--dash-500);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* ===== REACT GRID LAYOUT OVERRIDES ===== */
|
|
1709
|
+
|
|
1710
|
+
.react-grid-layout {
|
|
1711
|
+
position: relative;
|
|
1712
|
+
transition: height 200ms ease;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.react-grid-item {
|
|
1716
|
+
transition: all 200ms ease;
|
|
1717
|
+
transition-property: left, top, width, height;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.react-grid-item.cssTransforms {
|
|
1721
|
+
transition-property: transform, width, height;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.react-grid-item.resizing {
|
|
1725
|
+
z-index: 1;
|
|
1726
|
+
will-change: width, height;
|
|
1727
|
+
opacity: 0.9;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.react-grid-item.react-draggable-dragging {
|
|
1731
|
+
transition: none;
|
|
1732
|
+
z-index: 3;
|
|
1733
|
+
will-change: transform;
|
|
1734
|
+
opacity: 0.9;
|
|
1735
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
|
1736
|
+
border-radius: 16px;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.react-grid-item > .react-resizable-handle {
|
|
1740
|
+
position: absolute;
|
|
1741
|
+
width: 20px;
|
|
1742
|
+
height: 20px;
|
|
1743
|
+
bottom: 0;
|
|
1744
|
+
right: 0;
|
|
1745
|
+
cursor: se-resize;
|
|
1746
|
+
opacity: 0;
|
|
1747
|
+
transition: opacity 150ms ease;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.react-grid-item:hover > .react-resizable-handle {
|
|
1751
|
+
opacity: 1;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
.react-grid-item > .react-resizable-handle::after {
|
|
1755
|
+
content: '';
|
|
1756
|
+
position: absolute;
|
|
1757
|
+
right: 4px;
|
|
1758
|
+
bottom: 4px;
|
|
1759
|
+
width: 8px;
|
|
1760
|
+
height: 8px;
|
|
1761
|
+
border-right: 2px solid color-mix(in srgb, var(--dash-500, #f97316) 50%, transparent);
|
|
1762
|
+
border-bottom: 2px solid color-mix(in srgb, var(--dash-500, #f97316) 50%, transparent);
|
|
1763
|
+
border-radius: 0 0 2px 0;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
.react-grid-placeholder {
|
|
1767
|
+
background: color-mix(in srgb, var(--dash-500, #f97316) 10%, transparent) !important;
|
|
1768
|
+
border: 2px dashed color-mix(in srgb, var(--dash-500, #f97316) 30%, transparent) !important;
|
|
1769
|
+
border-radius: 16px !important;
|
|
1770
|
+
opacity: 1 !important;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
@keyframes gs-field-in {
|
|
1774
|
+
from {
|
|
1775
|
+
opacity: 0;
|
|
1776
|
+
transform: translateY(6px) scale(0.995);
|
|
1777
|
+
}
|
|
1778
|
+
to {
|
|
1779
|
+
opacity: 1;
|
|
1780
|
+
transform: translateY(0) scale(1);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
.gs-import-field {
|
|
1785
|
+
opacity: 0;
|
|
1786
|
+
animation: gs-field-in 340ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
|
1787
|
+
animation-delay: 0ms;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1791
|
+
.gs-import-field {
|
|
1792
|
+
opacity: 1;
|
|
1793
|
+
animation: none;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/* ===== MOTION SYSTEM — minimal pro ===== */
|
|
1798
|
+
|
|
1799
|
+
@keyframes ui-fade-in {
|
|
1800
|
+
from { opacity: 0; }
|
|
1801
|
+
to { opacity: 1; }
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
@keyframes ui-slide-up-sm {
|
|
1805
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
1806
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
@keyframes ui-slide-down-sm {
|
|
1810
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
1811
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
@keyframes ui-scale-in {
|
|
1815
|
+
from { opacity: 0; transform: scale(0.96); }
|
|
1816
|
+
to { opacity: 1; transform: scale(1); }
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
@keyframes ui-slide-up-fade-out {
|
|
1820
|
+
from { opacity: 1; transform: translateY(0); }
|
|
1821
|
+
to { opacity: 0; transform: translateY(-4px); }
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
@keyframes ui-count-pop {
|
|
1825
|
+
0% { transform: scale(1); }
|
|
1826
|
+
40% { transform: scale(1.2); }
|
|
1827
|
+
100% { transform: scale(1); }
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
/* Cloche notifications : léger balancement périodique pour attirer l’œil */
|
|
1831
|
+
@keyframes ui-bell-nudge {
|
|
1832
|
+
0%,
|
|
1833
|
+
12% {
|
|
1834
|
+
transform: rotate(0deg);
|
|
1835
|
+
}
|
|
1836
|
+
16% {
|
|
1837
|
+
transform: rotate(-9deg);
|
|
1838
|
+
}
|
|
1839
|
+
22% {
|
|
1840
|
+
transform: rotate(9deg);
|
|
1841
|
+
}
|
|
1842
|
+
28% {
|
|
1843
|
+
transform: rotate(-5deg);
|
|
1844
|
+
}
|
|
1845
|
+
34%,
|
|
1846
|
+
100% {
|
|
1847
|
+
transform: rotate(0deg);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
.ui-bell-notify {
|
|
1852
|
+
display: inline-flex;
|
|
1853
|
+
transform-origin: top center;
|
|
1854
|
+
will-change: transform;
|
|
1855
|
+
animation: ui-bell-nudge 4s ease-in-out infinite;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
.ui-fade-in {
|
|
1859
|
+
animation: ui-fade-in var(--duration-normal) var(--ease-standard) both;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.ui-slide-up {
|
|
1863
|
+
animation: ui-slide-up-sm var(--duration-normal) var(--ease-emphasized) both;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
.ui-slide-down {
|
|
1867
|
+
animation: ui-slide-down-sm var(--duration-normal) var(--ease-emphasized) both;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
.ui-scale-in {
|
|
1871
|
+
animation: ui-scale-in var(--duration-normal) var(--ease-emphasized) both;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
.ui-stagger-1 { animation-delay: 30ms; }
|
|
1875
|
+
.ui-stagger-2 { animation-delay: 60ms; }
|
|
1876
|
+
.ui-stagger-3 { animation-delay: 90ms; }
|
|
1877
|
+
.ui-stagger-4 { animation-delay: 120ms; }
|
|
1878
|
+
.ui-stagger-5 { animation-delay: 150ms; }
|
|
1879
|
+
.ui-stagger-6 { animation-delay: 180ms; }
|
|
1880
|
+
|
|
1881
|
+
.ui-pressable {
|
|
1882
|
+
transition: transform var(--duration-fast) var(--ease-standard);
|
|
1883
|
+
}
|
|
1884
|
+
.ui-pressable:active {
|
|
1885
|
+
transform: scale(0.97);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.ui-lift-hover {
|
|
1889
|
+
transition: box-shadow var(--duration-normal) var(--ease-standard),
|
|
1890
|
+
transform var(--duration-normal) var(--ease-standard);
|
|
1891
|
+
}
|
|
1892
|
+
.ui-lift-hover:hover {
|
|
1893
|
+
transform: translateY(-1px);
|
|
1894
|
+
box-shadow: 0 4px 16px -6px rgb(0 0 0 / 0.12);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
.ui-dropdown-enter {
|
|
1898
|
+
animation: ui-slide-up-sm var(--duration-fast) var(--ease-decelerate) both;
|
|
1899
|
+
}
|
|
1900
|
+
.ui-dropdown-exit {
|
|
1901
|
+
animation: ui-slide-up-fade-out var(--duration-fast) var(--ease-standard) both;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
.ui-count-pop {
|
|
1905
|
+
animation: ui-count-pop var(--duration-normal) var(--ease-emphasized);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.ui-row-hover {
|
|
1909
|
+
transition: background-color var(--duration-fast) var(--ease-standard);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
.ui-tab-indicator {
|
|
1913
|
+
transition: color var(--duration-normal) var(--ease-standard),
|
|
1914
|
+
border-color var(--duration-normal) var(--ease-standard);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1918
|
+
.ui-fade-in,
|
|
1919
|
+
.ui-slide-up,
|
|
1920
|
+
.ui-slide-down,
|
|
1921
|
+
.ui-scale-in,
|
|
1922
|
+
.ui-dropdown-enter,
|
|
1923
|
+
.ui-dropdown-exit,
|
|
1924
|
+
.ui-count-pop,
|
|
1925
|
+
.ui-bell-notify {
|
|
1926
|
+
animation: none !important;
|
|
1927
|
+
opacity: 1 !important;
|
|
1928
|
+
transform: none !important;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.ui-pressable:active {
|
|
1932
|
+
transform: none;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
.ui-lift-hover:hover {
|
|
1936
|
+
transform: none;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
.ui-stagger-1,
|
|
1940
|
+
.ui-stagger-2,
|
|
1941
|
+
.ui-stagger-3,
|
|
1942
|
+
.ui-stagger-4,
|
|
1943
|
+
.ui-stagger-5,
|
|
1944
|
+
.ui-stagger-6 {
|
|
1945
|
+
animation-delay: 0ms !important;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/* Respect user's reduced motion preferences */
|
|
1950
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1951
|
+
*,
|
|
1952
|
+
*::before,
|
|
1953
|
+
*::after {
|
|
1954
|
+
animation-duration: 0.01ms !important;
|
|
1955
|
+
animation-iteration-count: 1 !important;
|
|
1956
|
+
transition-duration: 0.01ms !important;
|
|
1957
|
+
scroll-behavior: auto !important;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Metadata } from 'next';
|
|
1
|
+
import type { Metadata, Viewport } from 'next';
|
|
2
2
|
import { DM_Sans, Playfair_Display, JetBrains_Mono } from 'next/font/google';
|
|
3
3
|
import './globals.css';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
@@ -20,9 +20,17 @@ const monoFont = JetBrains_Mono({
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
export const metadata: Metadata = {
|
|
23
|
-
title: '
|
|
23
|
+
title: 'CRM Template',
|
|
24
24
|
description:
|
|
25
|
-
'
|
|
25
|
+
'Un CRM moderne pour gérer vos contacts et relations clients.',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const viewport: Viewport = {
|
|
29
|
+
colorScheme: 'light',
|
|
30
|
+
themeColor: [
|
|
31
|
+
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
|
32
|
+
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
|
|
33
|
+
],
|
|
26
34
|
};
|
|
27
35
|
|
|
28
36
|
export default function RootLayout({
|
|
@@ -4,7 +4,7 @@ import { useState, useEffect, useRef } from 'react';
|
|
|
4
4
|
import { MapPin, X } from 'lucide-react';
|
|
5
5
|
import { Spinner } from '@/components/skeleton';
|
|
6
6
|
import { searchAddress, type AddressFeature, extractAddressComponents } from '@/lib/address-api';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
7
|
+
import { cn, devToast } from '@/lib/utils';
|
|
8
8
|
import { useAppToast } from '@/contexts/app-toast-context';
|
|
9
9
|
|
|
10
10
|
interface AddressAutocompleteProps {
|
|
@@ -95,8 +95,7 @@ export default function AddressAutocomplete({
|
|
|
95
95
|
setSuggestions(result.features);
|
|
96
96
|
setShowSuggestions(true);
|
|
97
97
|
} catch (error) {
|
|
98
|
-
|
|
99
|
-
setSearchError(message);
|
|
98
|
+
setSearchError(devToast('Impossible de rechercher l\'adresse. Veuillez réessayer.', error));
|
|
100
99
|
setSuggestions([]);
|
|
101
100
|
} finally {
|
|
102
101
|
setLoading(false);
|
|
@@ -160,7 +159,7 @@ export default function AddressAutocomplete({
|
|
|
160
159
|
return (
|
|
161
160
|
<div className="relative">
|
|
162
161
|
<div className="relative">
|
|
163
|
-
<MapPin className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
|
162
|
+
<MapPin aria-hidden="true" className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
|
164
163
|
<input
|
|
165
164
|
ref={inputRef}
|
|
166
165
|
data-ui-skip-token="true"
|
|
@@ -176,9 +175,10 @@ export default function AddressAutocomplete({
|
|
|
176
175
|
onBlur={handleBlur}
|
|
177
176
|
placeholder={placeholder}
|
|
178
177
|
disabled={disabled}
|
|
178
|
+
autoComplete="street-address"
|
|
179
179
|
className={cn(
|
|
180
180
|
'w-full rounded-lg border border-gray-300 py-2 pr-10 pl-10 text-sm transition-colors',
|
|
181
|
-
'focus:
|
|
181
|
+
'focus-visible:border-blue-500 focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none',
|
|
182
182
|
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500',
|
|
183
183
|
className,
|
|
184
184
|
)}
|
|
@@ -201,6 +201,7 @@ export default function AddressAutocomplete({
|
|
|
201
201
|
<div
|
|
202
202
|
ref={suggestionsRef}
|
|
203
203
|
className="absolute z-50 mt-1 w-full rounded-lg border border-gray-200 bg-white shadow-lg"
|
|
204
|
+
aria-live="polite"
|
|
204
205
|
>
|
|
205
206
|
<ul className="max-h-60 overflow-y-auto py-1">
|
|
206
207
|
{suggestions.map((suggestion, index) => (
|
|
@@ -214,7 +215,7 @@ export default function AddressAutocomplete({
|
|
|
214
215
|
selectedIndex === index && 'bg-blue-50',
|
|
215
216
|
)}
|
|
216
217
|
>
|
|
217
|
-
<MapPin className="mt-1 h-4 w-4 shrink-0 text-gray-400" />
|
|
218
|
+
<MapPin aria-hidden="true" className="mt-1 h-4 w-4 shrink-0 text-gray-400" />
|
|
218
219
|
<div className="flex-1">
|
|
219
220
|
<p className="text-sm font-medium text-gray-900">
|
|
220
221
|
{suggestion.properties.name}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { AlertCircle } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
interface ConfigErrorAlertProps {
|
|
7
|
+
/** Message d'erreur à afficher */
|
|
8
|
+
message: string;
|
|
9
|
+
/** Lien vers la section de configuration (ex: /settings?section=system) */
|
|
10
|
+
configLink?: string;
|
|
11
|
+
/** Texte du lien de redirection */
|
|
12
|
+
linkLabel?: string;
|
|
13
|
+
/** ClassName additionnel pour le conteneur */
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Affiche un message d'erreur lorsqu'une action nécessite une configuration manquante.
|
|
19
|
+
* Inclut un lien cliquable vers la section de configuration appropriée.
|
|
20
|
+
*/
|
|
21
|
+
export function ConfigErrorAlert({
|
|
22
|
+
message,
|
|
23
|
+
configLink,
|
|
24
|
+
linkLabel = 'Configurer dans les paramètres',
|
|
25
|
+
className = '',
|
|
26
|
+
}: ConfigErrorAlertProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
role="alert"
|
|
30
|
+
className={`flex items-start gap-3 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 ${className}`}
|
|
31
|
+
>
|
|
32
|
+
<AlertCircle className="mt-0.5 h-5 w-5 shrink-0 text-red-600" />
|
|
33
|
+
<div className="min-w-0 flex-1">
|
|
34
|
+
<p className="font-medium">{message}</p>
|
|
35
|
+
{configLink && (
|
|
36
|
+
<Link
|
|
37
|
+
href={configLink}
|
|
38
|
+
className="mt-2 inline-flex items-center font-medium text-red-700 underline underline-offset-2 hover:text-red-900"
|
|
39
|
+
>
|
|
40
|
+
{linkLabel} →
|
|
41
|
+
</Link>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|