@consilioweb/payload-support 0.5.1

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +525 -0
  3. package/dist/client.cjs +7 -0
  4. package/dist/client.d.cts +3 -0
  5. package/dist/client.d.ts +3 -0
  6. package/dist/client.js +5 -0
  7. package/dist/index.cjs +7766 -0
  8. package/dist/index.d.cts +384 -0
  9. package/dist/index.d.ts +384 -0
  10. package/dist/index.js +7730 -0
  11. package/dist/views.d.cts +30 -0
  12. package/dist/views.d.ts +30 -0
  13. package/package.json +131 -0
  14. package/src/client.ts +1 -0
  15. package/src/collections/AuthLogs.ts +65 -0
  16. package/src/collections/CannedResponses.ts +69 -0
  17. package/src/collections/ChatMessages.ts +98 -0
  18. package/src/collections/EmailLogs.ts +94 -0
  19. package/src/collections/KnowledgeBase.ts +99 -0
  20. package/src/collections/Macros.ts +98 -0
  21. package/src/collections/PendingEmails.ts +122 -0
  22. package/src/collections/SatisfactionSurveys.ts +98 -0
  23. package/src/collections/SlaPolicies.ts +123 -0
  24. package/src/collections/SupportClients.ts +210 -0
  25. package/src/collections/TicketActivityLog.ts +81 -0
  26. package/src/collections/TicketMessages.ts +364 -0
  27. package/src/collections/TicketStatuses.ts +108 -0
  28. package/src/collections/Tickets.ts +704 -0
  29. package/src/collections/TimeEntries.ts +105 -0
  30. package/src/collections/WebhookEndpoints.ts +96 -0
  31. package/src/collections/index.ts +16 -0
  32. package/src/components/TicketConversation/components/AISummaryPanel.tsx +85 -0
  33. package/src/components/TicketConversation/components/ActionPanels.tsx +140 -0
  34. package/src/components/TicketConversation/components/ActivityLog.tsx +39 -0
  35. package/src/components/TicketConversation/components/ClientBar.tsx +37 -0
  36. package/src/components/TicketConversation/components/ClientHistory.tsx +117 -0
  37. package/src/components/TicketConversation/components/CodeBlock.tsx +186 -0
  38. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +166 -0
  39. package/src/components/TicketConversation/components/QuickActions.tsx +82 -0
  40. package/src/components/TicketConversation/components/TicketHeader.tsx +91 -0
  41. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +161 -0
  42. package/src/components/TicketConversation/config.ts +82 -0
  43. package/src/components/TicketConversation/constants.ts +74 -0
  44. package/src/components/TicketConversation/context.ts +63 -0
  45. package/src/components/TicketConversation/hooks/useAI.ts +180 -0
  46. package/src/components/TicketConversation/hooks/useMessageActions.ts +131 -0
  47. package/src/components/TicketConversation/hooks/useReply.ts +190 -0
  48. package/src/components/TicketConversation/hooks/useTicketActions.ts +205 -0
  49. package/src/components/TicketConversation/hooks/useTimeTracking.ts +107 -0
  50. package/src/components/TicketConversation/hooks/useTranslation.ts +116 -0
  51. package/src/components/TicketConversation/index.tsx +1110 -0
  52. package/src/components/TicketConversation/locales/en.json +878 -0
  53. package/src/components/TicketConversation/locales/fr.json +878 -0
  54. package/src/components/TicketConversation/types.ts +54 -0
  55. package/src/components/TicketConversation/utils.ts +25 -0
  56. package/src/endpoints/admin-chat-stream.ts +238 -0
  57. package/src/endpoints/admin-chat.ts +263 -0
  58. package/src/endpoints/admin-stats.ts +200 -0
  59. package/src/endpoints/ai.ts +199 -0
  60. package/src/endpoints/apply-macro.ts +144 -0
  61. package/src/endpoints/auth-2fa.ts +163 -0
  62. package/src/endpoints/auto-close.ts +175 -0
  63. package/src/endpoints/billing.ts +167 -0
  64. package/src/endpoints/bulk-action.ts +103 -0
  65. package/src/endpoints/chat-stream.ts +127 -0
  66. package/src/endpoints/chat.ts +188 -0
  67. package/src/endpoints/chatbot.ts +113 -0
  68. package/src/endpoints/delete-account.ts +129 -0
  69. package/src/endpoints/email-stats.ts +109 -0
  70. package/src/endpoints/export-csv.ts +84 -0
  71. package/src/endpoints/export-data.ts +104 -0
  72. package/src/endpoints/import-conversation.ts +307 -0
  73. package/src/endpoints/index.ts +154 -0
  74. package/src/endpoints/login.ts +92 -0
  75. package/src/endpoints/merge-clients.ts +132 -0
  76. package/src/endpoints/merge-tickets.ts +137 -0
  77. package/src/endpoints/oauth-google.ts +179 -0
  78. package/src/endpoints/pending-emails-process.ts +224 -0
  79. package/src/endpoints/presence.ts +104 -0
  80. package/src/endpoints/process-scheduled.ts +144 -0
  81. package/src/endpoints/purge-logs.ts +58 -0
  82. package/src/endpoints/resend-notification.ts +99 -0
  83. package/src/endpoints/round-robin-config.ts +92 -0
  84. package/src/endpoints/satisfaction.ts +93 -0
  85. package/src/endpoints/search.ts +106 -0
  86. package/src/endpoints/seed-kb.ts +153 -0
  87. package/src/endpoints/settings.ts +144 -0
  88. package/src/endpoints/signature.ts +93 -0
  89. package/src/endpoints/sla-check.ts +124 -0
  90. package/src/endpoints/split-ticket.ts +131 -0
  91. package/src/endpoints/statuses.ts +45 -0
  92. package/src/endpoints/track-open.ts +154 -0
  93. package/src/endpoints/typing.ts +101 -0
  94. package/src/endpoints/user-prefs.ts +125 -0
  95. package/src/hooks/checkSLA.ts +414 -0
  96. package/src/hooks/ticketStatusEmail.ts +182 -0
  97. package/src/index.ts +51 -0
  98. package/src/plugin.ts +157 -0
  99. package/src/portal/LiveChat.tsx +1353 -0
  100. package/src/portal/auth/ChatWidget.tsx +350 -0
  101. package/src/portal/auth/ChatbotWidget.tsx +285 -0
  102. package/src/portal/auth/SupportHeader.tsx +409 -0
  103. package/src/portal/auth/dashboard/DashboardClient.tsx +650 -0
  104. package/src/portal/auth/dashboard/page.tsx +84 -0
  105. package/src/portal/auth/faq/FAQSearch.tsx +117 -0
  106. package/src/portal/auth/faq/page.tsx +199 -0
  107. package/src/portal/auth/layout.tsx +61 -0
  108. package/src/portal/auth/profile/page.tsx +705 -0
  109. package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +74 -0
  110. package/src/portal/auth/tickets/detail/CollapsibleMessages.tsx +46 -0
  111. package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +50 -0
  112. package/src/portal/auth/tickets/detail/MessageActions.tsx +158 -0
  113. package/src/portal/auth/tickets/detail/PrintButton.tsx +16 -0
  114. package/src/portal/auth/tickets/detail/ReadReceipt.tsx +34 -0
  115. package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +74 -0
  116. package/src/portal/auth/tickets/detail/SatisfactionForm.tsx +156 -0
  117. package/src/portal/auth/tickets/detail/TicketPolling.tsx +57 -0
  118. package/src/portal/auth/tickets/detail/TicketReplyForm.tsx +294 -0
  119. package/src/portal/auth/tickets/detail/TypingIndicator.tsx +58 -0
  120. package/src/portal/auth/tickets/detail/page.tsx +738 -0
  121. package/src/portal/auth/tickets/new/page.tsx +515 -0
  122. package/src/portal/forgot-password/page.tsx +114 -0
  123. package/src/portal/layout.tsx +26 -0
  124. package/src/portal/locales/en.json +374 -0
  125. package/src/portal/locales/fr.json +374 -0
  126. package/src/portal/login/page.tsx +351 -0
  127. package/src/portal/page.tsx +162 -0
  128. package/src/portal/register/page.tsx +281 -0
  129. package/src/portal/reset-password/page.tsx +152 -0
  130. package/src/styles/BillingView.module.scss +311 -0
  131. package/src/styles/ChatView.module.scss +438 -0
  132. package/src/styles/CommandPalette.module.scss +160 -0
  133. package/src/styles/CrmView.module.scss +554 -0
  134. package/src/styles/EmailTracking.module.scss +238 -0
  135. package/src/styles/ImportConversation.module.scss +267 -0
  136. package/src/styles/Layout.module.scss +55 -0
  137. package/src/styles/Logs.module.scss +164 -0
  138. package/src/styles/NewTicket.module.scss +143 -0
  139. package/src/styles/PendingEmails.module.scss +629 -0
  140. package/src/styles/SupportDashboard.module.scss +649 -0
  141. package/src/styles/TicketDetail.module.scss +1043 -0
  142. package/src/styles/TicketInbox.module.scss +296 -0
  143. package/src/styles/TicketingSettings.module.scss +358 -0
  144. package/src/styles/TimeDashboard.module.scss +287 -0
  145. package/src/styles/_tokens.scss +78 -0
  146. package/src/styles/theme.css +633 -0
  147. package/src/types.ts +255 -0
  148. package/src/utils/adminNotification.ts +38 -0
  149. package/src/utils/auth.ts +46 -0
  150. package/src/utils/emailTemplate.ts +343 -0
  151. package/src/utils/fireWebhooks.ts +84 -0
  152. package/src/utils/index.ts +22 -0
  153. package/src/utils/rateLimiter.ts +52 -0
  154. package/src/utils/readSettings.ts +67 -0
  155. package/src/utils/slugs.ts +54 -0
  156. package/src/utils/webhookDispatcher.ts +120 -0
  157. package/src/views/BillingView/client.tsx +137 -0
  158. package/src/views/BillingView/index.tsx +33 -0
  159. package/src/views/ChatView/client.tsx +294 -0
  160. package/src/views/ChatView/index.tsx +33 -0
  161. package/src/views/CrmView/client.tsx +206 -0
  162. package/src/views/CrmView/index.tsx +33 -0
  163. package/src/views/EmailTrackingView/client.tsx +124 -0
  164. package/src/views/EmailTrackingView/index.tsx +33 -0
  165. package/src/views/ImportConversationView/client.tsx +133 -0
  166. package/src/views/ImportConversationView/index.tsx +33 -0
  167. package/src/views/LogsView/client.tsx +151 -0
  168. package/src/views/LogsView/index.tsx +30 -0
  169. package/src/views/NewTicketView/client.tsx +227 -0
  170. package/src/views/NewTicketView/index.tsx +30 -0
  171. package/src/views/PendingEmailsView/client.tsx +177 -0
  172. package/src/views/PendingEmailsView/index.tsx +33 -0
  173. package/src/views/SupportDashboardView/client.tsx +424 -0
  174. package/src/views/SupportDashboardView/index.tsx +33 -0
  175. package/src/views/TicketDetailView/client.tsx +775 -0
  176. package/src/views/TicketDetailView/index.tsx +33 -0
  177. package/src/views/TicketInboxView/client.tsx +313 -0
  178. package/src/views/TicketInboxView/index.tsx +30 -0
  179. package/src/views/TicketingSettingsView/client.tsx +866 -0
  180. package/src/views/TicketingSettingsView/index.tsx +33 -0
  181. package/src/views/TimeDashboardView/client.tsx +144 -0
  182. package/src/views/TimeDashboardView/index.tsx +33 -0
  183. package/src/views/shared/AdminViewHeader.tsx +69 -0
  184. package/src/views/shared/ErrorBoundary.tsx +68 -0
  185. package/src/views/shared/Skeleton.tsx +125 -0
  186. package/src/views/shared/adminTokens.ts +37 -0
  187. package/src/views/shared/config.ts +82 -0
  188. package/src/views/shared/index.ts +6 -0
  189. package/src/views.ts +16 -0
@@ -0,0 +1,281 @@
1
+ 'use client'
2
+
3
+ import React, { useState, useRef } from 'react'
4
+ import Link from 'next/link'
5
+ import { useRouter } from 'next/navigation'
6
+
7
+ export default function SupportRegisterPage() {
8
+ const router = useRouter()
9
+ const [firstName, setFirstName] = useState('')
10
+ const [lastName, setLastName] = useState('')
11
+ const [company, setCompany] = useState('')
12
+ const [phone, setPhone] = useState('')
13
+ const [email, setEmail] = useState('')
14
+ const [password, setPassword] = useState('')
15
+ const [confirmPassword, setConfirmPassword] = useState('')
16
+ const [error, setError] = useState('')
17
+ const [loading, setLoading] = useState(false)
18
+ const loadedAt = useRef(Date.now())
19
+ const honeypotRef = useRef<HTMLInputElement>(null)
20
+
21
+ const handleSubmit = async (e: React.FormEvent) => {
22
+ e.preventDefault()
23
+ setError('')
24
+
25
+ // Client-side validation
26
+ if (password.length < 8) {
27
+ setError('Le mot de passé doit contenir au moins 8 caractères.')
28
+ return
29
+ }
30
+
31
+ if (password !== confirmPassword) {
32
+ setError('Les mots de passé ne correspondent pas.')
33
+ return
34
+ }
35
+
36
+ setLoading(true)
37
+
38
+ try {
39
+ const res = await fetch('/api/support-register', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ credentials: 'include',
43
+ body: JSON.stringify({
44
+ email,
45
+ password,
46
+ firstName,
47
+ lastName,
48
+ company,
49
+ ...(phone ? { phone } : {}),
50
+ _hp: honeypotRef.current?.value || '',
51
+ _t: loadedAt.current,
52
+ }),
53
+ })
54
+
55
+ const data = await res.json()
56
+
57
+ if (!res.ok) {
58
+ if (res.status === 409) {
59
+ // Account already exists — redirect to login with pre-filled email
60
+ router.push(`/support/login?email=${encodeURIComponent(email)}`)
61
+ return
62
+ }
63
+ setError(data.error || 'Erreur lors de la création du compte.')
64
+ return
65
+ }
66
+
67
+ router.push('/support/dashboard')
68
+ } catch {
69
+ setError('Erreur de connexion. Veuillez réessayer.')
70
+ } finally {
71
+ setLoading(false)
72
+ }
73
+ }
74
+
75
+ const inputClassName =
76
+ 'w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition-all placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10 dark:border-slate-600 dark:bg-slate-900 dark:text-white dark:placeholder:text-slate-500 dark:focus:border-blue-500 dark:focus:ring-blue-500/20'
77
+
78
+ return (
79
+ <div className="flex min-h-screen flex-col items-center justify-center px-4 py-12 bg-gradient-to-br from-slate-50 via-white to-blue-50 dark:from-slate-950 dark:via-slate-950 dark:to-blue-950/20">
80
+ {/* Decorative background element */}
81
+ <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_60%_50%_at_50%_0%,rgba(37,99,235,0.08),transparent)] dark:bg-[radial-gradient(ellipse_60%_50%_at_50%_0%,rgba(37,99,235,0.12),transparent)]" />
82
+
83
+ <div className="relative w-full max-w-[420px]">
84
+ {/* Logo / Header */}
85
+ <div className="mb-8 text-center">
86
+ <Link href="/support" className="group mb-6 inline-flex items-center gap-2.5 transition-opacity hover:opacity-80">
87
+ <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-600 text-white shadow-lg shadow-blue-600/25">
88
+ <svg
89
+ xmlns="http://www.w3.org/2000/svg"
90
+ viewBox="0 0 24 24"
91
+ fill="none"
92
+ stroke="currentColor"
93
+ strokeWidth="2"
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ className="h-5 w-5"
97
+ >
98
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
99
+ </svg>
100
+ </div>
101
+ <span className="text-lg font-semibold text-slate-900 dark:text-white">Support</span>
102
+ </Link>
103
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900 dark:text-white">Créer un compte</h1>
104
+ <p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
105
+ Rejoignez votre espace support en quelques secondes
106
+ </p>
107
+ </div>
108
+
109
+ {/* Registration Form */}
110
+ <form
111
+ onSubmit={handleSubmit}
112
+ className="rounded-2xl border border-slate-200 bg-white p-8 shadow-xl shadow-slate-200/50 dark:border-slate-700 dark:bg-slate-800 dark:shadow-none"
113
+ >
114
+ {error && (
115
+ <div className="mb-6 flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800/50 dark:bg-red-950/50 dark:text-red-400">
116
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="mt-0.5 h-4 w-4 shrink-0">
117
+ <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
118
+ </svg>
119
+ {error}
120
+ </div>
121
+ )}
122
+
123
+ {/* Prenom + Nom */}
124
+ <div className="mb-5 grid grid-cols-2 gap-4">
125
+ <div>
126
+ <label htmlFor="firstName" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
127
+ Prenom <span className="text-red-500">*</span>
128
+ </label>
129
+ <input
130
+ id="firstName"
131
+ type="text"
132
+ required
133
+ value={firstName}
134
+ onChange={(e) => setFirstName(e.target.value)}
135
+ placeholder="Jean"
136
+ className={inputClassName}
137
+ />
138
+ </div>
139
+ <div>
140
+ <label htmlFor="lastName" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
141
+ Nom <span className="text-red-500">*</span>
142
+ </label>
143
+ <input
144
+ id="lastName"
145
+ type="text"
146
+ required
147
+ value={lastName}
148
+ onChange={(e) => setLastName(e.target.value)}
149
+ placeholder="Dupont"
150
+ className={inputClassName}
151
+ />
152
+ </div>
153
+ </div>
154
+
155
+ {/* Entreprise */}
156
+ <div className="mb-5">
157
+ <label htmlFor="company" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
158
+ Entreprise <span className="text-red-500">*</span>
159
+ </label>
160
+ <input
161
+ id="company"
162
+ type="text"
163
+ required
164
+ value={company}
165
+ onChange={(e) => setCompany(e.target.value)}
166
+ placeholder="Mon Entreprise SARL"
167
+ className={inputClassName}
168
+ />
169
+ </div>
170
+
171
+ {/* Telephone */}
172
+ <div className="mb-5">
173
+ <label htmlFor="phone" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
174
+ Telephone <span className="text-sm font-normal text-slate-400">(optionnel)</span>
175
+ </label>
176
+ <input
177
+ id="phone"
178
+ type="tel"
179
+ value={phone}
180
+ onChange={(e) => setPhone(e.target.value)}
181
+ placeholder="06 12 34 56 78"
182
+ className={inputClassName}
183
+ />
184
+ </div>
185
+
186
+ {/* Email */}
187
+ <div className="mb-5">
188
+ <label htmlFor="email" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
189
+ Email <span className="text-red-500">*</span>
190
+ </label>
191
+ <input
192
+ id="email"
193
+ type="email"
194
+ required
195
+ value={email}
196
+ onChange={(e) => setEmail(e.target.value)}
197
+ placeholder="vous@entreprise.fr"
198
+ className={inputClassName}
199
+ />
200
+ </div>
201
+
202
+ {/* Mot de passé */}
203
+ <div className="mb-5">
204
+ <label htmlFor="password" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
205
+ Mot de passé <span className="text-red-500">*</span>
206
+ </label>
207
+ <input
208
+ id="password"
209
+ type="password"
210
+ required
211
+ minLength={8}
212
+ value={password}
213
+ onChange={(e) => setPassword(e.target.value)}
214
+ placeholder="Min. 8 caractères"
215
+ className={inputClassName}
216
+ />
217
+ </div>
218
+
219
+ {/* Confirmer mot de passé */}
220
+ <div className="mb-6">
221
+ <label htmlFor="confirmPassword" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
222
+ Confirmer le mot de passé <span className="text-red-500">*</span>
223
+ </label>
224
+ <input
225
+ id="confirmPassword"
226
+ type="password"
227
+ required
228
+ minLength={8}
229
+ value={confirmPassword}
230
+ onChange={(e) => setConfirmPassword(e.target.value)}
231
+ placeholder="Retapez votre mot de passe"
232
+ className={inputClassName}
233
+ />
234
+ </div>
235
+
236
+ {/* Honeypot — hidden from humans, filled by bots */}
237
+ <div aria-hidden="true" style={{ position: 'absolute', left: '-9999px', top: '-9999px' }}>
238
+ <label htmlFor="_hp">Ne pas remplir</label>
239
+ <input id="_hp" name="_hp" type="text" tabIndex={-1} autoComplete="off" ref={honeypotRef} />
240
+ </div>
241
+
242
+ <button
243
+ type="submit"
244
+ disabled={loading}
245
+ className="w-full rounded-xl bg-blue-600 px-6 py-3 text-sm font-medium text-white shadow-lg shadow-blue-600/25 transition-all hover:bg-blue-700 hover:shadow-xl active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none"
246
+ >
247
+ {loading ? 'Creation du compte...' : 'Créer mon compte'}
248
+ </button>
249
+
250
+ {/* Google SSO option */}
251
+ <div className="my-6 flex items-center gap-3">
252
+ <div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
253
+ <span className="text-xs font-medium text-slate-400 dark:text-slate-500">OU</span>
254
+ <div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
255
+ </div>
256
+
257
+ {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
258
+ <a
259
+ href="/api/support/oauth/google?action=register"
260
+ className="flex w-full items-center justify-center gap-3 rounded-xl border border-slate-300 bg-white px-6 py-3 text-sm font-medium text-slate-700 transition-all hover:bg-slate-50 hover:shadow-md active:scale-[0.98] dark:border-slate-600 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
261
+ >
262
+ <svg className="h-5 w-5" viewBox="0 0 24 24">
263
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4" />
264
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
265
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18A11.96 11.96 0 0 0 1 12c0 1.94.46 3.77 1.18 5.07l3.66-2.84z" fill="#FBBC05" />
266
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
267
+ </svg>
268
+ S&apos;inscrire avec Google
269
+ </a>
270
+ </form>
271
+
272
+ <p className="mt-6 text-center text-sm text-slate-500 dark:text-slate-400">
273
+ Deja un compte ?{' '}
274
+ <Link href="/support/login" className="font-medium text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
275
+ Se connecter
276
+ </Link>
277
+ </p>
278
+ </div>
279
+ </div>
280
+ )
281
+ }
@@ -0,0 +1,152 @@
1
+ 'use client'
2
+
3
+ import React, { Suspense, useState } from 'react'
4
+ import Link from 'next/link'
5
+ import { useSearchParams } from 'next/navigation'
6
+
7
+ function ResetPasswordForm() {
8
+ const searchParams = useSearchParams()
9
+ const token = searchParams.get('token')
10
+ const [password, setPassword] = useState('')
11
+ const [confirm, setConfirm] = useState('')
12
+ const [success, setSuccess] = useState(false)
13
+ const [error, setError] = useState('')
14
+ const [loading, setLoading] = useState(false)
15
+
16
+ const handleSubmit = async (e: React.FormEvent) => {
17
+ e.preventDefault()
18
+ setError('')
19
+
20
+ if (password.length < 8) {
21
+ setError('Le mot de passe doit faire au moins 8 caractères.')
22
+ return
23
+ }
24
+
25
+ if (password !== confirm) {
26
+ setError('Les mots de passe ne correspondent pas.')
27
+ return
28
+ }
29
+
30
+ setLoading(true)
31
+
32
+ try {
33
+ const res = await fetch('/api/support-clients/reset-password', {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ token, password }),
37
+ })
38
+
39
+ if (res.ok) {
40
+ setSuccess(true)
41
+ } else {
42
+ const data = await res.json()
43
+ setError(data.errors?.[0]?.message || 'Le lien est invalide ou a expiré.')
44
+ }
45
+ } catch {
46
+ setError('Erreur de connexion.')
47
+ } finally {
48
+ setLoading(false)
49
+ }
50
+ }
51
+
52
+ if (!token) {
53
+ return (
54
+ <div className="w-full max-w-md text-center">
55
+ <div className="rounded-2xl border-4 border-black bg-white p-8 shadow-[6px_6px_0px_#000]">
56
+ <p className="mb-4 text-lg font-bold text-red-600">Lien invalide</p>
57
+ <p className="mb-6 text-sm text-gray-500">
58
+ Ce lien de réinitialisation est invalide ou a expiré.
59
+ </p>
60
+ <Link
61
+ href="/support/forgot-password"
62
+ className="block w-full rounded-xl border-3 border-black bg-[#FFD600] px-6 py-3 text-center font-black text-black shadow-[4px_4px_0px_#000]"
63
+ >
64
+ Demander un nouveau lien
65
+ </Link>
66
+ </div>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ return (
72
+ <div className="w-full max-w-md">
73
+ <div className="mb-8 text-center">
74
+ <h1 className="text-3xl font-black text-black">Nouveau mot de passe</h1>
75
+ <p className="mt-2 text-gray-600">Choisissez votre nouveau mot de passe</p>
76
+ </div>
77
+
78
+ {success ? (
79
+ <div className="rounded-2xl border-4 border-black bg-white p-8 shadow-[6px_6px_0px_#000]">
80
+ <div className="mb-4 rounded-xl border-2 border-green-500 bg-green-50 px-4 py-3 text-sm font-semibold text-green-700">
81
+ Mot de passe modifié avec succès !
82
+ </div>
83
+ <Link
84
+ href="/support/login"
85
+ className="block w-full rounded-xl border-3 border-black bg-[#00E5FF] px-6 py-3 text-center text-base font-black text-black shadow-[4px_4px_0px_#000] transition-all hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000]"
86
+ >
87
+ Se connecter
88
+ </Link>
89
+ </div>
90
+ ) : (
91
+ <form
92
+ onSubmit={handleSubmit}
93
+ className="rounded-2xl border-4 border-black bg-white p-8 shadow-[6px_6px_0px_#000]"
94
+ >
95
+ {error && (
96
+ <div className="mb-6 rounded-xl border-2 border-red-500 bg-red-50 px-4 py-3 text-sm font-semibold text-red-700">
97
+ {error}
98
+ </div>
99
+ )}
100
+
101
+ <div className="mb-5">
102
+ <label htmlFor="password" className="mb-2 block text-sm font-bold text-black">
103
+ Nouveau mot de passe
104
+ </label>
105
+ <input
106
+ id="password"
107
+ type="password"
108
+ required
109
+ minLength={8}
110
+ value={password}
111
+ onChange={(e) => setPassword(e.target.value)}
112
+ className="w-full rounded-xl border-3 border-black px-4 py-3 text-base outline-none transition-shadow focus:shadow-[3px_3px_0px_#00E5FF]"
113
+ />
114
+ <p className="mt-1 text-xs text-gray-400">Minimum 8 caractères</p>
115
+ </div>
116
+
117
+ <div className="mb-6">
118
+ <label htmlFor="confirm" className="mb-2 block text-sm font-bold text-black">
119
+ Confirmer
120
+ </label>
121
+ <input
122
+ id="confirm"
123
+ type="password"
124
+ required
125
+ value={confirm}
126
+ onChange={(e) => setConfirm(e.target.value)}
127
+ className="w-full rounded-xl border-3 border-black px-4 py-3 text-base outline-none transition-shadow focus:shadow-[3px_3px_0px_#00E5FF]"
128
+ />
129
+ </div>
130
+
131
+ <button
132
+ type="submit"
133
+ disabled={loading}
134
+ className="w-full rounded-xl border-3 border-black bg-[#00E5FF] px-6 py-3 text-base font-black text-black shadow-[4px_4px_0px_#000] transition-all hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000] disabled:opacity-50"
135
+ >
136
+ {loading ? 'Modification...' : 'Modifier le mot de passe'}
137
+ </button>
138
+ </form>
139
+ )}
140
+ </div>
141
+ )
142
+ }
143
+
144
+ export default function ResetPasswordPage() {
145
+ return (
146
+ <div className="flex min-h-screen items-center justify-center px-4 py-12">
147
+ <Suspense fallback={<p className="text-gray-400">Chargement...</p>}>
148
+ <ResetPasswordForm />
149
+ </Suspense>
150
+ </div>
151
+ )
152
+ }