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.
- package/bin/create-crm-tmp.js +93 -0
- package/package.json +25 -0
- package/template/.prettierignore +33 -0
- package/template/.prettierrc.json +25 -0
- package/template/README.md +173 -0
- package/template/eslint.config.mjs +18 -0
- package/template/exemple-contacts.csv +11 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +64 -0
- package/template/postcss.config.mjs +7 -0
- package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
- package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +582 -0
- package/template/prisma.config.ts +14 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
- package/template/src/app/(auth)/layout.tsx +3 -0
- package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
- package/template/src/app/(auth)/reset-password/page.tsx +146 -0
- package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
- package/template/src/app/(auth)/signin/page.tsx +166 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
- package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
- package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
- package/template/src/app/(dashboard)/layout.tsx +30 -0
- package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
- package/template/src/app/(dashboard)/templates/page.tsx +567 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
- package/template/src/app/(dashboard)/users/page.tsx +457 -0
- package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
- package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
- package/template/src/app/api/audit-logs/route.ts +57 -0
- package/template/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/src/app/api/auth/check-active/route.ts +31 -0
- package/template/src/app/api/auth/google/callback/route.ts +94 -0
- package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
- package/template/src/app/api/auth/google/route.ts +34 -0
- package/template/src/app/api/auth/google/status/route.ts +32 -0
- package/template/src/app/api/closing-reasons/route.ts +27 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
- package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
- package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
- package/template/src/app/api/contacts/[id]/route.ts +322 -0
- package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
- package/template/src/app/api/contacts/export/route.ts +270 -0
- package/template/src/app/api/contacts/import/route.ts +381 -0
- package/template/src/app/api/contacts/route.ts +283 -0
- package/template/src/app/api/dashboard/stats/route.ts +299 -0
- package/template/src/app/api/email/track/[id]/route.ts +68 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
- package/template/src/app/api/invite/complete/route.ts +88 -0
- package/template/src/app/api/invite/validate/route.ts +55 -0
- package/template/src/app/api/reminders/route.ts +95 -0
- package/template/src/app/api/reset-password/complete/route.ts +73 -0
- package/template/src/app/api/reset-password/request/route.ts +84 -0
- package/template/src/app/api/reset-password/validate/route.ts +49 -0
- package/template/src/app/api/reset-password/verify/route.ts +74 -0
- package/template/src/app/api/roles/[id]/route.ts +183 -0
- package/template/src/app/api/roles/route.ts +140 -0
- package/template/src/app/api/send/route.ts +282 -0
- package/template/src/app/api/settings/change-password/route.ts +95 -0
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
- package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
- package/template/src/app/api/settings/company/route.ts +121 -0
- package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
- package/template/src/app/api/settings/google-ads/route.ts +122 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
- package/template/src/app/api/settings/google-sheet/route.ts +254 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
- package/template/src/app/api/settings/meta-leads/route.ts +132 -0
- package/template/src/app/api/settings/profile/route.ts +42 -0
- package/template/src/app/api/settings/smtp/route.ts +130 -0
- package/template/src/app/api/settings/smtp/test/route.ts +121 -0
- package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
- package/template/src/app/api/settings/statuses/route.ts +83 -0
- package/template/src/app/api/statuses/route.ts +25 -0
- package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
- package/template/src/app/api/tasks/[id]/route.ts +728 -0
- package/template/src/app/api/tasks/meet/route.ts +240 -0
- package/template/src/app/api/tasks/route.ts +417 -0
- package/template/src/app/api/templates/[id]/route.ts +140 -0
- package/template/src/app/api/templates/route.ts +91 -0
- package/template/src/app/api/users/[id]/route.ts +168 -0
- package/template/src/app/api/users/list/route.ts +45 -0
- package/template/src/app/api/users/me/route.ts +48 -0
- package/template/src/app/api/users/route.ts +250 -0
- package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
- package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
- package/template/src/app/api/workflows/[id]/route.ts +192 -0
- package/template/src/app/api/workflows/process/route.ts +293 -0
- package/template/src/app/api/workflows/route.ts +124 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +1416 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/components/dashboard/activity-chart.tsx +67 -0
- package/template/src/components/dashboard/contacts-chart.tsx +63 -0
- package/template/src/components/dashboard/recent-activity.tsx +164 -0
- package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
- package/template/src/components/dashboard/stat-card.tsx +61 -0
- package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
- package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
- package/template/src/components/editor.tsx +856 -0
- package/template/src/components/email-template.tsx +35 -0
- package/template/src/components/header.tsx +320 -0
- package/template/src/components/invitation-email-template.tsx +79 -0
- package/template/src/components/meet-cancellation-email-template.tsx +120 -0
- package/template/src/components/meet-confirmation-email-template.tsx +156 -0
- package/template/src/components/meet-update-email-template.tsx +209 -0
- package/template/src/components/page-header.tsx +61 -0
- package/template/src/components/reset-password-email-template.tsx +79 -0
- package/template/src/components/sidebar.tsx +294 -0
- package/template/src/components/skeleton.tsx +380 -0
- package/template/src/components/ui/commands.tsx +396 -0
- package/template/src/components/ui/components.tsx +150 -0
- package/template/src/components/ui/theme.tsx +5 -0
- package/template/src/components/view-as-banner.tsx +45 -0
- package/template/src/components/view-as-modal.tsx +186 -0
- package/template/src/contexts/mobile-menu-context.tsx +31 -0
- package/template/src/contexts/sidebar-context.tsx +107 -0
- package/template/src/contexts/task-reminder-context.tsx +239 -0
- package/template/src/contexts/view-as-context.tsx +84 -0
- package/template/src/hooks/use-user-role.ts +82 -0
- package/template/src/lib/audit-log.ts +45 -0
- package/template/src/lib/auth-client.ts +16 -0
- package/template/src/lib/auth.ts +35 -0
- package/template/src/lib/check-permission.ts +193 -0
- package/template/src/lib/contact-duplicate.ts +112 -0
- package/template/src/lib/contact-interactions.ts +371 -0
- package/template/src/lib/encryption.ts +99 -0
- package/template/src/lib/google-calendar.ts +300 -0
- package/template/src/lib/google-drive.ts +372 -0
- package/template/src/lib/permissions.ts +412 -0
- package/template/src/lib/prisma.ts +32 -0
- package/template/src/lib/roles.ts +120 -0
- package/template/src/lib/template-variables.ts +76 -0
- package/template/src/lib/utils.ts +46 -0
- package/template/src/lib/workflow-executor.ts +482 -0
- package/template/src/proxy.ts +91 -0
- package/template/tsconfig.json +34 -0
- package/template/vercel.json +8 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, Suspense } from 'react';
|
|
4
|
+
import { useRouter, useSearchParams } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
|
|
7
|
+
function VerifyResetCodeContent() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
const searchParams = useSearchParams();
|
|
10
|
+
const email = searchParams.get('email') || '';
|
|
11
|
+
|
|
12
|
+
const [code, setCode] = useState(['', '', '', '', '', '']);
|
|
13
|
+
const [error, setError] = useState('');
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!email) {
|
|
18
|
+
router.push('/reset-password');
|
|
19
|
+
}
|
|
20
|
+
}, [email, router]);
|
|
21
|
+
|
|
22
|
+
const handleCodeChange = (index: number, value: string) => {
|
|
23
|
+
if (!/^\d*$/.test(value)) return; // Seulement des chiffres
|
|
24
|
+
|
|
25
|
+
const newCode = [...code];
|
|
26
|
+
newCode[index] = value.slice(-1); // Prendre seulement le dernier caractère
|
|
27
|
+
setCode(newCode);
|
|
28
|
+
|
|
29
|
+
// Passer au champ suivant automatiquement
|
|
30
|
+
if (value && index < 5) {
|
|
31
|
+
const nextInput = document.getElementById(`code-${index + 1}`) as HTMLInputElement;
|
|
32
|
+
nextInput?.focus();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleKeyDown = (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
37
|
+
if (e.key === 'Backspace' && !code[index] && index > 0) {
|
|
38
|
+
const prevInput = document.getElementById(`code-${index - 1}`) as HTMLInputElement;
|
|
39
|
+
prevInput?.focus();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handlePaste = (e: React.ClipboardEvent) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
const pastedData = e.clipboardData.getData('text').slice(0, 6);
|
|
46
|
+
if (/^\d+$/.test(pastedData)) {
|
|
47
|
+
const newCode = pastedData.split('').concat(Array(6 - pastedData.length).fill(''));
|
|
48
|
+
setCode(newCode.slice(0, 6));
|
|
49
|
+
const lastFilledIndex = Math.min(pastedData.length - 1, 5);
|
|
50
|
+
const nextInput = document.getElementById(`code-${lastFilledIndex}`) as HTMLInputElement;
|
|
51
|
+
nextInput?.focus();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setError('');
|
|
58
|
+
|
|
59
|
+
const codeString = code.join('');
|
|
60
|
+
if (codeString.length !== 6) {
|
|
61
|
+
setError('Veuillez entrer le code complet à 6 chiffres');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setLoading(true);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch('/api/reset-password/verify', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ email, code: codeString }),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(data.error || 'Code invalide');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Rediriger vers la page de définition du nouveau mot de passe
|
|
81
|
+
router.push(`/reset-password/complete?token=${data.token}`);
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
setError(err.message || 'Code invalide');
|
|
84
|
+
setCode(['', '', '', '', '', '']);
|
|
85
|
+
const firstInput = document.getElementById('code-0') as HTMLInputElement;
|
|
86
|
+
firstInput?.focus();
|
|
87
|
+
} finally {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="flex min-h-screen items-center justify-center px-4">
|
|
94
|
+
<div className="w-full max-w-md">
|
|
95
|
+
<div className="rounded-2xl bg-white p-6 shadow-xl sm:p-8">
|
|
96
|
+
{/* Header */}
|
|
97
|
+
<div className="mb-6 text-center sm:mb-8">
|
|
98
|
+
<h1 className="text-2xl font-bold text-gray-900 sm:text-3xl">Vérifier le code</h1>
|
|
99
|
+
<p className="mt-2 text-sm text-gray-600">Entrez le code à 6 chiffres envoyé à</p>
|
|
100
|
+
<p className="mt-1 text-sm font-medium break-all text-gray-900">{email}</p>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Error Message */}
|
|
104
|
+
{error && (
|
|
105
|
+
<div className="mb-4 rounded-lg bg-red-50 p-4 text-sm text-red-600">{error}</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Form */}
|
|
109
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
110
|
+
<div className="flex justify-center gap-2 sm:gap-3">
|
|
111
|
+
{code.map((digit, index) => (
|
|
112
|
+
<input
|
|
113
|
+
key={index}
|
|
114
|
+
id={`code-${index}`}
|
|
115
|
+
type="text"
|
|
116
|
+
inputMode="numeric"
|
|
117
|
+
maxLength={1}
|
|
118
|
+
value={digit}
|
|
119
|
+
onChange={(e) => handleCodeChange(index, e.target.value)}
|
|
120
|
+
onKeyDown={(e) => handleKeyDown(index, e)}
|
|
121
|
+
onPaste={index === 0 ? handlePaste : undefined}
|
|
122
|
+
className="h-12 w-12 rounded-lg border-2 border-gray-300 text-center text-xl font-bold text-gray-900 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none sm:h-14 sm:w-14 sm:text-2xl"
|
|
123
|
+
autoFocus={index === 0}
|
|
124
|
+
/>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<button
|
|
129
|
+
type="submit"
|
|
130
|
+
disabled={loading || code.join('').length !== 6}
|
|
131
|
+
className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
132
|
+
>
|
|
133
|
+
{loading ? 'Vérification...' : 'Vérifier le code'}
|
|
134
|
+
</button>
|
|
135
|
+
</form>
|
|
136
|
+
|
|
137
|
+
<div className="mt-6 text-center">
|
|
138
|
+
<button
|
|
139
|
+
onClick={async () => {
|
|
140
|
+
try {
|
|
141
|
+
await fetch('/api/reset-password/request', {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: { 'Content-Type': 'application/json' },
|
|
144
|
+
body: JSON.stringify({ email }),
|
|
145
|
+
});
|
|
146
|
+
setError('');
|
|
147
|
+
} catch (err) {
|
|
148
|
+
// Ignorer les erreurs silencieusement
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
className="cursor-pointer text-sm text-indigo-600 hover:text-indigo-700"
|
|
152
|
+
>
|
|
153
|
+
Renvoyer le code
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<Link
|
|
158
|
+
href="/signin"
|
|
159
|
+
className="mt-4 block text-center text-sm text-indigo-600 hover:text-indigo-700"
|
|
160
|
+
>
|
|
161
|
+
Retour à la connexion
|
|
162
|
+
</Link>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default function VerifyResetCodePage() {
|
|
170
|
+
return (
|
|
171
|
+
<Suspense
|
|
172
|
+
fallback={
|
|
173
|
+
<div className="flex min-h-screen items-center justify-center px-4">
|
|
174
|
+
<div className="w-full max-w-md rounded-2xl bg-white p-8 text-center shadow-xl">
|
|
175
|
+
<p className="text-gray-600">Chargement...</p>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
}
|
|
179
|
+
>
|
|
180
|
+
<VerifyResetCodeContent />
|
|
181
|
+
</Suspense>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, Suspense } from 'react';
|
|
4
|
+
import { useRouter, useSearchParams } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { signIn, signOut } from '@/lib/auth-client';
|
|
7
|
+
import { Eye, EyeOff } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
function SignInContent() {
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const searchParams = useSearchParams();
|
|
12
|
+
const [email, setEmail] = useState('');
|
|
13
|
+
const [password, setPassword] = useState('');
|
|
14
|
+
const [error, setError] = useState('');
|
|
15
|
+
const [successMessage, setSuccessMessage] = useState('');
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const message = searchParams.get('message');
|
|
21
|
+
if (message) {
|
|
22
|
+
setSuccessMessage(message);
|
|
23
|
+
// Nettoyer l'URL
|
|
24
|
+
router.replace('/signin', { scroll: false });
|
|
25
|
+
}
|
|
26
|
+
}, [searchParams, router]);
|
|
27
|
+
|
|
28
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
setError('');
|
|
31
|
+
setLoading(true);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await signIn.email({
|
|
35
|
+
email,
|
|
36
|
+
password,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Vérifier si le compte est actif
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch('/api/auth/check-active', { method: 'GET' });
|
|
42
|
+
if (res.ok) {
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (!data.active) {
|
|
45
|
+
// Déconnecter immédiatement et afficher un message clair
|
|
46
|
+
await signOut();
|
|
47
|
+
setError(
|
|
48
|
+
'Votre compte a été désactivé. Merci de contacter un administrateur pour le réactiver.',
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (checkError) {
|
|
54
|
+
console.error('Erreur lors de la vérification du statut du compte:', checkError);
|
|
55
|
+
// On ne bloque pas la connexion dans ce cas, mais on logue l'erreur
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
router.push('/dashboard');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
setError('Email ou mot de passe incorrect');
|
|
61
|
+
console.error(err);
|
|
62
|
+
} finally {
|
|
63
|
+
setLoading(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex min-h-screen items-center justify-center px-4">
|
|
69
|
+
<div className="w-full max-w-md">
|
|
70
|
+
<div className="rounded-2xl bg-white p-6 shadow-xl sm:p-8">
|
|
71
|
+
{/* Header */}
|
|
72
|
+
<div className="mb-6 text-center sm:mb-8">
|
|
73
|
+
<h1 className="text-2xl font-bold text-gray-900 sm:text-3xl">Connexion</h1>
|
|
74
|
+
<p className="mt-2 text-sm text-gray-600">Connectez-vous à votre compte CRM</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Success Message */}
|
|
78
|
+
{successMessage && (
|
|
79
|
+
<div className="mb-4 rounded-lg bg-green-50 p-4 text-sm text-green-600">
|
|
80
|
+
{successMessage}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{/* Error Message */}
|
|
85
|
+
{error && (
|
|
86
|
+
<div className="mb-4 rounded-lg bg-red-50 p-4 text-sm text-red-600">{error}</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{/* Form */}
|
|
90
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
91
|
+
<div>
|
|
92
|
+
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
|
93
|
+
Email
|
|
94
|
+
</label>
|
|
95
|
+
<input
|
|
96
|
+
id="email"
|
|
97
|
+
type="email"
|
|
98
|
+
required
|
|
99
|
+
value={email}
|
|
100
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
101
|
+
className="mt-1 block w-full rounded-lg border border-gray-300 px-4 py-3 text-gray-900 placeholder-gray-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
102
|
+
placeholder="vous@exemple.com"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div>
|
|
107
|
+
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
|
108
|
+
Mot de passe
|
|
109
|
+
</label>
|
|
110
|
+
<div className="relative mt-1">
|
|
111
|
+
<input
|
|
112
|
+
id="password"
|
|
113
|
+
type={showPassword ? 'text' : 'password'}
|
|
114
|
+
required
|
|
115
|
+
value={password}
|
|
116
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
117
|
+
className="block w-full rounded-lg border border-gray-300 px-4 py-3 pr-10 text-gray-900 placeholder-gray-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
|
118
|
+
placeholder="••••••••"
|
|
119
|
+
/>
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
onClick={() => setShowPassword((prev) => !prev)}
|
|
123
|
+
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-gray-400 hover:text-gray-600"
|
|
124
|
+
aria-label={showPassword ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
|
|
125
|
+
>
|
|
126
|
+
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<button
|
|
132
|
+
type="submit"
|
|
133
|
+
disabled={loading}
|
|
134
|
+
className="w-full cursor-pointer rounded-lg bg-indigo-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
135
|
+
>
|
|
136
|
+
{loading ? 'Connexion...' : 'Se connecter'}
|
|
137
|
+
</button>
|
|
138
|
+
</form>
|
|
139
|
+
|
|
140
|
+
<Link
|
|
141
|
+
href="/reset-password"
|
|
142
|
+
className="mt-4 block text-center text-sm text-indigo-600 hover:text-indigo-700"
|
|
143
|
+
>
|
|
144
|
+
Mot de passe oublié ?
|
|
145
|
+
</Link>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default function SignInPage() {
|
|
153
|
+
return (
|
|
154
|
+
<Suspense
|
|
155
|
+
fallback={
|
|
156
|
+
<div className="flex min-h-screen items-center justify-center px-4">
|
|
157
|
+
<div className="w-full max-w-md rounded-2xl bg-white p-8 text-center shadow-xl">
|
|
158
|
+
<p className="text-gray-600">Chargement...</p>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
}
|
|
162
|
+
>
|
|
163
|
+
<SignInContent />
|
|
164
|
+
</Suspense>
|
|
165
|
+
);
|
|
166
|
+
}
|