create-crm-tmp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/bin/create-crm-tmp.js +93 -0
  2. package/package.json +25 -0
  3. package/template/.prettierignore +33 -0
  4. package/template/.prettierrc.json +25 -0
  5. package/template/README.md +173 -0
  6. package/template/eslint.config.mjs +18 -0
  7. package/template/exemple-contacts.csv +11 -0
  8. package/template/next.config.ts +8 -0
  9. package/template/package.json +64 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
  12. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
  13. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
  14. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
  15. package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
  16. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
  17. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
  18. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
  19. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
  20. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
  21. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
  22. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
  23. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
  24. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
  25. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
  26. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
  27. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
  28. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
  29. package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
  30. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
  31. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
  32. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
  33. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
  34. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
  35. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
  36. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
  37. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
  38. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
  39. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
  40. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
  41. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
  42. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
  43. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
  44. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
  45. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
  46. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
  47. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
  48. package/template/prisma/migrations/migration_lock.toml +3 -0
  49. package/template/prisma/schema.prisma +582 -0
  50. package/template/prisma.config.ts +14 -0
  51. package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
  52. package/template/src/app/(auth)/layout.tsx +3 -0
  53. package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
  54. package/template/src/app/(auth)/reset-password/page.tsx +146 -0
  55. package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
  56. package/template/src/app/(auth)/signin/page.tsx +166 -0
  57. package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
  58. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
  59. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
  60. package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
  61. package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
  62. package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
  63. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
  64. package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
  65. package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
  66. package/template/src/app/(dashboard)/layout.tsx +30 -0
  67. package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
  68. package/template/src/app/(dashboard)/templates/page.tsx +567 -0
  69. package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
  70. package/template/src/app/(dashboard)/users/page.tsx +457 -0
  71. package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
  72. package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
  73. package/template/src/app/api/audit-logs/route.ts +57 -0
  74. package/template/src/app/api/auth/[...all]/route.ts +4 -0
  75. package/template/src/app/api/auth/check-active/route.ts +31 -0
  76. package/template/src/app/api/auth/google/callback/route.ts +94 -0
  77. package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
  78. package/template/src/app/api/auth/google/route.ts +34 -0
  79. package/template/src/app/api/auth/google/status/route.ts +32 -0
  80. package/template/src/app/api/closing-reasons/route.ts +27 -0
  81. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
  82. package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
  83. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
  84. package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
  85. package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
  86. package/template/src/app/api/contacts/[id]/route.ts +322 -0
  87. package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
  88. package/template/src/app/api/contacts/export/route.ts +270 -0
  89. package/template/src/app/api/contacts/import/route.ts +381 -0
  90. package/template/src/app/api/contacts/route.ts +283 -0
  91. package/template/src/app/api/dashboard/stats/route.ts +299 -0
  92. package/template/src/app/api/email/track/[id]/route.ts +68 -0
  93. package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
  94. package/template/src/app/api/invite/complete/route.ts +88 -0
  95. package/template/src/app/api/invite/validate/route.ts +55 -0
  96. package/template/src/app/api/reminders/route.ts +95 -0
  97. package/template/src/app/api/reset-password/complete/route.ts +73 -0
  98. package/template/src/app/api/reset-password/request/route.ts +84 -0
  99. package/template/src/app/api/reset-password/validate/route.ts +49 -0
  100. package/template/src/app/api/reset-password/verify/route.ts +74 -0
  101. package/template/src/app/api/roles/[id]/route.ts +183 -0
  102. package/template/src/app/api/roles/route.ts +140 -0
  103. package/template/src/app/api/send/route.ts +282 -0
  104. package/template/src/app/api/settings/change-password/route.ts +95 -0
  105. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
  106. package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
  107. package/template/src/app/api/settings/company/route.ts +121 -0
  108. package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
  109. package/template/src/app/api/settings/google-ads/route.ts +122 -0
  110. package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
  111. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
  112. package/template/src/app/api/settings/google-sheet/route.ts +254 -0
  113. package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
  114. package/template/src/app/api/settings/meta-leads/route.ts +132 -0
  115. package/template/src/app/api/settings/profile/route.ts +42 -0
  116. package/template/src/app/api/settings/smtp/route.ts +130 -0
  117. package/template/src/app/api/settings/smtp/test/route.ts +121 -0
  118. package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
  119. package/template/src/app/api/settings/statuses/route.ts +83 -0
  120. package/template/src/app/api/statuses/route.ts +25 -0
  121. package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
  122. package/template/src/app/api/tasks/[id]/route.ts +728 -0
  123. package/template/src/app/api/tasks/meet/route.ts +240 -0
  124. package/template/src/app/api/tasks/route.ts +417 -0
  125. package/template/src/app/api/templates/[id]/route.ts +140 -0
  126. package/template/src/app/api/templates/route.ts +91 -0
  127. package/template/src/app/api/users/[id]/route.ts +168 -0
  128. package/template/src/app/api/users/list/route.ts +45 -0
  129. package/template/src/app/api/users/me/route.ts +48 -0
  130. package/template/src/app/api/users/route.ts +250 -0
  131. package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
  132. package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
  133. package/template/src/app/api/workflows/[id]/route.ts +192 -0
  134. package/template/src/app/api/workflows/process/route.ts +293 -0
  135. package/template/src/app/api/workflows/route.ts +124 -0
  136. package/template/src/app/favicon.ico +0 -0
  137. package/template/src/app/globals.css +1416 -0
  138. package/template/src/app/layout.tsx +31 -0
  139. package/template/src/app/page.tsx +32 -0
  140. package/template/src/components/dashboard/activity-chart.tsx +67 -0
  141. package/template/src/components/dashboard/contacts-chart.tsx +63 -0
  142. package/template/src/components/dashboard/recent-activity.tsx +164 -0
  143. package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
  144. package/template/src/components/dashboard/stat-card.tsx +61 -0
  145. package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
  146. package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
  147. package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
  148. package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
  149. package/template/src/components/editor.tsx +856 -0
  150. package/template/src/components/email-template.tsx +35 -0
  151. package/template/src/components/header.tsx +320 -0
  152. package/template/src/components/invitation-email-template.tsx +79 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +120 -0
  154. package/template/src/components/meet-confirmation-email-template.tsx +156 -0
  155. package/template/src/components/meet-update-email-template.tsx +209 -0
  156. package/template/src/components/page-header.tsx +61 -0
  157. package/template/src/components/reset-password-email-template.tsx +79 -0
  158. package/template/src/components/sidebar.tsx +294 -0
  159. package/template/src/components/skeleton.tsx +380 -0
  160. package/template/src/components/ui/commands.tsx +396 -0
  161. package/template/src/components/ui/components.tsx +150 -0
  162. package/template/src/components/ui/theme.tsx +5 -0
  163. package/template/src/components/view-as-banner.tsx +45 -0
  164. package/template/src/components/view-as-modal.tsx +186 -0
  165. package/template/src/contexts/mobile-menu-context.tsx +31 -0
  166. package/template/src/contexts/sidebar-context.tsx +107 -0
  167. package/template/src/contexts/task-reminder-context.tsx +239 -0
  168. package/template/src/contexts/view-as-context.tsx +84 -0
  169. package/template/src/hooks/use-user-role.ts +82 -0
  170. package/template/src/lib/audit-log.ts +45 -0
  171. package/template/src/lib/auth-client.ts +16 -0
  172. package/template/src/lib/auth.ts +35 -0
  173. package/template/src/lib/check-permission.ts +193 -0
  174. package/template/src/lib/contact-duplicate.ts +112 -0
  175. package/template/src/lib/contact-interactions.ts +371 -0
  176. package/template/src/lib/encryption.ts +99 -0
  177. package/template/src/lib/google-calendar.ts +300 -0
  178. package/template/src/lib/google-drive.ts +372 -0
  179. package/template/src/lib/permissions.ts +412 -0
  180. package/template/src/lib/prisma.ts +32 -0
  181. package/template/src/lib/roles.ts +120 -0
  182. package/template/src/lib/template-variables.ts +76 -0
  183. package/template/src/lib/utils.ts +46 -0
  184. package/template/src/lib/workflow-executor.ts +482 -0
  185. package/template/src/proxy.ts +91 -0
  186. package/template/tsconfig.json +34 -0
  187. package/template/vercel.json +8 -0
@@ -0,0 +1,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
+ }