@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,351 @@
1
+ 'use client'
2
+
3
+ import React, { useState, useEffect, Suspense } from 'react'
4
+ import Link from 'next/link'
5
+ import { useRouter, useSearchParams } from 'next/navigation'
6
+
7
+ export default function SupportLoginPage() {
8
+ return (
9
+ <Suspense>
10
+ <SupportLoginContent />
11
+ </Suspense>
12
+ )
13
+ }
14
+
15
+ function SupportLoginContent() {
16
+ const router = useRouter()
17
+ const searchParams = useSearchParams()
18
+ const [email, setEmail] = useState('')
19
+ const [info, setInfo] = useState('')
20
+
21
+ // Pre-fill email from query param (e.g. redirect from register page)
22
+ useEffect(() => {
23
+ const emailParam = searchParams.get('email')
24
+ if (emailParam) {
25
+ setEmail(emailParam)
26
+ setInfo('Vous avez déjà un compte. Connectez-vous pour accéder à votre espace support.')
27
+ }
28
+ }, [searchParams])
29
+ const [password, setPassword] = useState('')
30
+ const [error, setError] = useState('')
31
+ const [loading, setLoading] = useState(false)
32
+
33
+ // 2FA state
34
+ const [needs2FA, setNeeds2FA] = useState(false)
35
+ const [twoFactorCode, setTwoFactorCode] = useState('')
36
+ const [sending2FA, setSending2FA] = useState(false)
37
+
38
+ const handleSubmit = async (e: React.FormEvent) => {
39
+ e.preventDefault()
40
+ setError('')
41
+ setLoading(true)
42
+
43
+ try {
44
+ const res = await fetch('/api/support/login', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ credentials: 'include',
48
+ body: JSON.stringify({ email, password }),
49
+ })
50
+
51
+ const data = await res.json()
52
+
53
+ if (!res.ok || data.errors) {
54
+ setError(data.errors?.[0]?.message || 'Email ou mot de passé incorrect.')
55
+ return
56
+ }
57
+
58
+ // Check if 2FA is enabled for this user
59
+ if (data.user?.twoFactorEnabled) {
60
+ // Logout immediately (we need 2FA first)
61
+ await fetch('/api/support-clients/logout', {
62
+ method: 'POST',
63
+ credentials: 'include',
64
+ })
65
+
66
+ // Send 2FA code
67
+ setSending2FA(true)
68
+ const codeRes = await fetch('/api/support/2fa', {
69
+ method: 'POST',
70
+ headers: { 'Content-Type': 'application/json' },
71
+ body: JSON.stringify({ action: 'send', email }),
72
+ })
73
+
74
+ if (codeRes.ok) {
75
+ setNeeds2FA(true)
76
+ } else {
77
+ setError('Erreur lors de l\'envoi du code de vérification.')
78
+ }
79
+ setSending2FA(false)
80
+ return
81
+ }
82
+
83
+ router.push('/support/dashboard')
84
+ } catch {
85
+ setError('Erreur de connexion. Veuillez réessayer.')
86
+ } finally {
87
+ setLoading(false)
88
+ }
89
+ }
90
+
91
+ const handleVerify2FA = async (e: React.FormEvent) => {
92
+ e.preventDefault()
93
+ setError('')
94
+ setLoading(true)
95
+
96
+ try {
97
+ // Verify the 2FA code
98
+ const verifyRes = await fetch('/api/support/2fa', {
99
+ method: 'POST',
100
+ headers: { 'Content-Type': 'application/json' },
101
+ body: JSON.stringify({ action: 'verify', email, code: twoFactorCode }),
102
+ })
103
+
104
+ const verifyData = await verifyRes.json()
105
+
106
+ if (!verifyRes.ok || !verifyData.verified) {
107
+ setError(verifyData.error || 'Code incorrect.')
108
+ return
109
+ }
110
+
111
+ // Re-login now that 2FA is verified
112
+ const loginRes = await fetch('/api/support-clients/login', {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ credentials: 'include',
116
+ body: JSON.stringify({ email, password }),
117
+ })
118
+
119
+ if (loginRes.ok) {
120
+ router.push('/support/dashboard')
121
+ } else {
122
+ setError('Erreur de connexion après vérification.')
123
+ }
124
+ } catch {
125
+ setError('Erreur de connexion.')
126
+ } finally {
127
+ setLoading(false)
128
+ }
129
+ }
130
+
131
+ const handleResend2FA = async () => {
132
+ setSending2FA(true)
133
+ setError('')
134
+ try {
135
+ const res = await fetch('/api/support/2fa', {
136
+ method: 'POST',
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({ action: 'send', email }),
139
+ })
140
+ if (res.ok) {
141
+ setError('')
142
+ } else {
143
+ setError('Erreur lors du renvoi du code.')
144
+ }
145
+ } catch {
146
+ setError('Erreur de connexion.')
147
+ } finally {
148
+ setSending2FA(false)
149
+ }
150
+ }
151
+
152
+ return (
153
+ <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">
154
+ {/* Decorative background element */}
155
+ <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)]" />
156
+
157
+ <div className="relative w-full max-w-[420px]">
158
+ {/* Logo / Header */}
159
+ <div className="mb-8 text-center">
160
+ <Link href="/support" className="group mb-6 inline-flex items-center gap-2.5 transition-opacity hover:opacity-80">
161
+ <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-600 text-white shadow-lg shadow-blue-600/25">
162
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5">
163
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
164
+ </svg>
165
+ </div>
166
+ <span className="text-lg font-semibold text-slate-900 dark:text-white">Support</span>
167
+ </Link>
168
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900 dark:text-white">
169
+ Connexion à votre espace
170
+ </h1>
171
+ <p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
172
+ Portail client — Support et suivi de projets
173
+ </p>
174
+ </div>
175
+
176
+ {needs2FA ? (
177
+ /* 2FA Verification Form */
178
+ <form
179
+ onSubmit={handleVerify2FA}
180
+ 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"
181
+ >
182
+ <div className="mb-6 text-center">
183
+ <div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-blue-100 text-blue-600 dark:bg-blue-950 dark:text-blue-400">
184
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-7 w-7">
185
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
186
+ <path d="M7 11V7a5 5 0 0 1 10 0v4" />
187
+ </svg>
188
+ </div>
189
+ <h2 className="text-lg font-semibold text-slate-900 dark:text-white">Verification en deux étapes</h2>
190
+ <p className="mt-1.5 text-sm text-slate-500 dark:text-slate-400">
191
+ Un code à ete envoye à <strong className="font-medium text-slate-700 dark:text-slate-300">{email}</strong>
192
+ </p>
193
+ </div>
194
+
195
+ {error && (
196
+ <div className="mb-5 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">
197
+ <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">
198
+ <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" />
199
+ </svg>
200
+ {error}
201
+ </div>
202
+ )}
203
+
204
+ <div className="mb-6">
205
+ <label htmlFor="code" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
206
+ Code de verification
207
+ </label>
208
+ <input
209
+ id="code"
210
+ type="text"
211
+ required
212
+ maxLength={6}
213
+ value={twoFactorCode}
214
+ onChange={(e) => setTwoFactorCode(e.target.value.replace(/\D/g, ''))}
215
+ placeholder="000000"
216
+ className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-center text-2xl font-semibold tracking-[0.4em] text-slate-900 outline-none transition-all placeholder:text-slate-300 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-600 dark:focus:border-blue-500 dark:focus:ring-blue-500/20"
217
+ autoFocus
218
+ />
219
+ </div>
220
+
221
+ <button
222
+ type="submit"
223
+ disabled={loading || twoFactorCode.length !== 6}
224
+ 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"
225
+ >
226
+ {loading ? 'Verification...' : 'Verifier'}
227
+ </button>
228
+
229
+ <div className="mt-5 flex items-center justify-between text-sm">
230
+ <button
231
+ type="button"
232
+ onClick={handleResend2FA}
233
+ disabled={sending2FA}
234
+ className="font-medium text-blue-600 transition-colors hover:text-blue-700 disabled:opacity-50 dark:text-blue-400 dark:hover:text-blue-300"
235
+ >
236
+ {sending2FA ? 'Envoi...' : 'Renvoyer le code'}
237
+ </button>
238
+ <button
239
+ type="button"
240
+ onClick={() => { setNeeds2FA(false); setTwoFactorCode(''); setError('') }}
241
+ className="font-medium text-slate-500 transition-colors hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
242
+ >
243
+ Retour
244
+ </button>
245
+ </div>
246
+ </form>
247
+ ) : (
248
+ /* Login Form */
249
+ <form
250
+ onSubmit={handleSubmit}
251
+ 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"
252
+ >
253
+ {info && (
254
+ <div className="mb-6 flex items-start gap-3 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-700 dark:border-blue-800/50 dark:bg-blue-950/50 dark:text-blue-400">
255
+ <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">
256
+ <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clipRule="evenodd" />
257
+ </svg>
258
+ {info}
259
+ </div>
260
+ )}
261
+
262
+ {error && (
263
+ <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">
264
+ <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">
265
+ <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" />
266
+ </svg>
267
+ {error}
268
+ </div>
269
+ )}
270
+
271
+ <div className="mb-5">
272
+ <label htmlFor="email" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
273
+ Email
274
+ </label>
275
+ <input
276
+ id="email"
277
+ type="email"
278
+ required
279
+ value={email}
280
+ onChange={(e) => setEmail(e.target.value)}
281
+ placeholder="vous@entreprise.fr"
282
+ className="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"
283
+ />
284
+ </div>
285
+
286
+ <div className="mb-6">
287
+ <label htmlFor="password" className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
288
+ Mot de passe
289
+ </label>
290
+ <input
291
+ id="password"
292
+ type="password"
293
+ required
294
+ value={password}
295
+ onChange={(e) => setPassword(e.target.value)}
296
+ placeholder="••••••••"
297
+ className="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"
298
+ />
299
+ </div>
300
+
301
+ <button
302
+ type="submit"
303
+ disabled={loading || sending2FA}
304
+ 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"
305
+ >
306
+ {loading || sending2FA ? 'Connexion...' : 'Se connecter'}
307
+ </button>
308
+
309
+ {/* Google SSO divider */}
310
+ <div className="my-6 flex items-center gap-3">
311
+ <div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
312
+ <span className="text-xs font-medium text-slate-400 dark:text-slate-500">OU</span>
313
+ <div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
314
+ </div>
315
+
316
+ {/* Google SSO button — intentionally <a> not <Link>, this is an API redirect route */}
317
+ {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
318
+ <a
319
+ href="/api/support/oauth/google?action=login"
320
+ 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"
321
+ >
322
+ <svg className="h-5 w-5" viewBox="0 0 24 24">
323
+ <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" />
324
+ <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" />
325
+ <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" />
326
+ <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" />
327
+ </svg>
328
+ Continuer avec Google
329
+ </a>
330
+ </form>
331
+ )}
332
+
333
+ <div className="mt-6 flex items-center justify-between text-sm">
334
+ <Link href="/support/forgot-password" className="font-medium text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
335
+ Mot de passé oublié ?
336
+ </Link>
337
+ <a href={process.env.NEXT_PUBLIC_SERVER_URL || '/'} className="font-medium text-slate-500 transition-colors hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">
338
+ &larr; Retour au site
339
+ </a>
340
+ </div>
341
+
342
+ <p className="mt-5 text-center text-sm text-slate-500 dark:text-slate-400">
343
+ Pas encore de compte ?{' '}
344
+ <Link href="/support/register" className="font-medium text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
345
+ Créer un compte
346
+ </Link>
347
+ </p>
348
+ </div>
349
+ </div>
350
+ )
351
+ }
@@ -0,0 +1,162 @@
1
+ import React from 'react'
2
+ import Link from 'next/link'
3
+
4
+ const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL || ''
5
+ const supportEmail = process.env.SUPPORT_EMAIL || 'support@example.com'
6
+
7
+ export default function SupportPage() {
8
+ return (
9
+ <div className="flex min-h-screen flex-col bg-white dark:bg-slate-950">
10
+ {/* Header */}
11
+ <header className="border-b border-slate-200 bg-white/80 backdrop-blur-lg dark:border-slate-800 dark:bg-slate-950/80">
12
+ <div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
13
+ <Link href="/" className="flex items-center gap-3 transition-opacity hover:opacity-80">
14
+ <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-blue-600 text-white">
15
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5">
16
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
17
+ </svg>
18
+ </div>
19
+ <span className="text-lg font-semibold text-slate-900 dark:text-white">Support</span>
20
+ </Link>
21
+ <Link
22
+ href="/support/login"
23
+ className="rounded-lg bg-blue-600 px-5 py-2 text-sm font-medium text-white shadow-sm transition-all hover:bg-blue-700 hover:shadow-md active:scale-[0.98]"
24
+ >
25
+ Se connecter
26
+ </Link>
27
+ </div>
28
+ </header>
29
+
30
+ {/* Hero */}
31
+ <main className="flex-1">
32
+ <section className="relative overflow-hidden">
33
+ {/* Subtle gradient background */}
34
+ <div className="absolute inset-0 bg-gradient-to-br from-blue-50 via-white to-indigo-50 dark:from-slate-900 dark:via-slate-950 dark:to-blue-950/30" />
35
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(37,99,235,0.12),transparent)] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(37,99,235,0.15),transparent)]" />
36
+
37
+ <div className="relative mx-auto max-w-5xl px-6 py-20 sm:py-28 lg:py-32 text-center">
38
+ <div className="mx-auto mb-6 inline-flex items-center gap-2 rounded-full border border-blue-200 bg-blue-50 px-4 py-1.5 text-sm font-medium text-blue-700 dark:border-blue-800 dark:bg-blue-950/50 dark:text-blue-300">
39
+ <span className="h-1.5 w-1.5 rounded-full bg-blue-500 animate-pulse" />
40
+ Assistance disponible
41
+ </div>
42
+ <h1 className="text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl lg:text-6xl dark:text-white">
43
+ Espace Support Client
44
+ </h1>
45
+ <p className="mx-auto mt-6 max-w-2xl text-lg text-slate-600 sm:text-xl dark:text-slate-400">
46
+ Suivez vos projets, signalez un problème ou posez une question.
47
+ Notre équipe vous repond sous 24h.
48
+ </p>
49
+ <div className="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
50
+ <Link
51
+ href="/support/login"
52
+ className="inline-flex items-center gap-2 rounded-xl bg-blue-600 px-8 py-3.5 text-base font-medium text-white shadow-lg shadow-blue-600/25 transition-all hover:bg-blue-700 hover:shadow-xl hover:shadow-blue-600/30 active:scale-[0.98] dark:shadow-blue-600/20"
53
+ >
54
+ Accéder à mon espace
55
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-5 w-5">
56
+ <path fillRule="evenodd" d="M3 10a.75.75 0 01.75-.75h10.638L10.23 5.29a.75.75 0 111.04-1.08l5.5 5.25a.75.75 0 010 1.08l-5.5 5.25a.75.75 0 11-1.04-1.08l4.158-3.96H3.75A.75.75 0 013 10z" clipRule="evenodd" />
57
+ </svg>
58
+ </Link>
59
+ <Link
60
+ href="/support/register"
61
+ className="inline-flex items-center rounded-xl border border-slate-300 bg-white px-8 py-3.5 text-base font-medium text-slate-700 shadow-sm transition-all hover:bg-slate-50 hover:shadow-md active:scale-[0.98] dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
62
+ >
63
+ Créer un compte
64
+ </Link>
65
+ </div>
66
+ </div>
67
+ </section>
68
+
69
+ {/* Features */}
70
+ <section className="border-t border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/50">
71
+ <div className="mx-auto max-w-5xl px-6 py-20 sm:py-24">
72
+ <div className="mb-4 text-center">
73
+ <p className="text-sm font-semibold uppercase tracking-wider text-blue-600 dark:text-blue-400">Simple et efficace</p>
74
+ </div>
75
+ <h2 className="mb-4 text-center text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
76
+ Comment ça fonctionne ?
77
+ </h2>
78
+ <p className="mx-auto mb-14 max-w-xl text-center text-slate-500 dark:text-slate-400">
79
+ Trois étapes pour obtenir de l&apos;aide rapidement.
80
+ </p>
81
+ <div className="grid gap-8 sm:grid-cols-3">
82
+ {/* Card 1 */}
83
+ <div className="group rounded-2xl border border-slate-200 bg-white p-8 shadow-sm transition-all hover:shadow-lg hover:-translate-y-0.5 dark:border-slate-700 dark:bg-slate-800">
84
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100 text-blue-600 transition-colors group-hover:bg-blue-600 group-hover:text-white dark:bg-blue-950 dark:text-blue-400 dark:group-hover:bg-blue-600 dark:group-hover:text-white">
85
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6">
86
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
87
+ <polyline points="14 2 14 8 20 8" />
88
+ <line x1="16" y1="13" x2="8" y2="13" />
89
+ <line x1="16" y1="17" x2="8" y2="17" />
90
+ </svg>
91
+ </div>
92
+ <h3 className="mb-2 text-lg font-semibold text-slate-900 dark:text-white">Créez un ticket</h3>
93
+ <p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
94
+ Décrivez votre besoin ou problème. Ajoutez une catégorie et une priorité pour un traitement rapide.
95
+ </p>
96
+ </div>
97
+
98
+ {/* Card 2 */}
99
+ <div className="group rounded-2xl border border-slate-200 bg-white p-8 shadow-sm transition-all hover:shadow-lg hover:-translate-y-0.5 dark:border-slate-700 dark:bg-slate-800">
100
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100 text-blue-600 transition-colors group-hover:bg-blue-600 group-hover:text-white dark:bg-blue-950 dark:text-blue-400 dark:group-hover:bg-blue-600 dark:group-hover:text-white">
101
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6">
102
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
103
+ </svg>
104
+ </div>
105
+ <h3 className="mb-2 text-lg font-semibold text-slate-900 dark:text-white">Échangez avec nous</h3>
106
+ <p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
107
+ Discutez directement avec notre équipe. Chaque message est suivi et archive dans votre espace.
108
+ </p>
109
+ </div>
110
+
111
+ {/* Card 3 */}
112
+ <div className="group rounded-2xl border border-slate-200 bg-white p-8 shadow-sm transition-all hover:shadow-lg hover:-translate-y-0.5 dark:border-slate-700 dark:bg-slate-800">
113
+ <div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100 text-blue-600 transition-colors group-hover:bg-blue-600 group-hover:text-white dark:bg-blue-950 dark:text-blue-400 dark:group-hover:bg-blue-600 dark:group-hover:text-white">
114
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6">
115
+ <circle cx="12" cy="12" r="10" />
116
+ <polyline points="12 6 12 12 16 14" />
117
+ </svg>
118
+ </div>
119
+ <h3 className="mb-2 text-lg font-semibold text-slate-900 dark:text-white">Suivez l&apos;avancement</h3>
120
+ <p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
121
+ Consultez le statut de vos tickets, le temps passé et l&apos;historique complet de vos projets.
122
+ </p>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </section>
127
+
128
+ {/* Email alternative */}
129
+ <section className="border-t border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-950">
130
+ <div className="mx-auto max-w-5xl px-6 py-20 text-center">
131
+ <h2 className="mb-4 text-2xl font-bold tracking-tight text-slate-900 dark:text-white">
132
+ Pas encore de compte ?
133
+ </h2>
134
+ <p className="mx-auto mb-8 max-w-xl text-base text-slate-500 dark:text-slate-400">
135
+ Créez votre compte en quelques secondes pour acceder à votre espace support, ou envoyez un email a{' '}
136
+ <strong className="font-medium text-slate-700 dark:text-slate-300">{supportEmail}</strong>{' '}
137
+ pour une creation automatique.
138
+ </p>
139
+ <div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
140
+ <Link
141
+ href="/support/register"
142
+ className="inline-flex items-center rounded-xl bg-blue-600 px-8 py-3.5 text-base font-medium text-white shadow-lg shadow-blue-600/25 transition-all hover:bg-blue-700 hover:shadow-xl active:scale-[0.98]"
143
+ >
144
+ Créer un compte
145
+ </Link>
146
+ <a
147
+ href={`mailto:${supportEmail}`}
148
+ className="inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-8 py-3.5 text-base font-medium text-slate-700 shadow-sm transition-all hover:bg-slate-50 hover:shadow-md active:scale-[0.98] dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
149
+ >
150
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-5 w-5 text-slate-400">
151
+ <path d="M3 4a2 2 0 00-2 2v1.161l8.441 4.221a1.25 1.25 0 001.118 0L19 7.162V6a2 2 0 00-2-2H3z" />
152
+ <path d="M19 8.839l-7.77 3.885a2.75 2.75 0 01-2.46 0L1 8.839V14a2 2 0 002 2h14a2 2 0 002-2V8.839z" />
153
+ </svg>
154
+ {supportEmail}
155
+ </a>
156
+ </div>
157
+ </div>
158
+ </section>
159
+ </main>
160
+ </div>
161
+ )
162
+ }