create-crm-tmp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/bin/create-crm-tmp.js +93 -0
  2. package/package.json +25 -0
  3. package/template/.prettierignore +33 -0
  4. package/template/.prettierrc.json +25 -0
  5. package/template/README.md +173 -0
  6. package/template/eslint.config.mjs +18 -0
  7. package/template/exemple-contacts.csv +11 -0
  8. package/template/next.config.ts +8 -0
  9. package/template/package.json +64 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
  12. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
  13. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
  14. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
  15. package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
  16. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
  17. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
  18. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
  19. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
  20. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
  21. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
  22. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
  23. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
  24. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
  25. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
  26. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
  27. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
  28. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
  29. package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
  30. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
  31. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
  32. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
  33. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
  34. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
  35. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
  36. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
  37. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
  38. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
  39. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
  40. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
  41. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
  42. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
  43. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
  44. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
  45. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
  46. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
  47. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
  48. package/template/prisma/migrations/migration_lock.toml +3 -0
  49. package/template/prisma/schema.prisma +582 -0
  50. package/template/prisma.config.ts +14 -0
  51. package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
  52. package/template/src/app/(auth)/layout.tsx +3 -0
  53. package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
  54. package/template/src/app/(auth)/reset-password/page.tsx +146 -0
  55. package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
  56. package/template/src/app/(auth)/signin/page.tsx +166 -0
  57. package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
  58. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
  59. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
  60. package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
  61. package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
  62. package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
  63. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
  64. package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
  65. package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
  66. package/template/src/app/(dashboard)/layout.tsx +30 -0
  67. package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
  68. package/template/src/app/(dashboard)/templates/page.tsx +567 -0
  69. package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
  70. package/template/src/app/(dashboard)/users/page.tsx +457 -0
  71. package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
  72. package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
  73. package/template/src/app/api/audit-logs/route.ts +57 -0
  74. package/template/src/app/api/auth/[...all]/route.ts +4 -0
  75. package/template/src/app/api/auth/check-active/route.ts +31 -0
  76. package/template/src/app/api/auth/google/callback/route.ts +94 -0
  77. package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
  78. package/template/src/app/api/auth/google/route.ts +34 -0
  79. package/template/src/app/api/auth/google/status/route.ts +32 -0
  80. package/template/src/app/api/closing-reasons/route.ts +27 -0
  81. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
  82. package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
  83. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
  84. package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
  85. package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
  86. package/template/src/app/api/contacts/[id]/route.ts +322 -0
  87. package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
  88. package/template/src/app/api/contacts/export/route.ts +270 -0
  89. package/template/src/app/api/contacts/import/route.ts +381 -0
  90. package/template/src/app/api/contacts/route.ts +283 -0
  91. package/template/src/app/api/dashboard/stats/route.ts +299 -0
  92. package/template/src/app/api/email/track/[id]/route.ts +68 -0
  93. package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
  94. package/template/src/app/api/invite/complete/route.ts +88 -0
  95. package/template/src/app/api/invite/validate/route.ts +55 -0
  96. package/template/src/app/api/reminders/route.ts +95 -0
  97. package/template/src/app/api/reset-password/complete/route.ts +73 -0
  98. package/template/src/app/api/reset-password/request/route.ts +84 -0
  99. package/template/src/app/api/reset-password/validate/route.ts +49 -0
  100. package/template/src/app/api/reset-password/verify/route.ts +74 -0
  101. package/template/src/app/api/roles/[id]/route.ts +183 -0
  102. package/template/src/app/api/roles/route.ts +140 -0
  103. package/template/src/app/api/send/route.ts +282 -0
  104. package/template/src/app/api/settings/change-password/route.ts +95 -0
  105. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
  106. package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
  107. package/template/src/app/api/settings/company/route.ts +121 -0
  108. package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
  109. package/template/src/app/api/settings/google-ads/route.ts +122 -0
  110. package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
  111. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
  112. package/template/src/app/api/settings/google-sheet/route.ts +254 -0
  113. package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
  114. package/template/src/app/api/settings/meta-leads/route.ts +132 -0
  115. package/template/src/app/api/settings/profile/route.ts +42 -0
  116. package/template/src/app/api/settings/smtp/route.ts +130 -0
  117. package/template/src/app/api/settings/smtp/test/route.ts +121 -0
  118. package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
  119. package/template/src/app/api/settings/statuses/route.ts +83 -0
  120. package/template/src/app/api/statuses/route.ts +25 -0
  121. package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
  122. package/template/src/app/api/tasks/[id]/route.ts +728 -0
  123. package/template/src/app/api/tasks/meet/route.ts +240 -0
  124. package/template/src/app/api/tasks/route.ts +417 -0
  125. package/template/src/app/api/templates/[id]/route.ts +140 -0
  126. package/template/src/app/api/templates/route.ts +91 -0
  127. package/template/src/app/api/users/[id]/route.ts +168 -0
  128. package/template/src/app/api/users/list/route.ts +45 -0
  129. package/template/src/app/api/users/me/route.ts +48 -0
  130. package/template/src/app/api/users/route.ts +250 -0
  131. package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
  132. package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
  133. package/template/src/app/api/workflows/[id]/route.ts +192 -0
  134. package/template/src/app/api/workflows/process/route.ts +293 -0
  135. package/template/src/app/api/workflows/route.ts +124 -0
  136. package/template/src/app/favicon.ico +0 -0
  137. package/template/src/app/globals.css +1416 -0
  138. package/template/src/app/layout.tsx +31 -0
  139. package/template/src/app/page.tsx +32 -0
  140. package/template/src/components/dashboard/activity-chart.tsx +67 -0
  141. package/template/src/components/dashboard/contacts-chart.tsx +63 -0
  142. package/template/src/components/dashboard/recent-activity.tsx +164 -0
  143. package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
  144. package/template/src/components/dashboard/stat-card.tsx +61 -0
  145. package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
  146. package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
  147. package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
  148. package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
  149. package/template/src/components/editor.tsx +856 -0
  150. package/template/src/components/email-template.tsx +35 -0
  151. package/template/src/components/header.tsx +320 -0
  152. package/template/src/components/invitation-email-template.tsx +79 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +120 -0
  154. package/template/src/components/meet-confirmation-email-template.tsx +156 -0
  155. package/template/src/components/meet-update-email-template.tsx +209 -0
  156. package/template/src/components/page-header.tsx +61 -0
  157. package/template/src/components/reset-password-email-template.tsx +79 -0
  158. package/template/src/components/sidebar.tsx +294 -0
  159. package/template/src/components/skeleton.tsx +380 -0
  160. package/template/src/components/ui/commands.tsx +396 -0
  161. package/template/src/components/ui/components.tsx +150 -0
  162. package/template/src/components/ui/theme.tsx +5 -0
  163. package/template/src/components/view-as-banner.tsx +45 -0
  164. package/template/src/components/view-as-modal.tsx +186 -0
  165. package/template/src/contexts/mobile-menu-context.tsx +31 -0
  166. package/template/src/contexts/sidebar-context.tsx +107 -0
  167. package/template/src/contexts/task-reminder-context.tsx +239 -0
  168. package/template/src/contexts/view-as-context.tsx +84 -0
  169. package/template/src/hooks/use-user-role.ts +82 -0
  170. package/template/src/lib/audit-log.ts +45 -0
  171. package/template/src/lib/auth-client.ts +16 -0
  172. package/template/src/lib/auth.ts +35 -0
  173. package/template/src/lib/check-permission.ts +193 -0
  174. package/template/src/lib/contact-duplicate.ts +112 -0
  175. package/template/src/lib/contact-interactions.ts +371 -0
  176. package/template/src/lib/encryption.ts +99 -0
  177. package/template/src/lib/google-calendar.ts +300 -0
  178. package/template/src/lib/google-drive.ts +372 -0
  179. package/template/src/lib/permissions.ts +412 -0
  180. package/template/src/lib/prisma.ts +32 -0
  181. package/template/src/lib/roles.ts +120 -0
  182. package/template/src/lib/template-variables.ts +76 -0
  183. package/template/src/lib/utils.ts +46 -0
  184. package/template/src/lib/workflow-executor.ts +482 -0
  185. package/template/src/proxy.ts +91 -0
  186. package/template/tsconfig.json +34 -0
  187. package/template/vercel.json +8 -0
@@ -0,0 +1,396 @@
1
+ import { BaseCommands, ExtractCommands, CommandPaletteItem } from '@lexkit/editor';
2
+ import { extensions } from '../editor';
3
+
4
+ // Extract the commands type from our extensions
5
+ type EditorCommands = BaseCommands & ExtractCommands<typeof extensions>;
6
+
7
+ /**
8
+ * Keyboard shortcut configuration
9
+ */
10
+ export type KeyboardShortcut = {
11
+ key: string;
12
+ metaKey?: boolean;
13
+ ctrlKey?: boolean;
14
+ shiftKey?: boolean;
15
+ altKey?: boolean;
16
+ preventDefault?: boolean;
17
+ };
18
+
19
+ /**
20
+ * Command configuration with keyboard shortcuts
21
+ */
22
+ export type CommandConfig = {
23
+ id: string;
24
+ label: string;
25
+ description?: string;
26
+ category: string;
27
+ action: (commands: EditorCommands) => void;
28
+ shortcuts?: KeyboardShortcut[];
29
+ keywords?: string[];
30
+ icon?: string;
31
+ condition?: (commands: EditorCommands) => boolean; // Only show if condition is true
32
+ };
33
+
34
+ /**
35
+ * Generate all available commands based on the extensions
36
+ */
37
+ export function generateCommands(): CommandConfig[] {
38
+ const commands: CommandConfig[] = [
39
+ // Text Formatting Commands
40
+ {
41
+ id: 'format.bold',
42
+ label: 'Toggle Bold',
43
+ description: 'Make text bold or remove bold formatting',
44
+ category: 'Format',
45
+ action: (commands) => commands.toggleBold(),
46
+ shortcuts: [{ key: 'b', ctrlKey: true }],
47
+ keywords: ['bold', 'strong', 'format'],
48
+ icon: 'Bold',
49
+ },
50
+ {
51
+ id: 'format.italic',
52
+ label: 'Toggle Italic',
53
+ description: 'Make text italic or remove italic formatting',
54
+ category: 'Format',
55
+ action: (commands) => commands.toggleItalic(),
56
+ shortcuts: [{ key: 'i', ctrlKey: true }],
57
+ keywords: ['italic', 'emphasis', 'format'],
58
+ icon: 'Italic',
59
+ },
60
+ {
61
+ id: 'format.underline',
62
+ label: 'Toggle Underline',
63
+ description: 'Add or remove underline formatting',
64
+ category: 'Format',
65
+ action: (commands) => commands.toggleUnderline(),
66
+ shortcuts: [{ key: 'u', ctrlKey: true }],
67
+ keywords: ['underline', 'format'],
68
+ icon: 'Underline',
69
+ },
70
+ {
71
+ id: 'format.strikethrough',
72
+ label: 'Toggle Strikethrough',
73
+ description: 'Add or remove strikethrough formatting',
74
+ category: 'Format',
75
+ action: (commands) => commands.toggleStrikethrough(),
76
+ shortcuts: [{ key: 'd', ctrlKey: true, shiftKey: true }],
77
+ keywords: ['strikethrough', 'strike', 'format'],
78
+ icon: 'Strikethrough',
79
+ },
80
+ {
81
+ id: 'format.code',
82
+ label: 'Toggle Inline Code',
83
+ description: 'Format text as inline code',
84
+ category: 'Format',
85
+ action: (commands) => commands.formatText('code'),
86
+ shortcuts: [{ key: '`', ctrlKey: true }],
87
+ keywords: ['code', 'inline', 'format'],
88
+ icon: 'Code',
89
+ },
90
+
91
+ // Block Formatting Commands
92
+ {
93
+ id: 'block.heading1',
94
+ label: 'Heading 1',
95
+ description: 'Convert to large heading',
96
+ category: 'Block',
97
+ action: (commands) => commands.toggleHeading('h1'),
98
+ shortcuts: [{ key: '1', ctrlKey: true, altKey: true }],
99
+ keywords: ['heading', 'h1', 'title'],
100
+ icon: 'Heading1',
101
+ },
102
+ {
103
+ id: 'block.heading2',
104
+ label: 'Heading 2',
105
+ description: 'Convert to medium heading',
106
+ category: 'Block',
107
+ action: (commands) => commands.toggleHeading('h2'),
108
+ shortcuts: [{ key: '2', ctrlKey: true, altKey: true }],
109
+ keywords: ['heading', 'h2', 'subtitle'],
110
+ icon: 'Heading2',
111
+ },
112
+ {
113
+ id: 'block.heading3',
114
+ label: 'Heading 3',
115
+ description: 'Convert to small heading',
116
+ category: 'Block',
117
+ action: (commands) => commands.toggleHeading('h3'),
118
+ shortcuts: [{ key: '3', ctrlKey: true, altKey: true }],
119
+ keywords: ['heading', 'h3'],
120
+ icon: 'Heading3',
121
+ },
122
+ {
123
+ id: 'block.paragraph',
124
+ label: 'Paragraph',
125
+ description: 'Convert to paragraph',
126
+ category: 'Block',
127
+ action: (commands) => commands.toggleParagraph(),
128
+ shortcuts: [{ key: '0', ctrlKey: true, altKey: true }],
129
+ keywords: ['paragraph', 'normal', 'text'],
130
+ icon: 'Type',
131
+ },
132
+ {
133
+ id: 'block.quote',
134
+ label: 'Quote',
135
+ description: 'Convert to blockquote',
136
+ category: 'Block',
137
+ action: (commands) => commands.toggleQuote(),
138
+ shortcuts: [{ key: '>', ctrlKey: true, shiftKey: true }],
139
+ keywords: ['quote', 'blockquote', 'citation'],
140
+ icon: 'Quote',
141
+ },
142
+ {
143
+ id: 'block.codeblock',
144
+ label: 'Code Block',
145
+ description: 'Convert to code block',
146
+ category: 'Block',
147
+ action: (commands) => commands.toggleCodeBlock(),
148
+ shortcuts: [{ key: '`', ctrlKey: true, shiftKey: true }],
149
+ keywords: ['code', 'block', 'programming'],
150
+ icon: 'Terminal',
151
+ },
152
+
153
+ // List Commands
154
+ {
155
+ id: 'list.bullet',
156
+ label: 'Bullet List',
157
+ description: 'Create or toggle bullet list',
158
+ category: 'List',
159
+ action: (commands) => commands.toggleUnorderedList(),
160
+ shortcuts: [{ key: 'l', ctrlKey: true, shiftKey: true }],
161
+ keywords: ['list', 'bullet', 'unordered'],
162
+ icon: 'List',
163
+ },
164
+ {
165
+ id: 'list.numbered',
166
+ label: 'Numbered List',
167
+ description: 'Create or toggle numbered list',
168
+ category: 'List',
169
+ action: (commands) => commands.toggleOrderedList(),
170
+ shortcuts: [{ key: 'l', ctrlKey: true, altKey: true }],
171
+ keywords: ['list', 'numbered', 'ordered'],
172
+ icon: 'ListOrdered',
173
+ },
174
+
175
+ // Link Commands
176
+ {
177
+ id: 'link.insert',
178
+ label: 'Insert Link',
179
+ description: 'Insert or edit a link',
180
+ category: 'Insert',
181
+ action: (commands) => commands.insertLink(),
182
+ shortcuts: [{ key: 'k', ctrlKey: true }],
183
+ keywords: ['link', 'url', 'hyperlink'],
184
+ icon: 'Link',
185
+ },
186
+ {
187
+ id: 'link.remove',
188
+ label: 'Remove Link',
189
+ description: 'Remove link formatting',
190
+ category: 'Format',
191
+ action: (commands) => commands.removeLink(),
192
+ shortcuts: [{ key: 'k', ctrlKey: true, shiftKey: true }],
193
+ keywords: ['unlink', 'remove', 'link'],
194
+ icon: 'Unlink',
195
+ },
196
+
197
+ // Insert Commands
198
+ {
199
+ id: 'insert.horizontal-rule',
200
+ label: 'Insert Horizontal Rule',
201
+ description: 'Insert a horizontal line separator',
202
+ category: 'Insert',
203
+ action: (commands) => commands.insertHorizontalRule(),
204
+ shortcuts: [{ key: '-', ctrlKey: true, shiftKey: true }],
205
+ keywords: ['horizontal', 'rule', 'separator', 'line'],
206
+ icon: 'Minus',
207
+ },
208
+ {
209
+ id: 'insert.image',
210
+ label: 'Insert Image',
211
+ description: 'Insert an image from URL or file',
212
+ category: 'Insert',
213
+ action: (commands) => {
214
+ const src = prompt('Enter image URL:');
215
+ if (src) {
216
+ const alt = prompt('Enter alt text:') || '';
217
+ commands.insertImage({ src, alt });
218
+ }
219
+ },
220
+ shortcuts: [{ key: 'i', ctrlKey: true, shiftKey: true }],
221
+ keywords: ['image', 'picture', 'photo'],
222
+ icon: 'Image',
223
+ },
224
+
225
+ // Table Commands
226
+ {
227
+ id: 'table.insert',
228
+ label: 'Insert Table',
229
+ description: 'Insert a new table',
230
+ category: 'Table',
231
+ action: (commands) => commands.insertTable({ rows: 3, columns: 3, includeHeaders: true }),
232
+ shortcuts: [{ key: 't', ctrlKey: true, shiftKey: true }],
233
+ keywords: ['table', 'grid', 'data'],
234
+ icon: 'Table',
235
+ },
236
+ {
237
+ id: 'table.insert-row-above',
238
+ label: 'Insert Row Above',
239
+ description: 'Insert a new row above current row',
240
+ category: 'Table',
241
+ action: (commands) => commands.insertRowAbove(),
242
+ shortcuts: [{ key: 'ArrowUp', ctrlKey: true, shiftKey: true }],
243
+ keywords: ['table', 'row', 'insert', 'above'],
244
+ icon: 'ArrowUp',
245
+ },
246
+ {
247
+ id: 'table.insert-row-below',
248
+ label: 'Insert Row Below',
249
+ description: 'Insert a new row below current row',
250
+ category: 'Table',
251
+ action: (commands) => commands.insertRowBelow(),
252
+ shortcuts: [{ key: 'ArrowDown', ctrlKey: true, shiftKey: true }],
253
+ keywords: ['table', 'row', 'insert', 'below'],
254
+ icon: 'ArrowDown',
255
+ },
256
+ {
257
+ id: 'table.insert-column-left',
258
+ label: 'Insert Column Left',
259
+ description: 'Insert a new column to the left',
260
+ category: 'Table',
261
+ action: (commands) => commands.insertColumnLeft(),
262
+ shortcuts: [{ key: 'ArrowLeft', ctrlKey: true, shiftKey: true }],
263
+ keywords: ['table', 'column', 'insert', 'left'],
264
+ icon: 'ArrowLeft',
265
+ },
266
+ {
267
+ id: 'table.insert-column-right',
268
+ label: 'Insert Column Right',
269
+ description: 'Insert a new column to the right',
270
+ category: 'Table',
271
+ action: (commands) => commands.insertColumnRight(),
272
+ shortcuts: [{ key: 'ArrowRight', ctrlKey: true, shiftKey: true }],
273
+ keywords: ['table', 'column', 'insert', 'right'],
274
+ icon: 'ArrowRight',
275
+ },
276
+ {
277
+ id: 'table.delete-row',
278
+ label: 'Delete Row',
279
+ description: 'Delete the current table row',
280
+ category: 'Table',
281
+ action: (commands) => commands.deleteRow(),
282
+ shortcuts: [{ key: 'Backspace', ctrlKey: true, shiftKey: true }],
283
+ keywords: ['table', 'row', 'delete', 'remove'],
284
+ icon: 'Trash',
285
+ },
286
+ {
287
+ id: 'table.delete-column',
288
+ label: 'Delete Column',
289
+ description: 'Delete the current table column',
290
+ category: 'Table',
291
+ action: (commands) => commands.deleteColumn(),
292
+ shortcuts: [{ key: 'Delete', ctrlKey: true, shiftKey: true }],
293
+ keywords: ['table', 'column', 'delete', 'remove'],
294
+ icon: 'Trash',
295
+ },
296
+
297
+ // History Commands
298
+ {
299
+ id: 'edit.undo',
300
+ label: 'Undo',
301
+ description: 'Undo the last action',
302
+ category: 'Edit',
303
+ action: (commands) => commands.undo(),
304
+ shortcuts: [{ key: 'z', ctrlKey: true }],
305
+ keywords: ['undo', 'revert'],
306
+ icon: 'Undo',
307
+ },
308
+ {
309
+ id: 'edit.redo',
310
+ label: 'Redo',
311
+ description: 'Redo the last undone action',
312
+ category: 'Edit',
313
+ action: (commands) => commands.redo(),
314
+ shortcuts: [
315
+ { key: 'y', ctrlKey: true },
316
+ { key: 'z', ctrlKey: true, shiftKey: true },
317
+ ],
318
+ keywords: ['redo', 'repeat'],
319
+ icon: 'Redo',
320
+ },
321
+ ];
322
+
323
+ return commands;
324
+ }
325
+
326
+ /**
327
+ * Convert our commands to command palette items
328
+ */
329
+ export function commandsToCommandPaletteItems(commands: EditorCommands): CommandPaletteItem[] {
330
+ return generateCommands().map((cmd) => ({
331
+ id: cmd.id,
332
+ label: cmd.label,
333
+ description: cmd.description,
334
+ category: cmd.category,
335
+ action: () => cmd.action(commands),
336
+ keywords: cmd.keywords,
337
+ shortcut: cmd.shortcuts?.[0] ? formatShortcut(cmd.shortcuts[0]) : undefined,
338
+ }));
339
+ }
340
+
341
+ /**
342
+ * Format keyboard shortcut for display
343
+ */
344
+ function formatShortcut(shortcut: KeyboardShortcut): string {
345
+ const parts: string[] = [];
346
+ if (shortcut.ctrlKey) parts.push('Ctrl');
347
+ if (shortcut.metaKey) parts.push('Cmd');
348
+ if (shortcut.altKey) parts.push('Alt');
349
+ if (shortcut.shiftKey) parts.push('Shift');
350
+ parts.push(shortcut.key.toUpperCase());
351
+ return parts.join('+');
352
+ }
353
+
354
+ /**
355
+ * Register keyboard shortcuts
356
+ */
357
+ export function registerKeyboardShortcuts(
358
+ commands: EditorCommands,
359
+ element: HTMLElement = document.body,
360
+ ): () => void {
361
+ const commandConfigs = generateCommands();
362
+
363
+ const handleKeyDown = (event: KeyboardEvent) => {
364
+ for (const config of commandConfigs) {
365
+ if (!config.shortcuts) continue;
366
+
367
+ for (const shortcut of config.shortcuts) {
368
+ if (
369
+ event.key.toLowerCase() === shortcut.key.toLowerCase() &&
370
+ !!event.ctrlKey === !!shortcut.ctrlKey &&
371
+ !!event.metaKey === !!shortcut.metaKey &&
372
+ !!event.shiftKey === !!shortcut.shiftKey &&
373
+ !!event.altKey === !!shortcut.altKey
374
+ ) {
375
+ if (shortcut.preventDefault !== false) {
376
+ event.preventDefault();
377
+ }
378
+
379
+ // Check condition if exists
380
+ if (config.condition && !config.condition(commands)) {
381
+ continue;
382
+ }
383
+
384
+ config.action(commands);
385
+ return;
386
+ }
387
+ }
388
+ }
389
+ };
390
+
391
+ element.addEventListener('keydown', handleKeyDown);
392
+
393
+ return () => {
394
+ element.removeEventListener('keydown', handleKeyDown);
395
+ };
396
+ }
@@ -0,0 +1,150 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { ChevronDown, X } from 'lucide-react';
3
+
4
+ // Custom Select Component
5
+ export function Select({
6
+ value,
7
+ onValueChange,
8
+ options,
9
+ placeholder = 'Select...',
10
+ }: {
11
+ value: string;
12
+ onValueChange: (value: string) => void;
13
+ options: Array<{ value: string; label: string }>;
14
+ placeholder?: string;
15
+ }) {
16
+ const [isOpen, setIsOpen] = useState(false);
17
+ const selectRef = useRef<HTMLDivElement>(null);
18
+
19
+ // Close dropdown when clicking outside
20
+ useEffect(() => {
21
+ function handleClickOutside(event: MouseEvent) {
22
+ if (selectRef.current && !selectRef.current.contains(event.target as Node)) {
23
+ setIsOpen(false);
24
+ }
25
+ }
26
+ document.addEventListener('mousedown', handleClickOutside);
27
+ return () => document.removeEventListener('mousedown', handleClickOutside);
28
+ }, []);
29
+
30
+ const selectedOption = options.find((opt) => opt.value === value);
31
+
32
+ return (
33
+ <div className="lexkit-select" ref={selectRef}>
34
+ <button
35
+ className={`lexkit-select-trigger ${isOpen ? 'open' : ''}`}
36
+ onClick={() => setIsOpen(!isOpen)}
37
+ type="button"
38
+ >
39
+ <span>{selectedOption?.label || placeholder}</span>
40
+ <ChevronDown size={14} />
41
+ </button>
42
+ {isOpen && (
43
+ <div className="lexkit-select-dropdown">
44
+ {options.map((option) => (
45
+ <button
46
+ key={option.value}
47
+ className={`lexkit-select-option ${value === option.value ? 'selected' : ''}`}
48
+ onClick={() => {
49
+ onValueChange(option.value);
50
+ setIsOpen(false);
51
+ }}
52
+ type="button"
53
+ >
54
+ {option.label}
55
+ </button>
56
+ ))}
57
+ </div>
58
+ )}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ // Custom Dropdown Component
64
+ export function Dropdown({
65
+ trigger,
66
+ children,
67
+ isOpen,
68
+ onOpenChange,
69
+ }: {
70
+ trigger: React.ReactNode;
71
+ children: React.ReactNode;
72
+ isOpen: boolean;
73
+ onOpenChange: (open: boolean) => void;
74
+ }) {
75
+ const dropdownRef = useRef<HTMLDivElement>(null);
76
+
77
+ useEffect(() => {
78
+ function handleClickOutside(event: MouseEvent) {
79
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
80
+ onOpenChange(false);
81
+ }
82
+ }
83
+ document.addEventListener('mousedown', handleClickOutside);
84
+ return () => document.removeEventListener('mousedown', handleClickOutside);
85
+ }, [onOpenChange]);
86
+
87
+ return (
88
+ <div className="lexkit-dropdown" ref={dropdownRef}>
89
+ <div onClick={() => onOpenChange(!isOpen)}>{trigger}</div>
90
+ {isOpen && <div className="lexkit-dropdown-content">{children}</div>}
91
+ </div>
92
+ );
93
+ }
94
+
95
+ // Custom Dialog Component
96
+ export function Dialog({
97
+ isOpen,
98
+ onClose,
99
+ title,
100
+ children,
101
+ }: {
102
+ isOpen: boolean;
103
+ onClose: () => void;
104
+ title: string;
105
+ children: React.ReactNode;
106
+ }) {
107
+ const dialogRef = useRef<HTMLDivElement>(null);
108
+
109
+ useEffect(() => {
110
+ function handleClickOutside(event: MouseEvent) {
111
+ if (dialogRef.current && !dialogRef.current.contains(event.target as Node)) {
112
+ onClose();
113
+ }
114
+ }
115
+
116
+ function handleEscape(event: KeyboardEvent) {
117
+ if (event.key === 'Escape') {
118
+ onClose();
119
+ }
120
+ }
121
+
122
+ if (isOpen) {
123
+ document.addEventListener('mousedown', handleClickOutside);
124
+ document.addEventListener('keydown', handleEscape);
125
+ document.body.style.overflow = 'hidden';
126
+ }
127
+
128
+ return () => {
129
+ document.removeEventListener('mousedown', handleClickOutside);
130
+ document.removeEventListener('keydown', handleEscape);
131
+ document.body.style.overflow = 'unset';
132
+ };
133
+ }, [isOpen, onClose]);
134
+
135
+ if (!isOpen) return null;
136
+
137
+ return (
138
+ <div className="lexkit-dialog-overlay">
139
+ <div className="lexkit-dialog" ref={dialogRef}>
140
+ <div className="lexkit-dialog-header">
141
+ <h3 className="lexkit-dialog-title">{title}</h3>
142
+ <button className="lexkit-dialog-close" onClick={onClose} type="button">
143
+ <X size={16} />
144
+ </button>
145
+ </div>
146
+ <div className="lexkit-dialog-content">{children}</div>
147
+ </div>
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,5 @@
1
+ import { defaultLexKitTheme, LexKitTheme } from '@lexkit/editor';
2
+
3
+ export const defaultTheme: LexKitTheme = {
4
+ ...defaultLexKitTheme,
5
+ };
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import { useViewAs } from '@/contexts/view-as-context';
4
+ import { Eye, X } from 'lucide-react';
5
+ import { useRouter } from 'next/navigation';
6
+
7
+ export function ViewAsBanner() {
8
+ const { viewAsUser, clearViewAsUser, isViewingAsOther } = useViewAs();
9
+ const router = useRouter();
10
+
11
+ if (!isViewingAsOther || !viewAsUser) return null;
12
+
13
+ const handleReturn = () => {
14
+ clearViewAsUser();
15
+ router.refresh();
16
+ };
17
+
18
+ return (
19
+ <div className="shrink-0 bg-indigo-600 px-4 py-3 text-white shadow-md">
20
+ <div className="flex items-center justify-between gap-4">
21
+ <div className="flex items-center gap-3">
22
+ <Eye className="h-5 w-5 shrink-0" />
23
+ <div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
24
+ <span className="font-semibold">Mode vue utilisateur active</span>
25
+ <span className="text-sm text-white/90">
26
+ Vue : <span className="font-medium">{viewAsUser.name}</span>
27
+ {viewAsUser.customRole && (
28
+ <span className="ml-1">({viewAsUser.customRole.name})</span>
29
+ )}
30
+ </span>
31
+ </div>
32
+ </div>
33
+ <button
34
+ onClick={handleReturn}
35
+ className="flex shrink-0 cursor-pointer items-center gap-2 rounded-lg bg-white/20 px-4 py-2 text-sm font-medium transition-colors hover:bg-white/30"
36
+ aria-label="Revenir à ma vue"
37
+ >
38
+ <X className="h-4 w-4" />
39
+ <span className="hidden sm:inline">Revenir à ma vue</span>
40
+ <span className="sm:hidden">Retour</span>
41
+ </button>
42
+ </div>
43
+ </div>
44
+ );
45
+ }