create-crm-tmp 1.1.3 → 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/.prettierignore +2 -0
- package/template/README.md +230 -115
- package/template/components.json +22 -0
- package/template/eslint.config.mjs +13 -0
- package/template/exemple-contacts.csv +54 -0
- package/template/next.config.ts +41 -1
- package/template/package.json +63 -15
- package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
- package/template/prisma/schema.prisma +311 -67
- package/template/src/app/(auth)/invite/[token]/page.tsx +28 -29
- package/template/src/app/(auth)/layout.tsx +1 -1
- package/template/src/app/(auth)/reset-password/complete/page.tsx +21 -27
- package/template/src/app/(auth)/reset-password/page.tsx +14 -10
- package/template/src/app/(auth)/reset-password/verify/page.tsx +14 -10
- package/template/src/app/(auth)/signin/page.tsx +34 -23
- package/template/src/app/(dashboard)/agenda/page.tsx +3655 -2357
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +10 -7
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +609 -338
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +11 -8
- package/template/src/app/(dashboard)/automatisation/page.tsx +463 -186
- package/template/src/app/(dashboard)/closing/page.tsx +517 -469
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6151 -4210
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +1702 -0
- package/template/src/app/(dashboard)/contacts/loading.tsx +13 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +4124 -2130
- package/template/src/app/(dashboard)/dashboard/page.tsx +119 -105
- package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
- package/template/src/app/(dashboard)/error.tsx +37 -0
- package/template/src/app/(dashboard)/layout.tsx +6 -2
- 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 +1773 -3362
- package/template/src/app/(dashboard)/templates/page.tsx +504 -303
- package/template/src/app/(dashboard)/users/list/page.tsx +364 -355
- 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 +169 -140
- package/template/src/app/api/agenda/google-events/route.ts +92 -0
- package/template/src/app/api/audit-logs/route.ts +1 -1
- package/template/src/app/api/auth/check-active/route.ts +3 -2
- 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/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 +129 -0
- package/template/src/app/api/companies/[id]/route.ts +194 -0
- package/template/src/app/api/companies/export/route.ts +206 -0
- package/template/src/app/api/companies/route.ts +196 -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 +55 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +20 -48
- package/template/src/app/api/contacts/[id]/files/route.ts +125 -186
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
- package/template/src/app/api/contacts/[id]/interactions/route.ts +45 -8
- package/template/src/app/api/contacts/[id]/kyc/route.ts +81 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +55 -29
- package/template/src/app/api/contacts/[id]/route.ts +184 -21
- package/template/src/app/api/contacts/[id]/send-email/route.ts +33 -11
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +67 -0
- package/template/src/app/api/contacts/export/route.ts +22 -31
- package/template/src/app/api/contacts/import/route.ts +77 -44
- package/template/src/app/api/contacts/import-preview/route.ts +139 -0
- package/template/src/app/api/contacts/origins/route.ts +63 -0
- package/template/src/app/api/contacts/route.ts +322 -57
- package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
- package/template/src/app/api/dashboard/stats/route.ts +9 -292
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +0 -3
- package/template/src/app/api/dashboard/widgets/route.ts +19 -19
- 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 +28 -542
- package/template/src/app/api/invite/complete/route.ts +20 -23
- 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 +165 -39
- package/template/src/app/api/reminders/state/route.ts +164 -0
- package/template/src/app/api/reset-password/complete/route.ts +11 -13
- 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 +25 -47
- 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 +34 -23
- 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 +48 -23
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +56 -32
- package/template/src/app/api/settings/google-sheet/preview/route.ts +110 -0
- package/template/src/app/api/settings/google-sheet/route.ts +34 -23
- 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 +20 -24
- package/template/src/app/api/settings/meta-leads/route.ts +34 -25
- package/template/src/app/api/settings/smtp/route.ts +53 -6
- package/template/src/app/api/settings/statuses/[id]/route.ts +29 -32
- 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 +36 -13
- package/template/src/app/api/tasks/[id]/route.ts +357 -145
- package/template/src/app/api/tasks/meet/route.ts +37 -26
- package/template/src/app/api/tasks/route.ts +201 -96
- 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 +22 -16
- 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/list/route.ts +57 -19
- package/template/src/app/api/users/route.ts +89 -34
- package/template/src/app/api/webhooks/google-ads/route.ts +40 -1
- package/template/src/app/api/webhooks/meta-leads/route.ts +38 -1
- package/template/src/app/api/workflows/[id]/route.ts +29 -6
- package/template/src/app/api/workflows/process/route.ts +505 -170
- package/template/src/app/api/workflows/route.ts +42 -4
- package/template/src/app/globals.css +512 -32
- package/template/src/app/layout.tsx +28 -9
- package/template/src/app/page.tsx +37 -7
- package/template/src/components/address-autocomplete.tsx +233 -0
- package/template/src/components/config-error-alert.tsx +46 -0
- package/template/src/components/contacts/filter-bar.tsx +190 -0
- package/template/src/components/contacts/filter-builder.tsx +574 -0
- package/template/src/components/contacts/save-view-dialog.tsx +160 -0
- package/template/src/components/contacts/views-tab-bar.tsx +449 -0
- package/template/src/components/dashboard/activity-chart.tsx +6 -1
- package/template/src/components/dashboard/add-widget-dialog.tsx +13 -17
- package/template/src/components/dashboard/color-picker.tsx +7 -8
- package/template/src/components/dashboard/recent-activity.tsx +2 -5
- package/template/src/components/dashboard/stat-card.tsx +1 -3
- package/template/src/components/dashboard/status-distribution-chart.tsx +0 -1
- package/template/src/components/dashboard/top-contacts-list.tsx +7 -13
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +2 -5
- package/template/src/components/dashboard/widget-wrapper.tsx +3 -6
- package/template/src/components/date-picker.tsx +399 -0
- package/template/src/components/editor/upload-editor-image.ts +42 -0
- package/template/src/components/editor.tsx +188 -35
- package/template/src/components/email-template.tsx +4 -2
- package/template/src/components/global-search.tsx +360 -0
- package/template/src/components/header.tsx +200 -107
- 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 +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/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 +117 -100
- package/template/src/components/skeleton.tsx +128 -45
- 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 +71 -0
- 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-banner.tsx +1 -1
- package/template/src/components/view-as-modal.tsx +30 -19
- package/template/src/config/nav-pages.ts +108 -0
- package/template/src/contexts/app-toast-context.tsx +362 -0
- package/template/src/contexts/dashboard-theme-context.tsx +2 -7
- package/template/src/contexts/sidebar-context.tsx +27 -53
- package/template/src/contexts/task-reminder-context.tsx +134 -160
- package/template/src/contexts/view-as-context.tsx +32 -10
- 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/hooks/useIntegrationNotifications.ts +49 -0
- package/template/src/lib/address-api.ts +155 -0
- package/template/src/lib/auth.ts +8 -1
- package/template/src/lib/cache.ts +73 -0
- package/template/src/lib/check-permission.ts +12 -177
- 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 +24 -22
- package/template/src/lib/contact-view-filters.ts +301 -0
- package/template/src/lib/contacts-list-url.ts +190 -0
- package/template/src/lib/dashboard-stats.ts +282 -0
- package/template/src/lib/dashboard-themes.ts +0 -5
- package/template/src/lib/date-utils.ts +176 -0
- package/template/src/lib/default-widgets.ts +0 -2
- 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/get-auth-user.ts +25 -0
- package/template/src/lib/google-calendar-agenda.ts +201 -0
- package/template/src/lib/google-calendar.ts +309 -17
- package/template/src/lib/google-fetch.ts +63 -0
- 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/local-storage.ts +34 -0
- package/template/src/lib/permissions.ts +268 -40
- package/template/src/lib/prisma.ts +15 -12
- 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/roles.ts +12 -15
- package/template/src/lib/supabase-storage.ts +113 -0
- package/template/src/lib/template-variables.ts +204 -29
- package/template/src/lib/utils.ts +71 -11
- package/template/src/lib/widget-registry.ts +0 -4
- package/template/src/lib/workflow-executor.ts +391 -228
- package/template/src/proxy.ts +35 -73
- package/template/src/types/contact-views.ts +351 -0
- package/template/vercel.json +5 -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/20260226093949_fix_cascade_on_user_delete/migration.sql +0 -69
- package/template/src/app/(dashboard)/users/layout.tsx +0 -30
- package/template/src/lib/google-drive.ts +0 -380
|
@@ -64,9 +64,6 @@ import {
|
|
|
64
64
|
Code,
|
|
65
65
|
Terminal,
|
|
66
66
|
Table as TableIcon,
|
|
67
|
-
FileCode,
|
|
68
|
-
Eye,
|
|
69
|
-
Pencil,
|
|
70
67
|
Type,
|
|
71
68
|
Quote,
|
|
72
69
|
Indent,
|
|
@@ -74,8 +71,16 @@ import {
|
|
|
74
71
|
} from 'lucide-react';
|
|
75
72
|
import { createPortal } from 'react-dom';
|
|
76
73
|
import { commandsToCommandPaletteItems, registerKeyboardShortcuts } from './ui/commands';
|
|
77
|
-
import { Select,
|
|
74
|
+
import { Select, Dialog } from './ui/components';
|
|
78
75
|
import { defaultTheme } from './ui/theme';
|
|
76
|
+
import { useAppToast } from '@/contexts/app-toast-context';
|
|
77
|
+
import { devToast } from '@/lib/utils';
|
|
78
|
+
import { assertImageFileWithinLimit } from '@/lib/editor-image-limits';
|
|
79
|
+
import { uploadEditorImage } from './editor/upload-editor-image';
|
|
80
|
+
import {
|
|
81
|
+
applyOrderedImageLayoutAfterImport,
|
|
82
|
+
mergeImgDimensionAttrsIntoStyle,
|
|
83
|
+
} from '@/lib/editor-html-image-dimensions';
|
|
79
84
|
|
|
80
85
|
type TableConfig = {
|
|
81
86
|
rows?: number;
|
|
@@ -83,8 +88,36 @@ type TableConfig = {
|
|
|
83
88
|
includeHeaders?: boolean;
|
|
84
89
|
};
|
|
85
90
|
|
|
91
|
+
function fileToDataUrl(file: File): Promise<string> {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const reader = new FileReader();
|
|
94
|
+
reader.onload = () => {
|
|
95
|
+
const result = reader.result;
|
|
96
|
+
if (typeof result === 'string') {
|
|
97
|
+
resolve(result);
|
|
98
|
+
} else {
|
|
99
|
+
reject(new Error('Impossible de convertir le fichier en data URL'));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
reader.onerror = () =>
|
|
103
|
+
reject(reader.error ?? new Error('Erreur de lecture du fichier image'));
|
|
104
|
+
reader.readAsDataURL(file);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function imageFileToDataUrl(file: File, maxImageBytes?: number): Promise<string> {
|
|
109
|
+
if (maxImageBytes !== undefined) {
|
|
110
|
+
assertImageFileWithinLimit(file, maxImageBytes);
|
|
111
|
+
}
|
|
112
|
+
return fileToDataUrl(file);
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
// Create markdown extension instance
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
117
|
+
// @ts-ignore - Lexical duplicate types between packages
|
|
87
118
|
const markdownExt = new MarkdownExtension().configure({
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
120
|
+
// @ts-ignore - Lexical duplicate types between packages
|
|
88
121
|
customTransformers: ALL_MARKDOWN_TRANSFORMERS,
|
|
89
122
|
});
|
|
90
123
|
|
|
@@ -139,16 +172,21 @@ export interface DefaultTemplateRef {
|
|
|
139
172
|
}
|
|
140
173
|
|
|
141
174
|
// Hook for image handling logic
|
|
142
|
-
function useImageHandlers(
|
|
175
|
+
function useImageHandlers(
|
|
176
|
+
commands: EditorCommands,
|
|
177
|
+
editor: LexicalEditor | null,
|
|
178
|
+
maxImageBytes?: number,
|
|
179
|
+
) {
|
|
143
180
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
181
|
+
const toast = useAppToast();
|
|
144
182
|
|
|
145
183
|
const handlers = useMemo(
|
|
146
184
|
() => ({
|
|
147
185
|
insertFromUrl: () => {
|
|
148
|
-
const src = prompt('
|
|
186
|
+
const src = prompt("URL de l'image (https://…) :");
|
|
149
187
|
if (!src) return;
|
|
150
|
-
const alt = prompt('
|
|
151
|
-
const caption = prompt('
|
|
188
|
+
const alt = prompt('Texte alternatif (accessibilité) :') || '';
|
|
189
|
+
const caption = prompt('Légende (optionnel) :') || undefined;
|
|
152
190
|
commands.insertImage({ src, alt, caption });
|
|
153
191
|
},
|
|
154
192
|
insertFromFile: () => fileInputRef.current?.click(),
|
|
@@ -160,11 +198,19 @@ function useImageHandlers(commands: EditorCommands, editor: LexicalEditor | null
|
|
|
160
198
|
try {
|
|
161
199
|
src = await imageExtension.config.uploadHandler(file);
|
|
162
200
|
} catch (error) {
|
|
163
|
-
|
|
201
|
+
// Toast déjà affiché par uploadHandler (signature / collage)
|
|
202
|
+
console.error('Failed to upload image:', error);
|
|
203
|
+
e.target.value = '';
|
|
164
204
|
return;
|
|
165
205
|
}
|
|
166
206
|
} else {
|
|
167
|
-
|
|
207
|
+
try {
|
|
208
|
+
src = await imageFileToDataUrl(file, maxImageBytes);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
toast.error(devToast("Impossible d'ajouter cette image.", error));
|
|
211
|
+
e.target.value = '';
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
168
214
|
}
|
|
169
215
|
commands.insertImage({ src, alt: file.name, file });
|
|
170
216
|
e.target.value = '';
|
|
@@ -177,7 +223,7 @@ function useImageHandlers(commands: EditorCommands, editor: LexicalEditor | null
|
|
|
177
223
|
commands.setImageCaption(newCaption);
|
|
178
224
|
},
|
|
179
225
|
}),
|
|
180
|
-
[commands],
|
|
226
|
+
[commands, maxImageBytes, toast],
|
|
181
227
|
);
|
|
182
228
|
|
|
183
229
|
return { handlers, fileInputRef };
|
|
@@ -264,6 +310,10 @@ function FloatingToolbarRenderer() {
|
|
|
264
310
|
>
|
|
265
311
|
<Type size={14} />
|
|
266
312
|
</button>
|
|
313
|
+
<div className="bg-border mx-1 h-6 w-px" />
|
|
314
|
+
<span className="text-muted-foreground px-1.5 text-[11px] leading-6 whitespace-nowrap">
|
|
315
|
+
Shift + glisser pour garder le ratio
|
|
316
|
+
</span>
|
|
267
317
|
</>
|
|
268
318
|
) : (
|
|
269
319
|
<>
|
|
@@ -402,15 +452,15 @@ function Toolbar({
|
|
|
402
452
|
commands,
|
|
403
453
|
hasExtension,
|
|
404
454
|
activeStates,
|
|
455
|
+
maxImageBytes,
|
|
405
456
|
}: {
|
|
406
457
|
commands: EditorCommands;
|
|
407
458
|
hasExtension: (name: ExtensionNames) => boolean;
|
|
408
459
|
activeStates: EditorStateQueries;
|
|
460
|
+
maxImageBytes?: number;
|
|
409
461
|
}) {
|
|
410
462
|
const { lexical: editor } = useEditor();
|
|
411
|
-
const { handlers, fileInputRef } = useImageHandlers(commands, editor);
|
|
412
|
-
const [showImageDropdown, setShowImageDropdown] = useState(false);
|
|
413
|
-
const [showAlignDropdown, setShowAlignDropdown] = useState(false);
|
|
463
|
+
const { handlers, fileInputRef } = useImageHandlers(commands, editor, maxImageBytes);
|
|
414
464
|
const [showTableDialog, setShowTableDialog] = useState(false);
|
|
415
465
|
const [tableConfig, setTableConfig] = useState<TableConfig>({
|
|
416
466
|
rows: 3,
|
|
@@ -585,6 +635,35 @@ function Toolbar({
|
|
|
585
635
|
</div>
|
|
586
636
|
)}
|
|
587
637
|
|
|
638
|
+
{/* Image : fichier local ou URL (handlers étaient définis mais non exposés dans la barre) */}
|
|
639
|
+
{hasExtension('image') && (
|
|
640
|
+
<div className="lexkit-toolbar-section">
|
|
641
|
+
<input
|
|
642
|
+
ref={fileInputRef}
|
|
643
|
+
type="file"
|
|
644
|
+
accept="image/*"
|
|
645
|
+
className="hidden"
|
|
646
|
+
onChange={handlers.handleUpload}
|
|
647
|
+
/>
|
|
648
|
+
<button
|
|
649
|
+
type="button"
|
|
650
|
+
onClick={() => handlers.insertFromFile()}
|
|
651
|
+
className="lexkit-toolbar-button"
|
|
652
|
+
title="Insérer une image depuis un fichier"
|
|
653
|
+
>
|
|
654
|
+
<Upload size={16} />
|
|
655
|
+
</button>
|
|
656
|
+
<button
|
|
657
|
+
type="button"
|
|
658
|
+
onClick={() => handlers.insertFromUrl()}
|
|
659
|
+
className="lexkit-toolbar-button"
|
|
660
|
+
title="Insérer une image depuis une URL"
|
|
661
|
+
>
|
|
662
|
+
<ImageIcon size={16} />
|
|
663
|
+
</button>
|
|
664
|
+
</div>
|
|
665
|
+
)}
|
|
666
|
+
|
|
588
667
|
{/* Table */}
|
|
589
668
|
{hasExtension('table') && (
|
|
590
669
|
<div className="lexkit-toolbar-section">
|
|
@@ -635,13 +714,18 @@ function Toolbar({
|
|
|
635
714
|
<label htmlFor="table-rows">Rows:</label>
|
|
636
715
|
<input
|
|
637
716
|
id="table-rows"
|
|
638
|
-
type="
|
|
639
|
-
|
|
640
|
-
max="20"
|
|
717
|
+
type="text"
|
|
718
|
+
inputMode="numeric"
|
|
641
719
|
value={tableConfig.rows}
|
|
642
|
-
onChange={(e) =>
|
|
643
|
-
|
|
644
|
-
|
|
720
|
+
onChange={(e) => {
|
|
721
|
+
const value = e.target.value;
|
|
722
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
723
|
+
const num = parseInt(value) || 1;
|
|
724
|
+
if (num >= 1 && num <= 20) {
|
|
725
|
+
setTableConfig((prev) => ({ ...prev, rows: num }));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}}
|
|
645
729
|
className="lexkit-input"
|
|
646
730
|
/>
|
|
647
731
|
</div>
|
|
@@ -649,13 +733,18 @@ function Toolbar({
|
|
|
649
733
|
<label htmlFor="table-columns">Columns:</label>
|
|
650
734
|
<input
|
|
651
735
|
id="table-columns"
|
|
652
|
-
type="
|
|
653
|
-
|
|
654
|
-
max="20"
|
|
736
|
+
type="text"
|
|
737
|
+
inputMode="numeric"
|
|
655
738
|
value={tableConfig.columns}
|
|
656
|
-
onChange={(e) =>
|
|
657
|
-
|
|
658
|
-
|
|
739
|
+
onChange={(e) => {
|
|
740
|
+
const value = e.target.value;
|
|
741
|
+
if (value === '' || /^\d+$/.test(value)) {
|
|
742
|
+
const num = parseInt(value) || 1;
|
|
743
|
+
if (num >= 1 && num <= 20) {
|
|
744
|
+
setTableConfig((prev) => ({ ...prev, columns: num }));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}}
|
|
659
748
|
className="lexkit-input"
|
|
660
749
|
/>
|
|
661
750
|
</div>
|
|
@@ -708,11 +797,13 @@ function EditorContent({
|
|
|
708
797
|
isDark,
|
|
709
798
|
toggleTheme,
|
|
710
799
|
onReady,
|
|
800
|
+
maxImageBytes,
|
|
711
801
|
}: {
|
|
712
802
|
className?: string;
|
|
713
803
|
isDark: boolean;
|
|
714
804
|
toggleTheme: () => void;
|
|
715
805
|
onReady?: (methods: DefaultTemplateRef) => void;
|
|
806
|
+
maxImageBytes?: number;
|
|
716
807
|
}) {
|
|
717
808
|
const { commands, hasExtension, activeStates, lexical: editor } = useEditor();
|
|
718
809
|
const commandsRef = useRef<EditorCommands>(commands);
|
|
@@ -738,10 +829,46 @@ function EditorContent({
|
|
|
738
829
|
},
|
|
739
830
|
injectHTML: (content: string) => {
|
|
740
831
|
setTimeout(() => {
|
|
741
|
-
if (editor)
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
832
|
+
if (!editor) return;
|
|
833
|
+
const safeContent = typeof content === 'string' ? content.trim() : '';
|
|
834
|
+
const containsHtmlTag = /<[^>]+>/.test(safeContent);
|
|
835
|
+
const plainParagraph = `<p>${safeContent
|
|
836
|
+
.replaceAll('&', '&')
|
|
837
|
+
.replaceAll('<', '<')
|
|
838
|
+
.replaceAll('>', '>')
|
|
839
|
+
.replaceAll('"', '"')
|
|
840
|
+
.replaceAll("'", ''')
|
|
841
|
+
.replaceAll('\n', '<br />')}</p>`;
|
|
842
|
+
|
|
843
|
+
const contentForImport = !safeContent
|
|
844
|
+
? '<p></p>'
|
|
845
|
+
: containsHtmlTag
|
|
846
|
+
? mergeImgDimensionAttrsIntoStyle(safeContent)
|
|
847
|
+
: plainParagraph;
|
|
848
|
+
|
|
849
|
+
const afterImport = () => {
|
|
850
|
+
if (!containsHtmlTag || !contentForImport.includes('<img')) return;
|
|
851
|
+
applyOrderedImageLayoutAfterImport(editor, contentForImport);
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
const result = commandsRef.current.importFromHTML(contentForImport, {
|
|
856
|
+
preventFocus: true,
|
|
857
|
+
}) as Promise<void> | void;
|
|
858
|
+
|
|
859
|
+
if (result && typeof (result as Promise<void>).then === 'function') {
|
|
860
|
+
(result as Promise<void>)
|
|
861
|
+
.then(afterImport)
|
|
862
|
+
.catch((error) => {
|
|
863
|
+
console.error('Erreur import HTML éditeur, fallback contenu vide:', error);
|
|
864
|
+
void commandsRef.current.importFromHTML('<p></p>', { preventFocus: true });
|
|
865
|
+
});
|
|
866
|
+
} else {
|
|
867
|
+
afterImport();
|
|
868
|
+
}
|
|
869
|
+
} catch (error) {
|
|
870
|
+
console.error('Erreur injectHTML éditeur:', error);
|
|
871
|
+
void commandsRef.current.importFromHTML('<p></p>', { preventFocus: true });
|
|
745
872
|
}
|
|
746
873
|
}, 100);
|
|
747
874
|
},
|
|
@@ -760,7 +887,14 @@ function EditorContent({
|
|
|
760
887
|
}
|
|
761
888
|
},
|
|
762
889
|
getMarkdown: () => commandsRef.current.exportToMarkdown(),
|
|
763
|
-
getHTML: () =>
|
|
890
|
+
getHTML: () => {
|
|
891
|
+
try {
|
|
892
|
+
const raw = commandsRef.current.exportToHTML() || '';
|
|
893
|
+
return mergeImgDimensionAttrsIntoStyle(raw);
|
|
894
|
+
} catch {
|
|
895
|
+
return commandsRef.current.exportToHTML() || '';
|
|
896
|
+
}
|
|
897
|
+
},
|
|
764
898
|
}),
|
|
765
899
|
[editor],
|
|
766
900
|
);
|
|
@@ -789,7 +923,12 @@ function EditorContent({
|
|
|
789
923
|
return (
|
|
790
924
|
<>
|
|
791
925
|
<div className="lexkit-editor-header">
|
|
792
|
-
<Toolbar
|
|
926
|
+
<Toolbar
|
|
927
|
+
commands={commands}
|
|
928
|
+
hasExtension={hasExtension}
|
|
929
|
+
activeStates={activeStates}
|
|
930
|
+
maxImageBytes={maxImageBytes}
|
|
931
|
+
/>
|
|
793
932
|
</div>
|
|
794
933
|
<div className="lexkit-editor">
|
|
795
934
|
<div className="flex flex-1 flex-col" style={{ display: 'flex' }}>
|
|
@@ -809,23 +948,36 @@ function EditorContent({
|
|
|
809
948
|
interface DefaultTemplateProps {
|
|
810
949
|
className?: string;
|
|
811
950
|
onReady?: (methods: DefaultTemplateRef) => void;
|
|
951
|
+
/** Si défini, refuse les images plus lourdes (fichier + collage). */
|
|
952
|
+
maxImageBytes?: number;
|
|
812
953
|
}
|
|
813
954
|
|
|
814
955
|
export const Editor = forwardRef<DefaultTemplateRef, DefaultTemplateProps>(
|
|
815
|
-
({ className, onReady }, ref) => {
|
|
956
|
+
({ className, onReady, maxImageBytes }, ref) => {
|
|
816
957
|
const [editorTheme, setEditorTheme] = useState<'light' | 'dark'>('light');
|
|
958
|
+
const toast = useAppToast();
|
|
817
959
|
|
|
818
960
|
const isDark = editorTheme === 'dark';
|
|
819
961
|
|
|
820
962
|
useEffect(() => {
|
|
821
963
|
imageExtension.configure({
|
|
822
|
-
uploadHandler: async (file: File) =>
|
|
964
|
+
uploadHandler: async (file: File) => {
|
|
965
|
+
if (maxImageBytes !== undefined) {
|
|
966
|
+
assertImageFileWithinLimit(file, maxImageBytes);
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
return await uploadEditorImage(file);
|
|
970
|
+
} catch {
|
|
971
|
+
// Fallback: data URL (base64)
|
|
972
|
+
return imageFileToDataUrl(file, maxImageBytes);
|
|
973
|
+
}
|
|
974
|
+
},
|
|
823
975
|
defaultAlignment: 'center',
|
|
824
976
|
resizable: true,
|
|
825
977
|
pasteListener: { insert: true, replace: true },
|
|
826
978
|
debug: false,
|
|
827
979
|
});
|
|
828
|
-
}, []);
|
|
980
|
+
}, [maxImageBytes, toast]);
|
|
829
981
|
|
|
830
982
|
const toggleTheme = () => setEditorTheme(isDark ? 'light' : 'dark');
|
|
831
983
|
|
|
@@ -846,6 +998,7 @@ export const Editor = forwardRef<DefaultTemplateRef, DefaultTemplateProps>(
|
|
|
846
998
|
isDark={isDark}
|
|
847
999
|
toggleTheme={toggleTheme}
|
|
848
1000
|
onReady={handleReady}
|
|
1001
|
+
maxImageBytes={maxImageBytes}
|
|
849
1002
|
/>
|
|
850
1003
|
</Provider>
|
|
851
1004
|
</div>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sanitizeEmailHtml } from '@/lib/email-html-sanitize';
|
|
2
|
+
|
|
1
3
|
interface EmailTemplateProps {
|
|
2
4
|
firstName: string;
|
|
3
5
|
signature?: string | null;
|
|
@@ -7,7 +9,7 @@ export function EmailTemplate({ firstName, signature }: EmailTemplateProps) {
|
|
|
7
9
|
return (
|
|
8
10
|
<div
|
|
9
11
|
style={{
|
|
10
|
-
fontFamily: '
|
|
12
|
+
fontFamily: '"Segoe UI", "Helvetica Neue", sans-serif',
|
|
11
13
|
padding: '20px',
|
|
12
14
|
maxWidth: '600px',
|
|
13
15
|
margin: '0 auto',
|
|
@@ -27,7 +29,7 @@ export function EmailTemplate({ firstName, signature }: EmailTemplateProps) {
|
|
|
27
29
|
fontSize: '14px',
|
|
28
30
|
lineHeight: '1.6',
|
|
29
31
|
}}
|
|
30
|
-
dangerouslySetInnerHTML={{ __html: signature }}
|
|
32
|
+
dangerouslySetInnerHTML={{ __html: sanitizeEmailHtml(signature) }}
|
|
31
33
|
/>
|
|
32
34
|
)}
|
|
33
35
|
</div>
|