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
|
@@ -4,6 +4,11 @@ import { useState, useEffect } from 'react';
|
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { ArrowLeft, Shield, Users as UsersIcon, Key, Plus, Edit, Trash2, X } from 'lucide-react';
|
|
6
6
|
import { PERMISSIONS, PERMISSIONS_BY_CATEGORY } from '@/lib/permissions';
|
|
7
|
+
import { useMobileMenuContext } from '@/contexts/mobile-menu-context';
|
|
8
|
+
import { useConfirm } from '@/hooks/use-confirm';
|
|
9
|
+
import { ProtectedPage } from '@/components/protected-page';
|
|
10
|
+
import { useAppToast } from '@/contexts/app-toast-context';
|
|
11
|
+
import { devToast } from '@/lib/utils';
|
|
7
12
|
|
|
8
13
|
interface Role {
|
|
9
14
|
id: string;
|
|
@@ -24,6 +29,7 @@ interface RoleModalProps {
|
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
32
|
+
const toast = useAppToast();
|
|
27
33
|
const [formData, setFormData] = useState({
|
|
28
34
|
name: role?.name || '',
|
|
29
35
|
description: role?.description || '',
|
|
@@ -50,6 +56,12 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
50
56
|
setError('');
|
|
51
57
|
}, [role, isOpen]);
|
|
52
58
|
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!error) return;
|
|
61
|
+
toast.error(error);
|
|
62
|
+
setError('');
|
|
63
|
+
}, [error, toast]);
|
|
64
|
+
|
|
53
65
|
const togglePermission = (permissionCode: string) => {
|
|
54
66
|
setFormData((prev) => ({
|
|
55
67
|
...prev,
|
|
@@ -83,7 +95,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
83
95
|
onSave();
|
|
84
96
|
onClose();
|
|
85
97
|
} catch (err: any) {
|
|
86
|
-
setError(err
|
|
98
|
+
setError(devToast('Erreur lors de la sauvegarde du profil', err));
|
|
87
99
|
} finally {
|
|
88
100
|
setIsSubmitting(false);
|
|
89
101
|
}
|
|
@@ -112,9 +124,6 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
112
124
|
|
|
113
125
|
{/* Content */}
|
|
114
126
|
<div className="max-h-[70vh] overflow-y-auto p-6">
|
|
115
|
-
{error && (
|
|
116
|
-
<div className="mb-4 rounded-lg bg-red-50 p-4 text-sm text-red-600">{error}</div>
|
|
117
|
-
)}
|
|
118
127
|
<div className="space-y-6">
|
|
119
128
|
{/* Nom et description */}
|
|
120
129
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
@@ -127,7 +136,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
127
136
|
id="name"
|
|
128
137
|
value={formData.name}
|
|
129
138
|
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
|
|
130
|
-
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"
|
|
131
140
|
required
|
|
132
141
|
/>
|
|
133
142
|
</div>
|
|
@@ -145,7 +154,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
145
154
|
}))
|
|
146
155
|
}
|
|
147
156
|
rows={2}
|
|
148
|
-
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"
|
|
149
158
|
/>
|
|
150
159
|
</div>
|
|
151
160
|
</div>
|
|
@@ -170,7 +179,7 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
170
179
|
type="checkbox"
|
|
171
180
|
checked={formData.permissions.includes(permission.code)}
|
|
172
181
|
onChange={() => togglePermission(permission.code)}
|
|
173
|
-
className="mt-1 h-4 w-4 rounded border-gray-300 text-
|
|
182
|
+
className="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-gray-400/30"
|
|
174
183
|
/>
|
|
175
184
|
<div className="flex-1">
|
|
176
185
|
<div className="font-medium text-gray-900">{permission.name}</div>
|
|
@@ -211,6 +220,9 @@ function RoleModal({ isOpen, onClose, onSave, role }: RoleModalProps) {
|
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
export default function RolesPage() {
|
|
223
|
+
const { toggle: toggleMobileMenu, isOpen: isMobileMenuOpen } = useMobileMenuContext();
|
|
224
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
225
|
+
const toast = useAppToast();
|
|
214
226
|
const [showModal, setShowModal] = useState(false);
|
|
215
227
|
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
|
|
216
228
|
const [roles, setRoles] = useState<Role[]>([]);
|
|
@@ -227,7 +239,7 @@ export default function RolesPage() {
|
|
|
227
239
|
const data = await response.json();
|
|
228
240
|
setRoles(data);
|
|
229
241
|
} catch (err: any) {
|
|
230
|
-
setError(err
|
|
242
|
+
setError(devToast('Erreur lors du chargement des profils', err));
|
|
231
243
|
} finally {
|
|
232
244
|
setLoading(false);
|
|
233
245
|
}
|
|
@@ -237,6 +249,12 @@ export default function RolesPage() {
|
|
|
237
249
|
fetchRoles();
|
|
238
250
|
}, []);
|
|
239
251
|
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!error) return;
|
|
254
|
+
toast.error(error);
|
|
255
|
+
setError('');
|
|
256
|
+
}, [error, toast]);
|
|
257
|
+
|
|
240
258
|
const handleEditRole = (roleId: string) => {
|
|
241
259
|
const role = roles.find((r) => r.id === roleId);
|
|
242
260
|
if (role) {
|
|
@@ -249,11 +267,20 @@ export default function RolesPage() {
|
|
|
249
267
|
const role = roles.find((r) => r.id === roleId);
|
|
250
268
|
if (!role) return;
|
|
251
269
|
|
|
270
|
+
const confirmTitle = role.isSystem ? '⚠️ Supprimer un profil système' : 'Supprimer le profil';
|
|
252
271
|
const confirmMessage = role.isSystem
|
|
253
|
-
?
|
|
272
|
+
? `Attention : "${role.name}" est un profil système. Êtes-vous sûr de vouloir le supprimer ?`
|
|
254
273
|
: `Êtes-vous sûr de vouloir supprimer le profil "${role.name}" ?`;
|
|
255
274
|
|
|
256
|
-
|
|
275
|
+
const confirmed = await confirm({
|
|
276
|
+
title: confirmTitle,
|
|
277
|
+
description: confirmMessage,
|
|
278
|
+
confirmText: 'Supprimer',
|
|
279
|
+
cancelText: 'Annuler',
|
|
280
|
+
variant: 'destructive',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!confirmed) {
|
|
257
284
|
return;
|
|
258
285
|
}
|
|
259
286
|
|
|
@@ -268,9 +295,10 @@ export default function RolesPage() {
|
|
|
268
295
|
throw new Error(data.error || 'Erreur lors de la suppression');
|
|
269
296
|
}
|
|
270
297
|
|
|
298
|
+
toast.success('Profil supprimé');
|
|
271
299
|
await fetchRoles();
|
|
272
300
|
} catch (err: any) {
|
|
273
|
-
setError(err
|
|
301
|
+
setError(devToast('Erreur lors de la suppression du profil', err));
|
|
274
302
|
setTimeout(() => setError(''), 5000);
|
|
275
303
|
}
|
|
276
304
|
};
|
|
@@ -285,148 +313,149 @@ export default function RolesPage() {
|
|
|
285
313
|
};
|
|
286
314
|
|
|
287
315
|
return (
|
|
288
|
-
<
|
|
289
|
-
<div className="
|
|
290
|
-
<div className="
|
|
291
|
-
<div className="
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
<div>
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
316
|
+
<ProtectedPage requiredPermission="users.manage_roles">
|
|
317
|
+
<div className="h-full">
|
|
318
|
+
<div className="border-b border-gray-200 bg-white">
|
|
319
|
+
<div className="p-4 sm:p-6">
|
|
320
|
+
<div className="mb-4">
|
|
321
|
+
<Link
|
|
322
|
+
href="/users"
|
|
323
|
+
className="inline-flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900"
|
|
324
|
+
>
|
|
325
|
+
<ArrowLeft className="h-4 w-4" />
|
|
326
|
+
Retour
|
|
327
|
+
</Link>
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex items-center justify-between">
|
|
330
|
+
<div>
|
|
331
|
+
<h1 className="text-2xl font-bold text-gray-900">Gestion des profils</h1>
|
|
332
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
333
|
+
Créer et configurer les profils avec leurs permissions
|
|
334
|
+
</p>
|
|
335
|
+
</div>
|
|
336
|
+
<button
|
|
337
|
+
onClick={() => {
|
|
338
|
+
setSelectedRole(null);
|
|
339
|
+
setShowModal(true);
|
|
340
|
+
}}
|
|
341
|
+
className="flex cursor-pointer items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700"
|
|
342
|
+
>
|
|
343
|
+
<Plus className="h-4 w-4" />
|
|
344
|
+
Nouveau profil
|
|
345
|
+
</button>
|
|
306
346
|
</div>
|
|
307
|
-
<button
|
|
308
|
-
onClick={() => {
|
|
309
|
-
setSelectedRole(null);
|
|
310
|
-
setShowModal(true);
|
|
311
|
-
}}
|
|
312
|
-
className="flex cursor-pointer items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700"
|
|
313
|
-
>
|
|
314
|
-
<Plus className="h-4 w-4" />
|
|
315
|
-
Nouveau profil
|
|
316
|
-
</button>
|
|
317
347
|
</div>
|
|
318
348
|
</div>
|
|
319
|
-
</div>
|
|
320
|
-
|
|
321
|
-
<div className="p-4 sm:p-6">
|
|
322
|
-
{error && <div className="mb-4 rounded-lg bg-red-50 p-4 text-sm text-red-600">{error}</div>}
|
|
323
349
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
<div className="flex items-start
|
|
345
|
-
<div className="
|
|
346
|
-
<
|
|
350
|
+
<div className="p-4 sm:p-6">
|
|
351
|
+
{loading ? (
|
|
352
|
+
<div className="grid gap-6 lg:grid-cols-2">
|
|
353
|
+
{[1, 2, 3, 4].map((i) => (
|
|
354
|
+
<div key={i} className="h-64 animate-pulse rounded-lg bg-gray-200" />
|
|
355
|
+
))}
|
|
356
|
+
</div>
|
|
357
|
+
) : (
|
|
358
|
+
<div className="grid gap-6 lg:grid-cols-2">
|
|
359
|
+
{roles
|
|
360
|
+
.sort((a, b) => b.permissions.length - a.permissions.length) // Trier par nombre de permissions (DESC)
|
|
361
|
+
.map((role) => {
|
|
362
|
+
const visiblePermissions = role.permissions.slice(0, 4);
|
|
363
|
+
const remainingCount = role.permissions.length - 4;
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<div
|
|
367
|
+
key={role.id}
|
|
368
|
+
className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm"
|
|
369
|
+
>
|
|
370
|
+
<div className="flex items-start justify-between">
|
|
371
|
+
<div className="flex items-start gap-3">
|
|
372
|
+
<div className="rounded-lg bg-green-100 p-2">
|
|
373
|
+
<Shield className="h-5 w-5 text-green-600" />
|
|
374
|
+
</div>
|
|
375
|
+
<div>
|
|
376
|
+
<h3 className="font-semibold text-gray-900">
|
|
377
|
+
{role.name}
|
|
378
|
+
{role.isSystem && (
|
|
379
|
+
<span className="ml-2 inline-block rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-800">
|
|
380
|
+
Système
|
|
381
|
+
</span>
|
|
382
|
+
)}
|
|
383
|
+
</h3>
|
|
384
|
+
<p className="mt-1 text-sm text-gray-600">{role.description}</p>
|
|
385
|
+
</div>
|
|
347
386
|
</div>
|
|
348
|
-
<div>
|
|
349
|
-
<
|
|
350
|
-
{role.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
387
|
+
<div className="flex items-center gap-2">
|
|
388
|
+
<button
|
|
389
|
+
onClick={() => handleEditRole(role.id)}
|
|
390
|
+
className="cursor-pointer rounded-lg p-2 text-orange-600 hover:bg-orange-50"
|
|
391
|
+
title="Modifier"
|
|
392
|
+
>
|
|
393
|
+
<Edit className="h-4 w-4" />
|
|
394
|
+
</button>
|
|
395
|
+
<button
|
|
396
|
+
onClick={() => handleDeleteRole(role.id)}
|
|
397
|
+
className="cursor-pointer rounded-lg p-2 text-red-600 hover:bg-red-50"
|
|
398
|
+
title="Supprimer"
|
|
399
|
+
>
|
|
400
|
+
<Trash2 className="h-4 w-4" />
|
|
401
|
+
</button>
|
|
358
402
|
</div>
|
|
359
403
|
</div>
|
|
360
|
-
<div className="flex items-center gap-2">
|
|
361
|
-
<button
|
|
362
|
-
onClick={() => handleEditRole(role.id)}
|
|
363
|
-
className="cursor-pointer rounded-lg p-2 text-orange-600 hover:bg-orange-50"
|
|
364
|
-
title="Modifier"
|
|
365
|
-
>
|
|
366
|
-
<Edit className="h-4 w-4" />
|
|
367
|
-
</button>
|
|
368
|
-
<button
|
|
369
|
-
onClick={() => handleDeleteRole(role.id)}
|
|
370
|
-
className="cursor-pointer rounded-lg p-2 text-red-600 hover:bg-red-50"
|
|
371
|
-
title="Supprimer"
|
|
372
|
-
>
|
|
373
|
-
<Trash2 className="h-4 w-4" />
|
|
374
|
-
</button>
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
404
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
405
|
+
<div className="mt-4 flex items-center gap-4 text-sm text-gray-600">
|
|
406
|
+
<div className="flex items-center gap-1">
|
|
407
|
+
<UsersIcon className="h-4 w-4" />
|
|
408
|
+
<span>
|
|
409
|
+
{role.usersCount} utilisateur{role.usersCount > 1 ? 's' : ''}
|
|
410
|
+
</span>
|
|
411
|
+
</div>
|
|
412
|
+
<div className="flex items-center gap-1">
|
|
413
|
+
<Key className="h-4 w-4" />
|
|
414
|
+
<span>
|
|
415
|
+
{role.permissions.length} permission
|
|
416
|
+
{role.permissions.length > 1 ? 's' : ''}
|
|
417
|
+
</span>
|
|
418
|
+
</div>
|
|
391
419
|
</div>
|
|
392
|
-
</div>
|
|
393
420
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
<div className="mt-4">
|
|
422
|
+
<h4 className="mb-2 text-xs font-medium tracking-wide text-gray-500 uppercase">
|
|
423
|
+
Permissions
|
|
424
|
+
</h4>
|
|
425
|
+
<div className="flex flex-wrap gap-2">
|
|
426
|
+
{visiblePermissions.map((permCode) => {
|
|
427
|
+
const perm = PERMISSIONS.find((p) => p.code === permCode);
|
|
428
|
+
return (
|
|
429
|
+
<span
|
|
430
|
+
key={permCode}
|
|
431
|
+
className="rounded-full bg-green-100 px-3 py-1 text-xs font-medium text-green-700"
|
|
432
|
+
>
|
|
433
|
+
{perm?.name || permCode}
|
|
434
|
+
</span>
|
|
435
|
+
);
|
|
436
|
+
})}
|
|
437
|
+
{remainingCount > 0 && (
|
|
438
|
+
<span className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700">
|
|
439
|
+
+{remainingCount} autres
|
|
407
440
|
</span>
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
{remainingCount > 0 && (
|
|
411
|
-
<span className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700">
|
|
412
|
-
+{remainingCount} autres
|
|
413
|
-
</span>
|
|
414
|
-
)}
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
415
443
|
</div>
|
|
416
444
|
</div>
|
|
417
|
-
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
</div>
|
|
445
|
+
);
|
|
446
|
+
})}
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
423
450
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
451
|
+
<RoleModal
|
|
452
|
+
isOpen={showModal}
|
|
453
|
+
onClose={handleCloseModal}
|
|
454
|
+
onSave={handleSaveRole}
|
|
455
|
+
role={selectedRole || undefined}
|
|
456
|
+
/>
|
|
457
|
+
<ConfirmDialog />
|
|
458
|
+
</div>
|
|
459
|
+
</ProtectedPage>
|
|
431
460
|
);
|
|
432
461
|
}
|
|
@@ -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
|
+
}
|
|
@@ -15,7 +15,7 @@ export async function GET(request: NextRequest) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// On réutilise la permission de vue utilisateurs pour l’instant
|
|
18
|
-
const hasPermission = await checkPermission('
|
|
18
|
+
const hasPermission = await checkPermission('audit.view_all');
|
|
19
19
|
if (!hasPermission) {
|
|
20
20
|
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
21
21
|
}
|
|
@@ -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
|
}
|
|
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
4
|
import { exchangeGoogleCodeForTokens } from '@/lib/google-calendar';
|
|
5
|
+
import { encrypt } from '@/lib/encryption';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* GET /api/auth/google/callback
|
|
@@ -17,6 +18,8 @@ export async function GET(request: NextRequest) {
|
|
|
17
18
|
return NextResponse.redirect(new URL('/signin', request.url));
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
// Tous les utilisateurs peuvent maintenant connecter leur compte Google pour Calendar
|
|
22
|
+
|
|
20
23
|
const { searchParams } = new URL(request.url);
|
|
21
24
|
const code = searchParams.get('code');
|
|
22
25
|
const error = searchParams.get('error');
|
|
@@ -63,19 +66,19 @@ export async function GET(request: NextRequest) {
|
|
|
63
66
|
console.error("Erreur lors de la récupération de l'email Google:", err);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
// Sauvegarder ou mettre à jour les tokens
|
|
69
|
+
// Sauvegarder ou mettre à jour les tokens (chiffrés si ENCRYPTION_KEY est définie)
|
|
67
70
|
await prisma.userGoogleAccount.upsert({
|
|
68
71
|
where: { userId: session.user.id },
|
|
69
72
|
create: {
|
|
70
73
|
userId: session.user.id,
|
|
71
|
-
accessToken: tokens.access_token,
|
|
72
|
-
refreshToken: tokens.refresh_token
|
|
74
|
+
accessToken: encrypt(tokens.access_token),
|
|
75
|
+
refreshToken: tokens.refresh_token ? encrypt(tokens.refresh_token) : '',
|
|
73
76
|
tokenExpiresAt,
|
|
74
77
|
email: googleEmail,
|
|
75
78
|
},
|
|
76
79
|
update: {
|
|
77
|
-
accessToken: tokens.access_token,
|
|
78
|
-
refreshToken: tokens.refresh_token
|
|
80
|
+
accessToken: encrypt(tokens.access_token),
|
|
81
|
+
refreshToken: tokens.refresh_token ? encrypt(tokens.refresh_token) : undefined,
|
|
79
82
|
tokenExpiresAt,
|
|
80
83
|
email: googleEmail,
|
|
81
84
|
},
|
|
@@ -4,7 +4,7 @@ import { prisma } from '@/lib/prisma';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* POST /api/auth/google/disconnect
|
|
7
|
-
* Déconnecte le compte Google de l'
|
|
7
|
+
* Déconnecte le compte Google de l'administrateur (seuls les admins peuvent déconnecter)
|
|
8
8
|
*/
|
|
9
9
|
export async function POST(request: NextRequest) {
|
|
10
10
|
try {
|
|
@@ -16,7 +16,7 @@ export async function POST(request: NextRequest) {
|
|
|
16
16
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// Tous les utilisateurs peuvent déconnecter leur compte Google
|
|
20
20
|
await prisma.userGoogleAccount.deleteMany({
|
|
21
21
|
where: { userId: session.user.id },
|
|
22
22
|
});
|
|
@@ -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({
|