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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useSession } from '@/lib/auth-client';
|
|
4
4
|
import { useEffect, useState, useMemo } from 'react';
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
5
|
+
import { cn, devToast } from '@/lib/utils';
|
|
6
6
|
import { UsersTableSkeleton, Spinner } from '@/components/skeleton';
|
|
7
7
|
import { Search, RefreshCw, Send } from 'lucide-react';
|
|
8
8
|
import { useMobileMenuContext } from '@/contexts/mobile-menu-context';
|
|
@@ -64,7 +64,7 @@ export default function UsersPage() {
|
|
|
64
64
|
setUsers(data);
|
|
65
65
|
} catch (error) {
|
|
66
66
|
console.error('Erreur:', error);
|
|
67
|
-
setError('Erreur lors du chargement des utilisateurs');
|
|
67
|
+
setError(devToast('Erreur lors du chargement des utilisateurs', error));
|
|
68
68
|
} finally {
|
|
69
69
|
setLoading(false);
|
|
70
70
|
}
|
|
@@ -124,7 +124,7 @@ export default function UsersPage() {
|
|
|
124
124
|
setFormData({ name: '', email: '', customRoleId: '' });
|
|
125
125
|
fetchUsers();
|
|
126
126
|
} catch (error: any) {
|
|
127
|
-
setError(error
|
|
127
|
+
setError(devToast("Erreur lors de la création de l'utilisateur", error));
|
|
128
128
|
} finally {
|
|
129
129
|
setIsSubmitting(false);
|
|
130
130
|
}
|
|
@@ -149,7 +149,7 @@ export default function UsersPage() {
|
|
|
149
149
|
);
|
|
150
150
|
fetchUsers();
|
|
151
151
|
} catch (error: any) {
|
|
152
|
-
setError(error
|
|
152
|
+
setError(devToast("Erreur lors de la mise à jour du compte", error));
|
|
153
153
|
}
|
|
154
154
|
};
|
|
155
155
|
|
|
@@ -170,7 +170,7 @@ export default function UsersPage() {
|
|
|
170
170
|
setSuccessMessage('Profil modifié avec succès');
|
|
171
171
|
fetchUsers();
|
|
172
172
|
} catch (error: any) {
|
|
173
|
-
setError(error
|
|
173
|
+
setError(devToast('Erreur lors du changement de profil', error));
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
|
|
@@ -191,7 +191,7 @@ export default function UsersPage() {
|
|
|
191
191
|
setSuccessMessage(`Invitation renvoyée à ${userName}`);
|
|
192
192
|
fetchUsers();
|
|
193
193
|
} catch (err: any) {
|
|
194
|
-
setError(err
|
|
194
|
+
setError(devToast("Erreur lors de l'envoi de l'invitation", err));
|
|
195
195
|
} finally {
|
|
196
196
|
setResendingIds((prev) => {
|
|
197
197
|
const next = new Set(prev);
|
|
@@ -219,12 +219,12 @@ export default function UsersPage() {
|
|
|
219
219
|
<ProtectedPage requiredPermission="users.view">
|
|
220
220
|
<div className="kb-tab-scope bg-surface-page flex h-full flex-col">
|
|
221
221
|
{/* Header */}
|
|
222
|
-
<div className="border-
|
|
222
|
+
<div className="border-border bg-background/95 border-b px-4 py-4 backdrop-blur-sm sm:px-6 lg:px-8">
|
|
223
223
|
<div className="mb-3 flex items-start justify-between gap-3">
|
|
224
224
|
{/* Bouton menu mobile */}
|
|
225
225
|
<button
|
|
226
226
|
onClick={toggleMobileMenu}
|
|
227
|
-
className="mt-1 shrink-0 cursor-pointer rounded-lg p-2
|
|
227
|
+
className="text-foreground/80 hover:bg-muted mt-1 shrink-0 cursor-pointer rounded-lg p-2 transition-colors duration-200 lg:hidden"
|
|
228
228
|
aria-label="Basculer le menu"
|
|
229
229
|
>
|
|
230
230
|
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -249,19 +249,19 @@ export default function UsersPage() {
|
|
|
249
249
|
{/* Titre et breadcrumbs */}
|
|
250
250
|
<div className="flex-1">
|
|
251
251
|
<div className="mb-1 flex items-center gap-2">
|
|
252
|
-
<h1 className="text-2xl font-bold
|
|
253
|
-
<span className="
|
|
252
|
+
<h1 className="text-foreground text-2xl font-bold">Utilisateurs</h1>
|
|
253
|
+
<span className="bg-primary/20 text-primary rounded-full px-2.5 py-0.5 text-xs font-semibold">
|
|
254
254
|
{users.length}
|
|
255
255
|
</span>
|
|
256
256
|
</div>
|
|
257
|
-
<p className="text-
|
|
257
|
+
<p className="text-muted-foreground text-sm">Home > Utilisateurs</p>
|
|
258
258
|
</div>
|
|
259
259
|
|
|
260
260
|
{/* Actions globales */}
|
|
261
261
|
<div className="flex items-center gap-2">
|
|
262
262
|
<button
|
|
263
263
|
onClick={fetchUsers}
|
|
264
|
-
className="cursor-pointer rounded-lg p-2
|
|
264
|
+
className="text-muted-foreground hover:bg-muted focus-visible:ring-primary cursor-pointer rounded-lg p-2 transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
|
265
265
|
title="Actualiser"
|
|
266
266
|
>
|
|
267
267
|
<RefreshCw className="h-5 w-5" />
|
|
@@ -273,20 +273,20 @@ export default function UsersPage() {
|
|
|
273
273
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
274
274
|
{/* Recherche */}
|
|
275
275
|
<div className="relative w-full max-w-sm">
|
|
276
|
-
<Search className="pointer-events-none absolute top-2.5 left-3 h-4 w-4
|
|
276
|
+
<Search className="text-muted-foreground pointer-events-none absolute top-2.5 left-3 h-4 w-4" />
|
|
277
277
|
<input
|
|
278
278
|
type="text"
|
|
279
279
|
value={search}
|
|
280
280
|
onChange={(e) => setSearch(e.target.value)}
|
|
281
281
|
placeholder="Rechercher un utilisateur (nom, email, profil)"
|
|
282
|
-
className="
|
|
282
|
+
className="border-border bg-muted text-foreground placeholder:text-muted-foreground focus:border-primary/50 focus:bg-background focus:ring-primary/20 w-full rounded-lg border py-2 pr-3 pl-9 text-sm focus:ring-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
283
283
|
/>
|
|
284
284
|
</div>
|
|
285
285
|
|
|
286
286
|
{/* Bouton d’ajout */}
|
|
287
287
|
<button
|
|
288
288
|
onClick={() => setShowAddModal(true)}
|
|
289
|
-
className="inline-flex cursor-pointer items-center justify-center gap-2 rounded-lg
|
|
289
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary inline-flex cursor-pointer items-center justify-center gap-2 rounded-lg px-4 py-2 text-xs font-semibold shadow-(--shadow-card) transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none sm:text-sm"
|
|
290
290
|
>
|
|
291
291
|
+ Nouvel utilisateur
|
|
292
292
|
</button>
|
|
@@ -299,56 +299,59 @@ export default function UsersPage() {
|
|
|
299
299
|
{loading ? (
|
|
300
300
|
<UsersTableSkeleton />
|
|
301
301
|
) : (
|
|
302
|
-
<div className="overflow-hidden rounded-xl border
|
|
302
|
+
<div className="border-border bg-card overflow-hidden rounded-xl border shadow-(--shadow-card)">
|
|
303
303
|
<div className="overflow-x-auto">
|
|
304
|
-
<table className="min-w-full divide-y
|
|
304
|
+
<table className="divide-border min-w-full divide-y text-sm">
|
|
305
305
|
<thead className="bg-muted/70">
|
|
306
306
|
<tr>
|
|
307
|
-
<th className="px-3 py-3 text-left text-xs font-medium tracking-wider
|
|
307
|
+
<th className="text-muted-foreground px-3 py-3 text-left text-xs font-medium tracking-wider uppercase sm:px-6">
|
|
308
308
|
Utilisateur
|
|
309
309
|
</th>
|
|
310
|
-
<th className="px-3 py-3 text-left text-xs font-medium tracking-wider
|
|
310
|
+
<th className="text-muted-foreground px-3 py-3 text-left text-xs font-medium tracking-wider uppercase sm:px-6">
|
|
311
311
|
Email
|
|
312
312
|
</th>
|
|
313
|
-
<th className="px-3 py-3 text-left text-xs font-medium tracking-wider
|
|
313
|
+
<th className="text-muted-foreground px-3 py-3 text-left text-xs font-medium tracking-wider uppercase sm:px-6">
|
|
314
314
|
Profil
|
|
315
315
|
</th>
|
|
316
|
-
<th className="px-3 py-3 text-left text-xs font-medium tracking-wider
|
|
316
|
+
<th className="text-muted-foreground px-3 py-3 text-left text-xs font-medium tracking-wider uppercase sm:px-6">
|
|
317
317
|
Email vérifié
|
|
318
318
|
</th>
|
|
319
|
-
<th className="px-3 py-3 text-left text-xs font-medium tracking-wider
|
|
319
|
+
<th className="text-muted-foreground px-3 py-3 text-left text-xs font-medium tracking-wider uppercase sm:px-6">
|
|
320
320
|
Compte
|
|
321
321
|
</th>
|
|
322
322
|
</tr>
|
|
323
323
|
</thead>
|
|
324
|
-
<tbody className="divide-
|
|
324
|
+
<tbody className="divide-border bg-card divide-y">
|
|
325
325
|
{filteredUsers.length === 0 ? (
|
|
326
326
|
<tr>
|
|
327
327
|
<td
|
|
328
328
|
colSpan={5}
|
|
329
|
-
className="px-3 py-6 text-center text-sm
|
|
329
|
+
className="text-muted-foreground px-3 py-6 text-center text-sm sm:px-6"
|
|
330
330
|
>
|
|
331
331
|
Aucun utilisateur ne correspond à votre recherche
|
|
332
332
|
</td>
|
|
333
333
|
</tr>
|
|
334
334
|
) : (
|
|
335
335
|
filteredUsers.map((user) => (
|
|
336
|
-
<tr
|
|
336
|
+
<tr
|
|
337
|
+
key={user.id}
|
|
338
|
+
className="hover:bg-muted/70 transition-colors duration-200"
|
|
339
|
+
>
|
|
337
340
|
<td className="px-3 py-4 whitespace-nowrap sm:px-6">
|
|
338
341
|
<div className="flex items-center">
|
|
339
|
-
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full
|
|
342
|
+
<div className="bg-primary/20 text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-xs font-semibold sm:h-10 sm:w-10">
|
|
340
343
|
{(user.name?.[0] || user.email?.[0] || '?').toUpperCase()}
|
|
341
344
|
</div>
|
|
342
345
|
<div className="ml-3 min-w-0">
|
|
343
|
-
<div className="truncate text-sm font-medium
|
|
346
|
+
<div className="text-foreground truncate text-sm font-medium sm:text-base">
|
|
344
347
|
{user.name}
|
|
345
348
|
</div>
|
|
346
349
|
<div className="mt-0.5 flex flex-wrap items-center gap-1">
|
|
347
|
-
<span className="inline-flex items-center rounded-full
|
|
350
|
+
<span className="bg-muted text-muted-foreground inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium tracking-wide uppercase">
|
|
348
351
|
{user.customRole?.name || user.role.toLowerCase()}
|
|
349
352
|
</span>
|
|
350
353
|
{user.id === session?.user?.id && (
|
|
351
|
-
<span className="inline-flex items-center rounded-full
|
|
354
|
+
<span className="bg-primary/20 text-primary inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium">
|
|
352
355
|
Vous
|
|
353
356
|
</span>
|
|
354
357
|
)}
|
|
@@ -356,7 +359,7 @@ export default function UsersPage() {
|
|
|
356
359
|
</div>
|
|
357
360
|
</div>
|
|
358
361
|
</td>
|
|
359
|
-
<td className="px-3 py-4 text-xs whitespace-nowrap
|
|
362
|
+
<td className="text-muted-foreground px-3 py-4 text-xs whitespace-nowrap sm:px-6 sm:text-sm">
|
|
360
363
|
<span className="block max-w-[180px] truncate sm:max-w-xs">
|
|
361
364
|
{user.email}
|
|
362
365
|
</span>
|
|
@@ -366,7 +369,7 @@ export default function UsersPage() {
|
|
|
366
369
|
value={user.customRoleId || ''}
|
|
367
370
|
onChange={(e) => handleChangeRole(user.id, e.target.value)}
|
|
368
371
|
disabled={user.id === session?.user?.id}
|
|
369
|
-
className="w-full rounded-md border
|
|
372
|
+
className="border-border bg-background text-foreground w-full rounded-md border px-2 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50 sm:px-3 sm:text-sm"
|
|
370
373
|
>
|
|
371
374
|
<option value="">Sélectionner un profil</option>
|
|
372
375
|
{roles.map((role) => (
|
|
@@ -398,7 +401,7 @@ export default function UsersPage() {
|
|
|
398
401
|
type="button"
|
|
399
402
|
disabled={resendingIds.has(user.id)}
|
|
400
403
|
onClick={() => handleResendInvite(user.id, user.name)}
|
|
401
|
-
className="inline-flex cursor-pointer items-center gap-1 rounded-md
|
|
404
|
+
className="bg-primary/20 text-primary hover:bg-primary/25 focus-visible:ring-primary inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
402
405
|
title="Renvoyer l'email d'invitation"
|
|
403
406
|
>
|
|
404
407
|
{resendingIds.has(user.id) ? (
|
|
@@ -435,7 +438,7 @@ export default function UsersPage() {
|
|
|
435
438
|
)}
|
|
436
439
|
/>
|
|
437
440
|
</button>
|
|
438
|
-
<span className="text-xs font-medium
|
|
441
|
+
<span className="text-muted-foreground text-xs font-medium">
|
|
439
442
|
{user.active ? 'Actif' : 'Inactif'}
|
|
440
443
|
</span>
|
|
441
444
|
</div>
|
|
@@ -452,12 +455,12 @@ export default function UsersPage() {
|
|
|
452
455
|
|
|
453
456
|
{/* Modal d'ajout */}
|
|
454
457
|
{showAddModal && (
|
|
455
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center
|
|
456
|
-
<div className="flex max-h-[90vh] w-full max-w-2xl flex-col rounded-xl border
|
|
458
|
+
<div className="bg-foreground/10 fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-sm sm:p-6">
|
|
459
|
+
<div className="border-border bg-card flex max-h-[90vh] w-full max-w-2xl flex-col rounded-xl border p-6 shadow-(--shadow-dropdown) sm:p-8">
|
|
457
460
|
{/* En-tête fixe */}
|
|
458
|
-
<div className="shrink-0 border-b
|
|
461
|
+
<div className="border-border shrink-0 border-b pb-4">
|
|
459
462
|
<div className="flex items-center justify-between">
|
|
460
|
-
<h2 className="text-xl font-bold
|
|
463
|
+
<h2 className="text-foreground text-xl font-bold sm:text-2xl">
|
|
461
464
|
Ajouter un utilisateur
|
|
462
465
|
</h2>
|
|
463
466
|
<button
|
|
@@ -467,7 +470,7 @@ export default function UsersPage() {
|
|
|
467
470
|
setFormData({ name: '', email: '', customRoleId: '' });
|
|
468
471
|
setError('');
|
|
469
472
|
}}
|
|
470
|
-
className="cursor-pointer rounded-lg p-2
|
|
473
|
+
className="text-muted-foreground hover:bg-muted cursor-pointer rounded-lg p-2 transition-colors duration-200"
|
|
471
474
|
>
|
|
472
475
|
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
473
476
|
<path
|
|
@@ -488,37 +491,37 @@ export default function UsersPage() {
|
|
|
488
491
|
className="flex-1 space-y-4 overflow-y-auto pt-4 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
489
492
|
>
|
|
490
493
|
<div>
|
|
491
|
-
<label className="block text-sm font-medium
|
|
494
|
+
<label className="text-foreground block text-sm font-medium">Nom complet</label>
|
|
492
495
|
<input
|
|
493
496
|
type="text"
|
|
494
497
|
required
|
|
495
498
|
value={formData.name}
|
|
496
499
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
497
|
-
className="mt-1 block w-full rounded-lg border
|
|
500
|
+
className="border-border bg-background focus:border-primary/50 focus:ring-primary/20 mt-1 block w-full rounded-lg border px-4 py-2 focus:ring-2"
|
|
498
501
|
/>
|
|
499
502
|
</div>
|
|
500
503
|
|
|
501
504
|
<div>
|
|
502
|
-
<label className="block text-sm font-medium
|
|
505
|
+
<label className="text-foreground block text-sm font-medium">Email</label>
|
|
503
506
|
<input
|
|
504
507
|
type="email"
|
|
505
508
|
required
|
|
506
509
|
value={formData.email}
|
|
507
510
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
508
|
-
className="mt-1 block w-full rounded-lg border
|
|
511
|
+
className="border-border bg-background focus:border-primary/50 focus:ring-primary/20 mt-1 block w-full rounded-lg border px-4 py-2 focus:ring-2"
|
|
509
512
|
/>
|
|
510
|
-
<p className="mt-1 text-xs
|
|
513
|
+
<p className="text-muted-foreground mt-1 text-xs">
|
|
511
514
|
Un email d'invitation sera envoyé à cet utilisateur
|
|
512
515
|
</p>
|
|
513
516
|
</div>
|
|
514
517
|
|
|
515
518
|
<div>
|
|
516
|
-
<label className="block text-sm font-medium
|
|
519
|
+
<label className="text-foreground block text-sm font-medium">Profil</label>
|
|
517
520
|
<select
|
|
518
521
|
value={formData.customRoleId}
|
|
519
522
|
onChange={(e) => setFormData({ ...formData, customRoleId: e.target.value })}
|
|
520
523
|
required
|
|
521
|
-
className="mt-1 block w-full rounded-lg border
|
|
524
|
+
className="border-border bg-background focus:border-primary/50 focus:ring-primary/20 mt-1 block w-full rounded-lg border px-4 py-2 focus:ring-2"
|
|
522
525
|
>
|
|
523
526
|
<option value="">Sélectionner un profil</option>
|
|
524
527
|
{roles.map((role) => (
|
|
@@ -532,7 +535,7 @@ export default function UsersPage() {
|
|
|
532
535
|
</form>
|
|
533
536
|
|
|
534
537
|
{/* Pied de modal fixe */}
|
|
535
|
-
<div className="shrink-0 border-t
|
|
538
|
+
<div className="border-border shrink-0 border-t pt-4">
|
|
536
539
|
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
|
537
540
|
<button
|
|
538
541
|
type="button"
|
|
@@ -543,7 +546,7 @@ export default function UsersPage() {
|
|
|
543
546
|
setFormData({ name: '', email: '', customRoleId: '' });
|
|
544
547
|
setError('');
|
|
545
548
|
}}
|
|
546
|
-
className="w-full cursor-pointer rounded-lg border
|
|
549
|
+
className="border-border text-foreground hover:bg-muted w-full cursor-pointer rounded-lg border px-4 py-2 text-sm font-medium transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
|
547
550
|
>
|
|
548
551
|
Annuler
|
|
549
552
|
</button>
|
|
@@ -551,7 +554,7 @@ export default function UsersPage() {
|
|
|
551
554
|
type="submit"
|
|
552
555
|
form="add-user-form"
|
|
553
556
|
disabled={isSubmitting}
|
|
554
|
-
className="inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg
|
|
557
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-60 sm:w-auto"
|
|
555
558
|
>
|
|
556
559
|
{isSubmitting && <Spinner size="sm" className="text-white" />}
|
|
557
560
|
{isSubmitting ? 'Création en cours...' : 'Créer'}
|
|
@@ -129,7 +129,7 @@ export default function AccessControlPage() {
|
|
|
129
129
|
<Link
|
|
130
130
|
key={card.title}
|
|
131
131
|
href={card.href}
|
|
132
|
-
className="group cursor-pointer rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-
|
|
132
|
+
className="group cursor-pointer rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-[border-color,box-shadow] hover:border-blue-300 hover:shadow-md"
|
|
133
133
|
>
|
|
134
134
|
<div className="flex items-start gap-4">
|
|
135
135
|
<div className={`rounded-lg p-3 ${card.iconBg}`}>
|
|
@@ -99,14 +99,14 @@ export default function PermissionsPage() {
|
|
|
99
99
|
placeholder="Rechercher une permission..."
|
|
100
100
|
value={searchTerm}
|
|
101
101
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
102
|
-
className="w-full rounded-lg border border-gray-300 py-2 pr-4 pl-10 text-sm focus:
|
|
102
|
+
className="w-full rounded-lg border border-gray-300 py-2 pr-4 pl-10 text-sm focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
103
103
|
/>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
106
|
<select
|
|
107
107
|
value={selectedCategory}
|
|
108
108
|
onChange={(e) => setSelectedCategory(e.target.value)}
|
|
109
|
-
className="rounded-lg border border-gray-300 px-4 py-2 text-sm focus:
|
|
109
|
+
className="rounded-lg border border-gray-300 px-4 py-2 text-sm focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
110
110
|
>
|
|
111
111
|
<option value="all">Toutes les catégories</option>
|
|
112
112
|
{Object.values(PERMISSION_CATEGORIES).map((category) => (
|
|
@@ -8,6 +8,7 @@ import { useMobileMenuContext } from '@/contexts/mobile-menu-context';
|
|
|
8
8
|
import { useConfirm } from '@/hooks/use-confirm';
|
|
9
9
|
import { ProtectedPage } from '@/components/protected-page';
|
|
10
10
|
import { useAppToast } from '@/contexts/app-toast-context';
|
|
11
|
+
import { devToast } from '@/lib/utils';
|
|
11
12
|
|
|
12
13
|
interface Role {
|
|
13
14
|
id: string;
|
|
@@ -94,7 +95,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
94
95
|
onSave();
|
|
95
96
|
onClose();
|
|
96
97
|
} catch (err: any) {
|
|
97
|
-
setError(err
|
|
98
|
+
setError(devToast('Erreur lors de la sauvegarde du profil', err));
|
|
98
99
|
} finally {
|
|
99
100
|
setIsSubmitting(false);
|
|
100
101
|
}
|
|
@@ -135,7 +136,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
135
136
|
id="name"
|
|
136
137
|
value={formData.name}
|
|
137
138
|
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
138
|
-
className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:
|
|
139
|
+
className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
139
140
|
required
|
|
140
141
|
/>
|
|
141
142
|
</div>
|
|
@@ -153,7 +154,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
153
154
|
}))
|
|
154
155
|
}
|
|
155
156
|
rows={2}
|
|
156
|
-
className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:
|
|
157
|
+
className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-gray-400/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
157
158
|
/>
|
|
158
159
|
</div>
|
|
159
160
|
</div>
|
|
@@ -238,7 +239,7 @@ export default function RolesPage() {
|
|
|
238
239
|
const data = await response.json();
|
|
239
240
|
setRoles(data);
|
|
240
241
|
} catch (err: any) {
|
|
241
|
-
setError(err
|
|
242
|
+
setError(devToast('Erreur lors du chargement des profils', err));
|
|
242
243
|
} finally {
|
|
243
244
|
setLoading(false);
|
|
244
245
|
}
|
|
@@ -294,9 +295,10 @@ export default function RolesPage() {
|
|
|
294
295
|
throw new Error(data.error || 'Erreur lors de la suppression');
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
toast.success('Profil supprimé');
|
|
297
299
|
await fetchRoles();
|
|
298
300
|
} catch (err: any) {
|
|
299
|
-
setError(err
|
|
301
|
+
setError(devToast('Erreur lors de la suppression du profil', err));
|
|
300
302
|
setTimeout(() => setError(''), 5000);
|
|
301
303
|
}
|
|
302
304
|
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { encrypt } from '@/lib/encryption';
|
|
5
|
+
import {
|
|
6
|
+
getValidAccessToken,
|
|
7
|
+
getUserGoogleAccount,
|
|
8
|
+
listGoogleCalendarEvents,
|
|
9
|
+
} from '@/lib/google-calendar';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* GET /api/agenda/google-events?startDate=&endDate= (ISO)
|
|
13
|
+
* Événements Google des calendriers cochés dans les préférences utilisateur.
|
|
14
|
+
*/
|
|
15
|
+
export async function GET(request: NextRequest) {
|
|
16
|
+
try {
|
|
17
|
+
const session = await auth.api.getSession({
|
|
18
|
+
headers: request.headers,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!session) {
|
|
22
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { searchParams } = new URL(request.url);
|
|
26
|
+
const startRaw = searchParams.get('startDate');
|
|
27
|
+
const endRaw = searchParams.get('endDate');
|
|
28
|
+
if (!startRaw || !endRaw) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'Paramètres startDate et endDate (ISO) requis' },
|
|
31
|
+
{ status: 400 },
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const timeMin = new Date(startRaw);
|
|
36
|
+
const timeMax = new Date(endRaw);
|
|
37
|
+
if (Number.isNaN(timeMin.getTime()) || Number.isNaN(timeMax.getTime())) {
|
|
38
|
+
return NextResponse.json({ error: 'Dates invalides' }, { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let googleAccount;
|
|
42
|
+
try {
|
|
43
|
+
googleAccount = await getUserGoogleAccount(session.user.id);
|
|
44
|
+
} catch {
|
|
45
|
+
return NextResponse.json({ events: [] });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const visible = normalizeAgendaIds(googleAccount.agendaVisibleGoogleCalendarIds);
|
|
49
|
+
if (visible.length === 0) {
|
|
50
|
+
return NextResponse.json({ events: [] });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const accessToken = await getValidAccessToken(
|
|
54
|
+
googleAccount.accessToken,
|
|
55
|
+
googleAccount.refreshToken,
|
|
56
|
+
googleAccount.tokenExpiresAt,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (accessToken !== googleAccount.accessToken) {
|
|
60
|
+
const tokenExpiresAt = new Date();
|
|
61
|
+
tokenExpiresAt.setSeconds(tokenExpiresAt.getSeconds() + 3600);
|
|
62
|
+
await prisma.userGoogleAccount.update({
|
|
63
|
+
where: { userId: session.user.id },
|
|
64
|
+
data: {
|
|
65
|
+
accessToken: encrypt(accessToken),
|
|
66
|
+
tokenExpiresAt,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const results = await Promise.all(
|
|
72
|
+
visible.map((calendarId) =>
|
|
73
|
+
listGoogleCalendarEvents(accessToken, calendarId, timeMin, timeMax).catch((err) => {
|
|
74
|
+
console.error(`google-events ${calendarId}:`, err);
|
|
75
|
+
return [] as Awaited<ReturnType<typeof listGoogleCalendarEvents>>;
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const events = results.flat();
|
|
81
|
+
|
|
82
|
+
return NextResponse.json({ events });
|
|
83
|
+
} catch (error: any) {
|
|
84
|
+
console.error('GET agenda google-events:', error);
|
|
85
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeAgendaIds(raw: unknown): string[] {
|
|
90
|
+
if (!raw || !Array.isArray(raw)) return [];
|
|
91
|
+
return raw.filter((x): x is string => typeof x === 'string' && x.trim() !== '');
|
|
92
|
+
}
|
|
@@ -23,9 +23,10 @@ export async function GET(request: NextRequest) {
|
|
|
23
23
|
return NextResponse.json({ active: isActive }, { status: 200 });
|
|
24
24
|
} catch (error: any) {
|
|
25
25
|
console.error('Erreur lors de la vérification du statut utilisateur:', error);
|
|
26
|
+
// 500 : ne pas renvoyer active: false pour éviter une déconnexion client sur erreur transitoire
|
|
26
27
|
return NextResponse.json(
|
|
27
|
-
{
|
|
28
|
-
{ status:
|
|
28
|
+
{ error: error.message || 'Erreur serveur' },
|
|
29
|
+
{ status: 500 },
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -15,8 +15,9 @@ export async function GET(request: NextRequest) {
|
|
|
15
15
|
|
|
16
16
|
const scopes = [
|
|
17
17
|
'https://www.googleapis.com/auth/calendar.events',
|
|
18
|
+
// Liste des calendriers (partagés, etc.) — les comptes déjà connectés doivent se reconnecter pour obtenir ce scope.
|
|
19
|
+
'https://www.googleapis.com/auth/calendar.calendarlist.readonly',
|
|
18
20
|
'https://www.googleapis.com/auth/spreadsheets.readonly',
|
|
19
|
-
'https://www.googleapis.com/auth/drive.file', // Accès aux fichiers créés par l'application
|
|
20
21
|
];
|
|
21
22
|
|
|
22
23
|
const params = new URLSearchParams({
|
|
@@ -4,7 +4,7 @@ import { prisma } from '@/lib/prisma';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* GET /api/auth/google/status
|
|
7
|
-
* Récupère le statut de connexion Google
|
|
7
|
+
* Récupère le statut de connexion Google Calendar (utilisateur courant)
|
|
8
8
|
*/
|
|
9
9
|
export async function GET(request: NextRequest) {
|
|
10
10
|
try {
|
|
@@ -16,29 +16,6 @@ export async function GET(request: NextRequest) {
|
|
|
16
16
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Statut Google Drive (admin uniquement)
|
|
20
|
-
let driveConnected = false;
|
|
21
|
-
let driveEmail: string | null = null;
|
|
22
|
-
try {
|
|
23
|
-
const adminUser = await prisma.user.findFirst({
|
|
24
|
-
where: { role: 'ADMIN' },
|
|
25
|
-
include: {
|
|
26
|
-
googleAccount: true,
|
|
27
|
-
},
|
|
28
|
-
orderBy: {
|
|
29
|
-
createdAt: 'asc',
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (adminUser && adminUser.googleAccount) {
|
|
34
|
-
driveConnected = true;
|
|
35
|
-
driveEmail = adminUser.googleAccount.email || adminUser.email || null;
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// Ignorer l'erreur
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Statut Google Calendar (utilisateur courant)
|
|
42
19
|
let calendarConnected = false;
|
|
43
20
|
let calendarEmail: string | null = null;
|
|
44
21
|
try {
|
|
@@ -50,22 +27,21 @@ export async function GET(request: NextRequest) {
|
|
|
50
27
|
calendarConnected = true;
|
|
51
28
|
calendarEmail = userGoogleAccount.email || null;
|
|
52
29
|
}
|
|
53
|
-
} catch
|
|
30
|
+
} catch {
|
|
54
31
|
// Ignorer l'erreur
|
|
55
32
|
}
|
|
56
33
|
|
|
57
34
|
return NextResponse.json({
|
|
58
|
-
drive: {
|
|
59
|
-
connected: driveConnected,
|
|
60
|
-
email: driveEmail,
|
|
61
|
-
},
|
|
62
35
|
calendar: {
|
|
63
36
|
connected: calendarConnected,
|
|
64
37
|
email: calendarEmail,
|
|
65
38
|
},
|
|
66
39
|
});
|
|
67
|
-
} catch (error:
|
|
40
|
+
} catch (error: unknown) {
|
|
68
41
|
console.error('Erreur lors de la récupération du statut Google:', error);
|
|
69
|
-
return NextResponse.json(
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: error instanceof Error ? error.message : 'Erreur serveur' },
|
|
44
|
+
{ status: 500 },
|
|
45
|
+
);
|
|
70
46
|
}
|
|
71
47
|
}
|
|
@@ -81,9 +81,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const canAddActivity =
|
|
84
|
-
(await checkPermission('companies.add_activity')) ||
|
|
85
|
-
(await checkPermission('companies.edit')) ||
|
|
86
|
-
(await checkPermission('contacts.edit'));
|
|
84
|
+
(await checkPermission('companies.add_activity')) || (await checkPermission('companies.edit'));
|
|
87
85
|
if (!canAddActivity) {
|
|
88
86
|
return NextResponse.json({ error: 'Permission insuffisante' }, { status: 403 });
|
|
89
87
|
}
|
|
@@ -75,8 +75,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
|
|
75
75
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const canEdit =
|
|
79
|
-
(await checkPermission('companies.edit')) || (await checkPermission('contacts.edit'));
|
|
78
|
+
const canEdit = await checkPermission('companies.edit');
|
|
80
79
|
if (!canEdit) {
|
|
81
80
|
return NextResponse.json({ error: 'Permission insuffisante' }, { status: 403 });
|
|
82
81
|
}
|