@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.
- package/LICENSE +21 -0
- package/README.md +525 -0
- package/dist/client.cjs +7 -0
- package/dist/client.d.cts +3 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +5 -0
- package/dist/index.cjs +7766 -0
- package/dist/index.d.cts +384 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.js +7730 -0
- package/dist/views.d.cts +30 -0
- package/dist/views.d.ts +30 -0
- package/package.json +131 -0
- package/src/client.ts +1 -0
- package/src/collections/AuthLogs.ts +65 -0
- package/src/collections/CannedResponses.ts +69 -0
- package/src/collections/ChatMessages.ts +98 -0
- package/src/collections/EmailLogs.ts +94 -0
- package/src/collections/KnowledgeBase.ts +99 -0
- package/src/collections/Macros.ts +98 -0
- package/src/collections/PendingEmails.ts +122 -0
- package/src/collections/SatisfactionSurveys.ts +98 -0
- package/src/collections/SlaPolicies.ts +123 -0
- package/src/collections/SupportClients.ts +210 -0
- package/src/collections/TicketActivityLog.ts +81 -0
- package/src/collections/TicketMessages.ts +364 -0
- package/src/collections/TicketStatuses.ts +108 -0
- package/src/collections/Tickets.ts +704 -0
- package/src/collections/TimeEntries.ts +105 -0
- package/src/collections/WebhookEndpoints.ts +96 -0
- package/src/collections/index.ts +16 -0
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +85 -0
- package/src/components/TicketConversation/components/ActionPanels.tsx +140 -0
- package/src/components/TicketConversation/components/ActivityLog.tsx +39 -0
- package/src/components/TicketConversation/components/ClientBar.tsx +37 -0
- package/src/components/TicketConversation/components/ClientHistory.tsx +117 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +186 -0
- package/src/components/TicketConversation/components/CodeBlockInserter.tsx +166 -0
- package/src/components/TicketConversation/components/QuickActions.tsx +82 -0
- package/src/components/TicketConversation/components/TicketHeader.tsx +91 -0
- package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +161 -0
- package/src/components/TicketConversation/config.ts +82 -0
- package/src/components/TicketConversation/constants.ts +74 -0
- package/src/components/TicketConversation/context.ts +63 -0
- package/src/components/TicketConversation/hooks/useAI.ts +180 -0
- package/src/components/TicketConversation/hooks/useMessageActions.ts +131 -0
- package/src/components/TicketConversation/hooks/useReply.ts +190 -0
- package/src/components/TicketConversation/hooks/useTicketActions.ts +205 -0
- package/src/components/TicketConversation/hooks/useTimeTracking.ts +107 -0
- package/src/components/TicketConversation/hooks/useTranslation.ts +116 -0
- package/src/components/TicketConversation/index.tsx +1110 -0
- package/src/components/TicketConversation/locales/en.json +878 -0
- package/src/components/TicketConversation/locales/fr.json +878 -0
- package/src/components/TicketConversation/types.ts +54 -0
- package/src/components/TicketConversation/utils.ts +25 -0
- package/src/endpoints/admin-chat-stream.ts +238 -0
- package/src/endpoints/admin-chat.ts +263 -0
- package/src/endpoints/admin-stats.ts +200 -0
- package/src/endpoints/ai.ts +199 -0
- package/src/endpoints/apply-macro.ts +144 -0
- package/src/endpoints/auth-2fa.ts +163 -0
- package/src/endpoints/auto-close.ts +175 -0
- package/src/endpoints/billing.ts +167 -0
- package/src/endpoints/bulk-action.ts +103 -0
- package/src/endpoints/chat-stream.ts +127 -0
- package/src/endpoints/chat.ts +188 -0
- package/src/endpoints/chatbot.ts +113 -0
- package/src/endpoints/delete-account.ts +129 -0
- package/src/endpoints/email-stats.ts +109 -0
- package/src/endpoints/export-csv.ts +84 -0
- package/src/endpoints/export-data.ts +104 -0
- package/src/endpoints/import-conversation.ts +307 -0
- package/src/endpoints/index.ts +154 -0
- package/src/endpoints/login.ts +92 -0
- package/src/endpoints/merge-clients.ts +132 -0
- package/src/endpoints/merge-tickets.ts +137 -0
- package/src/endpoints/oauth-google.ts +179 -0
- package/src/endpoints/pending-emails-process.ts +224 -0
- package/src/endpoints/presence.ts +104 -0
- package/src/endpoints/process-scheduled.ts +144 -0
- package/src/endpoints/purge-logs.ts +58 -0
- package/src/endpoints/resend-notification.ts +99 -0
- package/src/endpoints/round-robin-config.ts +92 -0
- package/src/endpoints/satisfaction.ts +93 -0
- package/src/endpoints/search.ts +106 -0
- package/src/endpoints/seed-kb.ts +153 -0
- package/src/endpoints/settings.ts +144 -0
- package/src/endpoints/signature.ts +93 -0
- package/src/endpoints/sla-check.ts +124 -0
- package/src/endpoints/split-ticket.ts +131 -0
- package/src/endpoints/statuses.ts +45 -0
- package/src/endpoints/track-open.ts +154 -0
- package/src/endpoints/typing.ts +101 -0
- package/src/endpoints/user-prefs.ts +125 -0
- package/src/hooks/checkSLA.ts +414 -0
- package/src/hooks/ticketStatusEmail.ts +182 -0
- package/src/index.ts +51 -0
- package/src/plugin.ts +157 -0
- package/src/portal/LiveChat.tsx +1353 -0
- package/src/portal/auth/ChatWidget.tsx +350 -0
- package/src/portal/auth/ChatbotWidget.tsx +285 -0
- package/src/portal/auth/SupportHeader.tsx +409 -0
- package/src/portal/auth/dashboard/DashboardClient.tsx +650 -0
- package/src/portal/auth/dashboard/page.tsx +84 -0
- package/src/portal/auth/faq/FAQSearch.tsx +117 -0
- package/src/portal/auth/faq/page.tsx +199 -0
- package/src/portal/auth/layout.tsx +61 -0
- package/src/portal/auth/profile/page.tsx +705 -0
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/CollapsibleMessages.tsx +46 -0
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +50 -0
- package/src/portal/auth/tickets/detail/MessageActions.tsx +158 -0
- package/src/portal/auth/tickets/detail/PrintButton.tsx +16 -0
- package/src/portal/auth/tickets/detail/ReadReceipt.tsx +34 -0
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/SatisfactionForm.tsx +156 -0
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +57 -0
- package/src/portal/auth/tickets/detail/TicketReplyForm.tsx +294 -0
- package/src/portal/auth/tickets/detail/TypingIndicator.tsx +58 -0
- package/src/portal/auth/tickets/detail/page.tsx +738 -0
- package/src/portal/auth/tickets/new/page.tsx +515 -0
- package/src/portal/forgot-password/page.tsx +114 -0
- package/src/portal/layout.tsx +26 -0
- package/src/portal/locales/en.json +374 -0
- package/src/portal/locales/fr.json +374 -0
- package/src/portal/login/page.tsx +351 -0
- package/src/portal/page.tsx +162 -0
- package/src/portal/register/page.tsx +281 -0
- package/src/portal/reset-password/page.tsx +152 -0
- package/src/styles/BillingView.module.scss +311 -0
- package/src/styles/ChatView.module.scss +438 -0
- package/src/styles/CommandPalette.module.scss +160 -0
- package/src/styles/CrmView.module.scss +554 -0
- package/src/styles/EmailTracking.module.scss +238 -0
- package/src/styles/ImportConversation.module.scss +267 -0
- package/src/styles/Layout.module.scss +55 -0
- package/src/styles/Logs.module.scss +164 -0
- package/src/styles/NewTicket.module.scss +143 -0
- package/src/styles/PendingEmails.module.scss +629 -0
- package/src/styles/SupportDashboard.module.scss +649 -0
- package/src/styles/TicketDetail.module.scss +1043 -0
- package/src/styles/TicketInbox.module.scss +296 -0
- package/src/styles/TicketingSettings.module.scss +358 -0
- package/src/styles/TimeDashboard.module.scss +287 -0
- package/src/styles/_tokens.scss +78 -0
- package/src/styles/theme.css +633 -0
- package/src/types.ts +255 -0
- package/src/utils/adminNotification.ts +38 -0
- package/src/utils/auth.ts +46 -0
- package/src/utils/emailTemplate.ts +343 -0
- package/src/utils/fireWebhooks.ts +84 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/rateLimiter.ts +52 -0
- package/src/utils/readSettings.ts +67 -0
- package/src/utils/slugs.ts +54 -0
- package/src/utils/webhookDispatcher.ts +120 -0
- package/src/views/BillingView/client.tsx +137 -0
- package/src/views/BillingView/index.tsx +33 -0
- package/src/views/ChatView/client.tsx +294 -0
- package/src/views/ChatView/index.tsx +33 -0
- package/src/views/CrmView/client.tsx +206 -0
- package/src/views/CrmView/index.tsx +33 -0
- package/src/views/EmailTrackingView/client.tsx +124 -0
- package/src/views/EmailTrackingView/index.tsx +33 -0
- package/src/views/ImportConversationView/client.tsx +133 -0
- package/src/views/ImportConversationView/index.tsx +33 -0
- package/src/views/LogsView/client.tsx +151 -0
- package/src/views/LogsView/index.tsx +30 -0
- package/src/views/NewTicketView/client.tsx +227 -0
- package/src/views/NewTicketView/index.tsx +30 -0
- package/src/views/PendingEmailsView/client.tsx +177 -0
- package/src/views/PendingEmailsView/index.tsx +33 -0
- package/src/views/SupportDashboardView/client.tsx +424 -0
- package/src/views/SupportDashboardView/index.tsx +33 -0
- package/src/views/TicketDetailView/client.tsx +775 -0
- package/src/views/TicketDetailView/index.tsx +33 -0
- package/src/views/TicketInboxView/client.tsx +313 -0
- package/src/views/TicketInboxView/index.tsx +30 -0
- package/src/views/TicketingSettingsView/client.tsx +866 -0
- package/src/views/TicketingSettingsView/index.tsx +33 -0
- package/src/views/TimeDashboardView/client.tsx +144 -0
- package/src/views/TimeDashboardView/index.tsx +33 -0
- package/src/views/shared/AdminViewHeader.tsx +69 -0
- package/src/views/shared/ErrorBoundary.tsx +68 -0
- package/src/views/shared/Skeleton.tsx +125 -0
- package/src/views/shared/adminTokens.ts +37 -0
- package/src/views/shared/config.ts +82 -0
- package/src/views/shared/index.ts +6 -0
- package/src/views.ts +16 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
interface ProfileData {
|
|
7
|
+
firstName: string
|
|
8
|
+
lastName: string
|
|
9
|
+
company: string
|
|
10
|
+
phone: string
|
|
11
|
+
email: string
|
|
12
|
+
notifyOnReply: boolean
|
|
13
|
+
notifyOnStatusChange: boolean
|
|
14
|
+
twoFactorEnabled: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function ProfilePage() {
|
|
18
|
+
const router = useRouter()
|
|
19
|
+
const [profile, setProfile] = useState<ProfileData | null>(null)
|
|
20
|
+
const [loading, setLoading] = useState(true)
|
|
21
|
+
const [saving, setSaving] = useState(false)
|
|
22
|
+
const [success, setSuccess] = useState('')
|
|
23
|
+
const [error, setError] = useState('')
|
|
24
|
+
|
|
25
|
+
// Account deletion
|
|
26
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
|
27
|
+
const [deletePassword, setDeletePassword] = useState('')
|
|
28
|
+
const [deleting, setDeleting] = useState(false)
|
|
29
|
+
const [deleteError, setDeleteError] = useState('')
|
|
30
|
+
|
|
31
|
+
// Password change
|
|
32
|
+
const [currentPassword, setCurrentPassword] = useState('')
|
|
33
|
+
const [newPassword, setNewPassword] = useState('')
|
|
34
|
+
const [confirmPassword, setConfirmPassword] = useState('')
|
|
35
|
+
const [passwordError, setPasswordError] = useState('')
|
|
36
|
+
const [passwordSuccess, setPasswordSuccess] = useState('')
|
|
37
|
+
const [changingPassword, setChangingPassword] = useState(false)
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const fetchProfile = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch('/api/support-clients/me', { credentials: 'include' })
|
|
43
|
+
if (res.ok) {
|
|
44
|
+
const data = await res.json()
|
|
45
|
+
setProfile({
|
|
46
|
+
firstName: data.user?.firstName || '',
|
|
47
|
+
lastName: data.user?.lastName || '',
|
|
48
|
+
company: data.user?.company || '',
|
|
49
|
+
phone: data.user?.phone || '',
|
|
50
|
+
email: data.user?.email || '',
|
|
51
|
+
notifyOnReply: data.user?.notifyOnReply !== false,
|
|
52
|
+
notifyOnStatusChange: data.user?.notifyOnStatusChange !== false,
|
|
53
|
+
twoFactorEnabled: data.user?.twoFactorEnabled === true,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
setError('Impossible de charger le profil.')
|
|
58
|
+
} finally {
|
|
59
|
+
setLoading(false)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
fetchProfile()
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
const handleSaveProfile = async (e: React.FormEvent) => {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
if (!profile) return
|
|
68
|
+
setError('')
|
|
69
|
+
setSuccess('')
|
|
70
|
+
setSaving(true)
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Get user ID first
|
|
74
|
+
const meRes = await fetch('/api/support-clients/me', { credentials: 'include' })
|
|
75
|
+
const meData = await meRes.json()
|
|
76
|
+
const userId = meData.user?.id
|
|
77
|
+
|
|
78
|
+
if (!userId) {
|
|
79
|
+
setError('Session expirée. Veuillez vous reconnecter.')
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const res = await fetch(`/api/support-clients/${userId}`, {
|
|
84
|
+
method: 'PATCH',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
credentials: 'include',
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
firstName: profile.firstName,
|
|
89
|
+
lastName: profile.lastName,
|
|
90
|
+
company: profile.company,
|
|
91
|
+
phone: profile.phone,
|
|
92
|
+
notifyOnReply: profile.notifyOnReply,
|
|
93
|
+
notifyOnStatusChange: profile.notifyOnStatusChange,
|
|
94
|
+
twoFactorEnabled: profile.twoFactorEnabled,
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (res.ok) {
|
|
99
|
+
setSuccess('Profil mis a jour avec succes.')
|
|
100
|
+
router.refresh()
|
|
101
|
+
} else {
|
|
102
|
+
const data = await res.json()
|
|
103
|
+
setError(data.errors?.[0]?.message || 'Erreur lors de la mise a jour.')
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
setError('Erreur de connexion.')
|
|
107
|
+
} finally {
|
|
108
|
+
setSaving(false)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const handleChangePassword = async (e: React.FormEvent) => {
|
|
113
|
+
e.preventDefault()
|
|
114
|
+
setPasswordError('')
|
|
115
|
+
setPasswordSuccess('')
|
|
116
|
+
|
|
117
|
+
if (newPassword.length < 8) {
|
|
118
|
+
setPasswordError('Le mot de passe doit faire au moins 8 caracteres.')
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (newPassword !== confirmPassword) {
|
|
123
|
+
setPasswordError('Les mots de passe ne correspondent pas.')
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setChangingPassword(true)
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
// Verify current password by logging in
|
|
131
|
+
const loginRes = await fetch('/api/support-clients/login', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { 'Content-Type': 'application/json' },
|
|
134
|
+
credentials: 'include',
|
|
135
|
+
body: JSON.stringify({ email: profile?.email, password: currentPassword }),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if (!loginRes.ok) {
|
|
139
|
+
setPasswordError('Mot de passe actuel incorrect.')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Get user ID
|
|
144
|
+
const meRes = await fetch('/api/support-clients/me', { credentials: 'include' })
|
|
145
|
+
const meData = await meRes.json()
|
|
146
|
+
const userId = meData.user?.id
|
|
147
|
+
|
|
148
|
+
// Update password
|
|
149
|
+
const res = await fetch(`/api/support-clients/${userId}`, {
|
|
150
|
+
method: 'PATCH',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
credentials: 'include',
|
|
153
|
+
body: JSON.stringify({ password: newPassword }),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
if (res.ok) {
|
|
157
|
+
setPasswordSuccess('Mot de passe modifie avec succes.')
|
|
158
|
+
setCurrentPassword('')
|
|
159
|
+
setNewPassword('')
|
|
160
|
+
setConfirmPassword('')
|
|
161
|
+
} else {
|
|
162
|
+
setPasswordError('Erreur lors du changement de mot de passe.')
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
setPasswordError('Erreur de connexion.')
|
|
166
|
+
} finally {
|
|
167
|
+
setChangingPassword(false)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (loading) {
|
|
172
|
+
return (
|
|
173
|
+
<div className="flex flex-col items-center justify-center py-24">
|
|
174
|
+
<div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-200 border-t-blue-600 dark:border-slate-700 dark:border-t-blue-400" />
|
|
175
|
+
<p className="mt-4 text-sm font-medium text-slate-400 dark:text-slate-500">Chargement du profil...</p>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!profile) return null
|
|
181
|
+
|
|
182
|
+
const initials = `${profile.firstName.charAt(0)}${profile.lastName.charAt(0)}`.toUpperCase()
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div className="mx-auto max-w-3xl px-4 pb-12">
|
|
186
|
+
{/* Back navigation */}
|
|
187
|
+
<button
|
|
188
|
+
onClick={() => router.push('/support/dashboard')}
|
|
189
|
+
className="group mb-8 inline-flex items-center gap-2 text-sm font-medium text-slate-500 dark:text-slate-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
|
190
|
+
>
|
|
191
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 transition-transform group-hover:-translate-x-0.5">
|
|
192
|
+
<path fillRule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" clipRule="evenodd" />
|
|
193
|
+
</svg>
|
|
194
|
+
Retour aux tickets
|
|
195
|
+
</button>
|
|
196
|
+
|
|
197
|
+
{/* Profile header */}
|
|
198
|
+
<div className="mb-8 flex items-center gap-5">
|
|
199
|
+
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 text-xl font-bold text-white shadow-lg shadow-blue-500/20">
|
|
200
|
+
{initials}
|
|
201
|
+
</div>
|
|
202
|
+
<div>
|
|
203
|
+
<h1 className="text-2xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-3xl">
|
|
204
|
+
Mon profil
|
|
205
|
+
</h1>
|
|
206
|
+
<p className="mt-0.5 text-sm text-slate-500 dark:text-slate-400">
|
|
207
|
+
Gerez vos informations personnelles et vos preferences
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div className="space-y-6">
|
|
213
|
+
{/* ── Personal info card ── */}
|
|
214
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm">
|
|
215
|
+
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
216
|
+
<div className="flex items-center gap-3">
|
|
217
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 dark:bg-blue-900/30">
|
|
218
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-blue-600 dark:text-blue-400">
|
|
219
|
+
<path d="M10 8a3 3 0 100-6 3 3 0 000 6zM3.465 14.493a1.23 1.23 0 00.41 1.412A9.957 9.957 0 0010 18c2.31 0 4.438-.784 6.131-2.1.43-.333.604-.903.408-1.41a7.002 7.002 0 00-13.074.003z" />
|
|
220
|
+
</svg>
|
|
221
|
+
</div>
|
|
222
|
+
<h2 className="text-base font-semibold text-slate-900 dark:text-white">Informations personnelles</h2>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<form onSubmit={handleSaveProfile} className="p-6 sm:p-8">
|
|
227
|
+
{/* Alerts */}
|
|
228
|
+
{error && (
|
|
229
|
+
<div className="mb-6 flex items-start gap-3 rounded-xl border border-red-200 dark:border-red-800/50 bg-red-50 dark:bg-red-900/20 px-4 py-3.5">
|
|
230
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="mt-0.5 h-5 w-5 flex-shrink-0 text-red-500">
|
|
231
|
+
<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" />
|
|
232
|
+
</svg>
|
|
233
|
+
<p className="text-sm font-medium text-red-700 dark:text-red-400">{error}</p>
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
{success && (
|
|
237
|
+
<div className="mb-6 flex items-start gap-3 rounded-xl border border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 px-4 py-3.5">
|
|
238
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="mt-0.5 h-5 w-5 flex-shrink-0 text-emerald-500">
|
|
239
|
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clipRule="evenodd" />
|
|
240
|
+
</svg>
|
|
241
|
+
<p className="text-sm font-medium text-emerald-700 dark:text-emerald-400">{success}</p>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{/* Name row */}
|
|
246
|
+
<div className="mb-5 grid grid-cols-1 gap-5 sm:grid-cols-2">
|
|
247
|
+
<div>
|
|
248
|
+
<label htmlFor="firstName" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
249
|
+
Prenom
|
|
250
|
+
</label>
|
|
251
|
+
<input
|
|
252
|
+
id="firstName"
|
|
253
|
+
type="text"
|
|
254
|
+
required
|
|
255
|
+
value={profile.firstName}
|
|
256
|
+
onChange={(e) => setProfile({ ...profile, firstName: e.target.value })}
|
|
257
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
<div>
|
|
261
|
+
<label htmlFor="lastName" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
262
|
+
Nom
|
|
263
|
+
</label>
|
|
264
|
+
<input
|
|
265
|
+
id="lastName"
|
|
266
|
+
type="text"
|
|
267
|
+
required
|
|
268
|
+
value={profile.lastName}
|
|
269
|
+
onChange={(e) => setProfile({ ...profile, lastName: e.target.value })}
|
|
270
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{/* Company */}
|
|
276
|
+
<div className="mb-5">
|
|
277
|
+
<label htmlFor="company" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
278
|
+
Entreprise
|
|
279
|
+
</label>
|
|
280
|
+
<input
|
|
281
|
+
id="company"
|
|
282
|
+
type="text"
|
|
283
|
+
required
|
|
284
|
+
value={profile.company}
|
|
285
|
+
onChange={(e) => setProfile({ ...profile, company: e.target.value })}
|
|
286
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
{/* Phone */}
|
|
291
|
+
<div className="mb-5">
|
|
292
|
+
<label htmlFor="phone" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
293
|
+
Telephone
|
|
294
|
+
</label>
|
|
295
|
+
<input
|
|
296
|
+
id="phone"
|
|
297
|
+
type="tel"
|
|
298
|
+
value={profile.phone}
|
|
299
|
+
onChange={(e) => setProfile({ ...profile, phone: e.target.value })}
|
|
300
|
+
placeholder="+33 6 00 00 00 00"
|
|
301
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Email (read-only) */}
|
|
306
|
+
<div className="mb-6">
|
|
307
|
+
<label className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">Email</label>
|
|
308
|
+
<div className="flex items-center gap-3 rounded-xl border border-slate-100 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 px-4 py-3">
|
|
309
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 flex-shrink-0 text-slate-400">
|
|
310
|
+
<path fillRule="evenodd" d="M10 1a4.5 4.5 0 00-4.5 4.5V9H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-.5V5.5A4.5 4.5 0 0010 1zm3 8V5.5a3 3 0 10-6 0V9h6z" clipRule="evenodd" />
|
|
311
|
+
</svg>
|
|
312
|
+
<span className="text-sm text-slate-500 dark:text-slate-400">{profile.email}</span>
|
|
313
|
+
</div>
|
|
314
|
+
<p className="mt-1.5 text-xs text-slate-400 dark:text-slate-500">L'email ne peut pas etre modifie. Contactez le support si besoin.</p>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<button
|
|
318
|
+
type="submit"
|
|
319
|
+
disabled={saving}
|
|
320
|
+
className="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-blue-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
321
|
+
>
|
|
322
|
+
{saving ? (
|
|
323
|
+
<>
|
|
324
|
+
<svg className="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
325
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
326
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
327
|
+
</svg>
|
|
328
|
+
Enregistrement...
|
|
329
|
+
</>
|
|
330
|
+
) : (
|
|
331
|
+
'Enregistrer les modifications'
|
|
332
|
+
)}
|
|
333
|
+
</button>
|
|
334
|
+
</form>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{/* ── Security & 2FA card ── */}
|
|
338
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm">
|
|
339
|
+
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
340
|
+
<div className="flex items-center gap-3">
|
|
341
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-amber-50 dark:bg-amber-900/30">
|
|
342
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-amber-600 dark:text-amber-400">
|
|
343
|
+
<path fillRule="evenodd" d="M10 1a4.5 4.5 0 00-4.5 4.5V9H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-.5V5.5A4.5 4.5 0 0010 1zm3 8V5.5a3 3 0 10-6 0V9h6z" clipRule="evenodd" />
|
|
344
|
+
</svg>
|
|
345
|
+
</div>
|
|
346
|
+
<h2 className="text-base font-semibold text-slate-900 dark:text-white">Securite</h2>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<div className="p-6 sm:p-8">
|
|
351
|
+
{/* 2FA toggle */}
|
|
352
|
+
<div className="flex items-start justify-between gap-4 rounded-xl border border-slate-100 dark:border-slate-700/50 bg-slate-50/50 dark:bg-slate-900/30 p-4">
|
|
353
|
+
<div className="flex items-start gap-3">
|
|
354
|
+
<div className="mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-50 dark:bg-blue-900/30">
|
|
355
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-blue-600 dark:text-blue-400">
|
|
356
|
+
<path fillRule="evenodd" d="M10 2.5c-1.31 0-2.526.386-3.546 1.051a.75.75 0 01-.908-1.194A8.459 8.459 0 0110 1c1.51 0 2.934.398 4.16 1.094.71.404.607 1.258-.146 1.258-.017 0-.22-.109-.268-.132A6.947 6.947 0 0010 2.5zM5.25 10c0-.98.12-1.933.347-2.845a.75.75 0 10-1.452-.376A10.477 10.477 0 003.75 10c0 .98.12 1.933.347 2.845a.75.75 0 101.452-.376A8.953 8.953 0 015.25 10zm9.5 0c0 .98-.12 1.933-.347 2.845a.75.75 0 101.452.376c.246-.98.395-2 .395-3.221s-.149-2.241-.395-3.221a.75.75 0 10-1.452.376c.227.912.347 1.865.347 2.845zM10 17.5c1.31 0 2.526-.386 3.546-1.051a.75.75 0 01.908 1.194A8.459 8.459 0 0110 19c-1.51 0-2.934-.398-4.16-1.094-.71-.404-.607-1.258.146-1.258.017 0 .22.109.268.132A6.947 6.947 0 0010 17.5zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
|
|
357
|
+
</svg>
|
|
358
|
+
</div>
|
|
359
|
+
<div>
|
|
360
|
+
<p className="text-sm font-semibold text-slate-900 dark:text-white">Verification en deux etapes (2FA)</p>
|
|
361
|
+
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">Recevoir un code par email a chaque connexion pour plus de securite</p>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
onClick={() => setProfile({ ...profile, twoFactorEnabled: !profile.twoFactorEnabled })}
|
|
367
|
+
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-500/20 ${
|
|
368
|
+
profile.twoFactorEnabled ? 'bg-blue-600' : 'bg-slate-200 dark:bg-slate-600'
|
|
369
|
+
}`}
|
|
370
|
+
role="switch"
|
|
371
|
+
aria-checked={profile.twoFactorEnabled}
|
|
372
|
+
>
|
|
373
|
+
<span
|
|
374
|
+
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
|
375
|
+
profile.twoFactorEnabled ? 'translate-x-5' : 'translate-x-0'
|
|
376
|
+
}`}
|
|
377
|
+
/>
|
|
378
|
+
</button>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* ── Notifications card ── */}
|
|
384
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm">
|
|
385
|
+
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
386
|
+
<div className="flex items-center gap-3">
|
|
387
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-50 dark:bg-violet-900/30">
|
|
388
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-violet-600 dark:text-violet-400">
|
|
389
|
+
<path fillRule="evenodd" d="M10 2a6 6 0 00-6 6c0 1.887-.454 3.665-1.257 5.234a.75.75 0 00.515 1.076 32.91 32.91 0 003.256.508 3.5 3.5 0 006.972 0 32.903 32.903 0 003.256-.508.75.75 0 00.515-1.076A11.448 11.448 0 0116 8a6 6 0 00-6-6zM8.05 14.943a33.54 33.54 0 003.9 0 2 2 0 01-3.9 0z" clipRule="evenodd" />
|
|
390
|
+
</svg>
|
|
391
|
+
</div>
|
|
392
|
+
<h2 className="text-base font-semibold text-slate-900 dark:text-white">Notifications par email</h2>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div className="divide-y divide-slate-100 dark:divide-slate-700/50">
|
|
397
|
+
{/* Reply notifications */}
|
|
398
|
+
<div className="flex items-center justify-between gap-4 px-6 py-4 sm:px-8">
|
|
399
|
+
<div>
|
|
400
|
+
<p className="text-sm font-semibold text-slate-900 dark:text-white">Reponses du support</p>
|
|
401
|
+
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">Recevoir un email a chaque reponse sur mes tickets</p>
|
|
402
|
+
</div>
|
|
403
|
+
<button
|
|
404
|
+
type="button"
|
|
405
|
+
onClick={() => setProfile({ ...profile, notifyOnReply: !profile.notifyOnReply })}
|
|
406
|
+
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-500/20 ${
|
|
407
|
+
profile.notifyOnReply ? 'bg-blue-600' : 'bg-slate-200 dark:bg-slate-600'
|
|
408
|
+
}`}
|
|
409
|
+
role="switch"
|
|
410
|
+
aria-checked={profile.notifyOnReply}
|
|
411
|
+
>
|
|
412
|
+
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
|
413
|
+
profile.notifyOnReply ? 'translate-x-5' : 'translate-x-0'
|
|
414
|
+
}`} />
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
{/* Status change notifications */}
|
|
419
|
+
<div className="flex items-center justify-between gap-4 px-6 py-4 sm:px-8">
|
|
420
|
+
<div>
|
|
421
|
+
<p className="text-sm font-semibold text-slate-900 dark:text-white">Changements de statut</p>
|
|
422
|
+
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">Recevoir un email quand un ticket est resolu ou ferme</p>
|
|
423
|
+
</div>
|
|
424
|
+
<button
|
|
425
|
+
type="button"
|
|
426
|
+
onClick={() => setProfile({ ...profile, notifyOnStatusChange: !profile.notifyOnStatusChange })}
|
|
427
|
+
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-500/20 ${
|
|
428
|
+
profile.notifyOnStatusChange ? 'bg-blue-600' : 'bg-slate-200 dark:bg-slate-600'
|
|
429
|
+
}`}
|
|
430
|
+
role="switch"
|
|
431
|
+
aria-checked={profile.notifyOnStatusChange}
|
|
432
|
+
>
|
|
433
|
+
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
|
434
|
+
profile.notifyOnStatusChange ? 'translate-x-5' : 'translate-x-0'
|
|
435
|
+
}`} />
|
|
436
|
+
</button>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
{/* Save button for notifications + 2FA */}
|
|
441
|
+
<div className="border-t border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
442
|
+
<button
|
|
443
|
+
type="button"
|
|
444
|
+
onClick={handleSaveProfile}
|
|
445
|
+
disabled={saving}
|
|
446
|
+
className="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-6 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-blue-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
447
|
+
>
|
|
448
|
+
{saving ? 'Enregistrement...' : 'Sauvegarder les preferences'}
|
|
449
|
+
</button>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
{/* ── Password change card ── */}
|
|
454
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm">
|
|
455
|
+
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
456
|
+
<div className="flex items-center gap-3">
|
|
457
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-100 dark:bg-slate-700">
|
|
458
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-slate-600 dark:text-slate-400">
|
|
459
|
+
<path fillRule="evenodd" d="M8 7a5 5 0 113.61 4.804l-1.903 1.903A1 1 0 019 14H8v1a1 1 0 01-1 1H6v1a1 1 0 01-1 1H3a1 1 0 01-1-1v-2a1 1 0 01.293-.707L8.196 8.39A5.002 5.002 0 018 7zm5-3a.75.75 0 000 1.5A1.5 1.5 0 0114.5 7 .75.75 0 0016 7a3 3 0 00-3-3z" clipRule="evenodd" />
|
|
460
|
+
</svg>
|
|
461
|
+
</div>
|
|
462
|
+
<h2 className="text-base font-semibold text-slate-900 dark:text-white">Changer le mot de passe</h2>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<form onSubmit={handleChangePassword} className="p-6 sm:p-8">
|
|
467
|
+
{passwordError && (
|
|
468
|
+
<div className="mb-6 flex items-start gap-3 rounded-xl border border-red-200 dark:border-red-800/50 bg-red-50 dark:bg-red-900/20 px-4 py-3.5">
|
|
469
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="mt-0.5 h-5 w-5 flex-shrink-0 text-red-500">
|
|
470
|
+
<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" />
|
|
471
|
+
</svg>
|
|
472
|
+
<p className="text-sm font-medium text-red-700 dark:text-red-400">{passwordError}</p>
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
{passwordSuccess && (
|
|
476
|
+
<div className="mb-6 flex items-start gap-3 rounded-xl border border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 px-4 py-3.5">
|
|
477
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="mt-0.5 h-5 w-5 flex-shrink-0 text-emerald-500">
|
|
478
|
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clipRule="evenodd" />
|
|
479
|
+
</svg>
|
|
480
|
+
<p className="text-sm font-medium text-emerald-700 dark:text-emerald-400">{passwordSuccess}</p>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
|
|
484
|
+
<div className="mb-5">
|
|
485
|
+
<label htmlFor="currentPassword" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
486
|
+
Mot de passe actuel
|
|
487
|
+
</label>
|
|
488
|
+
<input
|
|
489
|
+
id="currentPassword"
|
|
490
|
+
type="password"
|
|
491
|
+
required
|
|
492
|
+
value={currentPassword}
|
|
493
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
494
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
495
|
+
/>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
<div className="mb-5">
|
|
499
|
+
<label htmlFor="newPassword" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
500
|
+
Nouveau mot de passe
|
|
501
|
+
</label>
|
|
502
|
+
<input
|
|
503
|
+
id="newPassword"
|
|
504
|
+
type="password"
|
|
505
|
+
required
|
|
506
|
+
minLength={8}
|
|
507
|
+
value={newPassword}
|
|
508
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
509
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
510
|
+
/>
|
|
511
|
+
<p className="mt-1.5 text-xs text-slate-400 dark:text-slate-500">Minimum 8 caracteres</p>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<div className="mb-6">
|
|
515
|
+
<label htmlFor="confirmPassword" className="mb-2 block text-sm font-semibold text-slate-700 dark:text-slate-200">
|
|
516
|
+
Confirmer le mot de passe
|
|
517
|
+
</label>
|
|
518
|
+
<input
|
|
519
|
+
id="confirmPassword"
|
|
520
|
+
type="password"
|
|
521
|
+
required
|
|
522
|
+
value={confirmPassword}
|
|
523
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
524
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm text-slate-900 dark:text-white outline-none transition-all duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
525
|
+
/>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<button
|
|
529
|
+
type="submit"
|
|
530
|
+
disabled={changingPassword}
|
|
531
|
+
className="inline-flex items-center justify-center gap-2 rounded-xl bg-slate-900 dark:bg-slate-100 px-6 py-3 text-sm font-semibold text-white dark:text-slate-900 shadow-sm transition-all duration-200 hover:bg-slate-800 dark:hover:bg-white hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
532
|
+
>
|
|
533
|
+
{changingPassword ? (
|
|
534
|
+
<>
|
|
535
|
+
<svg className="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
536
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
537
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
538
|
+
</svg>
|
|
539
|
+
Modification...
|
|
540
|
+
</>
|
|
541
|
+
) : (
|
|
542
|
+
'Changer le mot de passe'
|
|
543
|
+
)}
|
|
544
|
+
</button>
|
|
545
|
+
</form>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
{/* ── RGPD Data Export card ── */}
|
|
549
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm">
|
|
550
|
+
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
551
|
+
<div className="flex items-center gap-3">
|
|
552
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-50 dark:bg-emerald-900/30">
|
|
553
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-emerald-600 dark:text-emerald-400">
|
|
554
|
+
<path d="M10.75 2.75a.75.75 0 00-1.5 0v8.614L6.295 8.235a.75.75 0 10-1.09 1.03l4.25 4.5a.75.75 0 001.09 0l4.25-4.5a.75.75 0 00-1.09-1.03l-2.955 3.129V2.75z" />
|
|
555
|
+
<path d="M3.5 12.75a.75.75 0 00-1.5 0v2.5A2.75 2.75 0 004.75 18h10.5A2.75 2.75 0 0018 15.25v-2.5a.75.75 0 00-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5z" />
|
|
556
|
+
</svg>
|
|
557
|
+
</div>
|
|
558
|
+
<h2 className="text-base font-semibold text-slate-900 dark:text-white">Mes données personnelles</h2>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div className="p-6 sm:p-8">
|
|
563
|
+
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
|
|
564
|
+
Conformement au RGPD, vous pouvez télécharger l'ensemble de vos données personnelles
|
|
565
|
+
(profil, tickets, messages, enquetes de satisfaction).
|
|
566
|
+
</p>
|
|
567
|
+
<a
|
|
568
|
+
href="/api/support/export-data"
|
|
569
|
+
download
|
|
570
|
+
className="inline-flex items-center gap-2 rounded-xl border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-5 py-2.5 text-sm font-semibold text-slate-700 dark:text-slate-300 transition-all duration-200 hover:border-emerald-300 dark:hover:border-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 hover:text-emerald-700 dark:hover:text-emerald-400"
|
|
571
|
+
>
|
|
572
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4">
|
|
573
|
+
<path d="M10.75 2.75a.75.75 0 00-1.5 0v8.614L6.295 8.235a.75.75 0 10-1.09 1.03l4.25 4.5a.75.75 0 001.09 0l4.25-4.5a.75.75 0 00-1.09-1.03l-2.955 3.129V2.75z" />
|
|
574
|
+
<path d="M3.5 12.75a.75.75 0 00-1.5 0v2.5A2.75 2.75 0 004.75 18h10.5A2.75 2.75 0 0018 15.25v-2.5a.75.75 0 00-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5z" />
|
|
575
|
+
</svg>
|
|
576
|
+
Exporter mes données (JSON)
|
|
577
|
+
</a>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
{/* ── Danger zone — Account Deletion ── */}
|
|
582
|
+
<div className="rounded-2xl border border-red-200 dark:border-red-800/30 bg-white dark:bg-slate-800/50 shadow-sm">
|
|
583
|
+
<div className="border-b border-red-100 dark:border-red-800/20 px-6 py-4 sm:px-8">
|
|
584
|
+
<div className="flex items-center gap-3">
|
|
585
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-red-50 dark:bg-red-900/30">
|
|
586
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-red-500">
|
|
587
|
+
<path fillRule="evenodd" d="M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.52.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z" clipRule="evenodd" />
|
|
588
|
+
</svg>
|
|
589
|
+
</div>
|
|
590
|
+
<div>
|
|
591
|
+
<h2 className="text-base font-semibold text-red-700 dark:text-red-400">Zone de danger</h2>
|
|
592
|
+
<p className="text-xs text-slate-500 dark:text-slate-400">Actions irréversibles</p>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
|
|
597
|
+
<div className="p-6 sm:p-8">
|
|
598
|
+
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400 leading-relaxed">
|
|
599
|
+
Conformement au RGPD (Article 17 — Droit a l'effacement), vous pouvez demander la suppression
|
|
600
|
+
définitive de votre compte et de toutes vos données. Cette action est irréversible.
|
|
601
|
+
</p>
|
|
602
|
+
|
|
603
|
+
{!showDeleteConfirm ? (
|
|
604
|
+
<button
|
|
605
|
+
onClick={() => setShowDeleteConfirm(true)}
|
|
606
|
+
className="inline-flex items-center gap-2 rounded-xl border border-red-200 dark:border-red-800/50 bg-white dark:bg-slate-800 px-5 py-2.5 text-sm font-semibold text-red-600 dark:text-red-400 transition-all duration-200 hover:bg-red-50 dark:hover:bg-red-900/20 hover:border-red-300"
|
|
607
|
+
>
|
|
608
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4">
|
|
609
|
+
<path fillRule="evenodd" d="M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.52.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z" clipRule="evenodd" />
|
|
610
|
+
</svg>
|
|
611
|
+
Supprimer mon compte
|
|
612
|
+
</button>
|
|
613
|
+
) : (
|
|
614
|
+
<div className="rounded-xl border border-red-200 dark:border-red-800/30 bg-red-50/50 dark:bg-red-900/10 p-5">
|
|
615
|
+
<p className="mb-3 text-sm font-semibold text-red-700 dark:text-red-400">
|
|
616
|
+
Cette action supprimera definitivement :
|
|
617
|
+
</p>
|
|
618
|
+
<ul className="mb-4 space-y-1.5 text-sm text-red-600 dark:text-red-400">
|
|
619
|
+
<li className="flex items-center gap-2">
|
|
620
|
+
<span className="h-1 w-1 flex-shrink-0 rounded-full bg-red-400" />
|
|
621
|
+
Votre compte et profil
|
|
622
|
+
</li>
|
|
623
|
+
<li className="flex items-center gap-2">
|
|
624
|
+
<span className="h-1 w-1 flex-shrink-0 rounded-full bg-red-400" />
|
|
625
|
+
Tous vos tickets et messages
|
|
626
|
+
</li>
|
|
627
|
+
<li className="flex items-center gap-2">
|
|
628
|
+
<span className="h-1 w-1 flex-shrink-0 rounded-full bg-red-400" />
|
|
629
|
+
Vos enquetes de satisfaction
|
|
630
|
+
</li>
|
|
631
|
+
<li className="flex items-center gap-2">
|
|
632
|
+
<span className="h-1 w-1 flex-shrink-0 rounded-full bg-red-400" />
|
|
633
|
+
Vos conversations de chat
|
|
634
|
+
</li>
|
|
635
|
+
</ul>
|
|
636
|
+
|
|
637
|
+
{deleteError && (
|
|
638
|
+
<div className="mb-4 flex items-start gap-3 rounded-lg border border-red-200 dark:border-red-800/50 bg-red-50 dark:bg-red-900/20 px-4 py-2.5">
|
|
639
|
+
<p className="text-sm font-medium text-red-700 dark:text-red-400">{deleteError}</p>
|
|
640
|
+
</div>
|
|
641
|
+
)}
|
|
642
|
+
|
|
643
|
+
<div className="mb-4">
|
|
644
|
+
<label htmlFor="deletePassword" className="mb-2 block text-sm font-semibold text-red-700 dark:text-red-400">
|
|
645
|
+
Confirmez avec votre mot de passe
|
|
646
|
+
</label>
|
|
647
|
+
<input
|
|
648
|
+
id="deletePassword"
|
|
649
|
+
type="password"
|
|
650
|
+
required
|
|
651
|
+
value={deletePassword}
|
|
652
|
+
onChange={(e) => setDeletePassword(e.target.value)}
|
|
653
|
+
placeholder="Votre mot de passe"
|
|
654
|
+
className="w-full rounded-xl border border-red-200 dark:border-red-700 bg-white dark:bg-slate-900 px-4 py-3 text-sm text-slate-900 dark:text-white placeholder:text-slate-400 outline-none transition-all focus:border-red-500 focus:ring-4 focus:ring-red-500/10"
|
|
655
|
+
/>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<div className="flex gap-3">
|
|
659
|
+
<button
|
|
660
|
+
onClick={async () => {
|
|
661
|
+
if (!deletePassword) {
|
|
662
|
+
setDeleteError('Veuillez entrer votre mot de passe.')
|
|
663
|
+
return
|
|
664
|
+
}
|
|
665
|
+
setDeleting(true)
|
|
666
|
+
setDeleteError('')
|
|
667
|
+
try {
|
|
668
|
+
const res = await fetch('/api/support/delete-account', {
|
|
669
|
+
method: 'POST',
|
|
670
|
+
headers: { 'Content-Type': 'application/json' },
|
|
671
|
+
credentials: 'include',
|
|
672
|
+
body: JSON.stringify({ confirmPassword: deletePassword }),
|
|
673
|
+
})
|
|
674
|
+
const data = await res.json()
|
|
675
|
+
if (res.ok && data.deleted) {
|
|
676
|
+
router.push('/support/login?deleted=true')
|
|
677
|
+
} else {
|
|
678
|
+
setDeleteError(data.error || 'Erreur lors de la suppression.')
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
setDeleteError('Erreur de connexion.')
|
|
682
|
+
} finally {
|
|
683
|
+
setDeleting(false)
|
|
684
|
+
}
|
|
685
|
+
}}
|
|
686
|
+
disabled={deleting || !deletePassword}
|
|
687
|
+
className="rounded-xl bg-red-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-red-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
688
|
+
>
|
|
689
|
+
{deleting ? 'Suppression...' : 'Confirmer la suppression'}
|
|
690
|
+
</button>
|
|
691
|
+
<button
|
|
692
|
+
onClick={() => { setShowDeleteConfirm(false); setDeletePassword(''); setDeleteError('') }}
|
|
693
|
+
className="rounded-xl border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-5 py-2.5 text-sm font-semibold text-slate-600 dark:text-slate-300 transition-all duration-200 hover:bg-slate-50 dark:hover:bg-slate-700"
|
|
694
|
+
>
|
|
695
|
+
Annuler
|
|
696
|
+
</button>
|
|
697
|
+
</div>
|
|
698
|
+
</div>
|
|
699
|
+
)}
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
)
|
|
705
|
+
}
|