@consilioweb/payload-support 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/components/TicketConversation/components/AISummaryPanel.cjs +22 -14
- package/dist/components/TicketConversation/components/AISummaryPanel.js +23 -16
- package/dist/components/TicketConversation/components/ActionPanels.cjs +5 -5
- package/dist/components/TicketConversation/components/ActionPanels.js +5 -5
- package/dist/components/TicketConversation/components/ClientBar.cjs +1 -1
- package/dist/components/TicketConversation/components/ClientBar.js +1 -1
- package/dist/components/TicketConversation/components/QuickActions.cjs +2 -2
- package/dist/components/TicketConversation/components/QuickActions.js +2 -2
- package/dist/components/TicketConversation/components/TicketHeader.cjs +1 -1
- package/dist/components/TicketConversation/components/TicketHeader.js +1 -1
- package/dist/components/TicketConversation/index.cjs +10 -10
- package/dist/components/TicketConversation/index.js +10 -10
- package/dist/index.cjs +710 -174
- package/dist/index.d.cts +165 -3
- package/dist/index.d.ts +165 -3
- package/dist/index.js +703 -176
- package/dist/styles/ChatView.module.scss +1 -1
- package/dist/styles/TicketDetail.module.scss +3 -3
- package/dist/styles/_tokens.scss +1 -1
- package/dist/styles/theme.css +6 -6
- package/dist/views/TicketingSettingsView/client.cjs +3 -3
- package/dist/views/TicketingSettingsView/client.js +3 -3
- package/package.json +9 -9
- package/src/__tests__/aiSummaryRendering.test.ts +21 -0
- package/src/__tests__/authResponses.test.ts +69 -0
- package/src/__tests__/capabilities.test.ts +71 -0
- package/src/__tests__/generateTrackingToken.test.ts +18 -4
- package/src/__tests__/integration/core-behaviors.test.ts +23 -0
- package/src/__tests__/rateLimiter.test.ts +60 -66
- package/src/__tests__/trackOpenEndpoint.test.ts +54 -0
- package/src/__tests__/webhookSecurity.test.ts +58 -0
- package/src/collections/PendingEmails.ts +2 -1
- package/src/collections/SupportClients.ts +43 -1
- package/src/collections/SupportCounters.ts +14 -0
- package/src/collections/SupportRateLimits.ts +20 -0
- package/src/collections/TicketMessages.ts +76 -0
- package/src/collections/Tickets.ts +183 -40
- package/src/collections/index.ts +2 -0
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +26 -13
- package/src/components/TicketConversation/components/ActionPanels.tsx +5 -5
- package/src/components/TicketConversation/components/ClientBar.tsx +1 -1
- package/src/components/TicketConversation/components/QuickActions.tsx +2 -2
- package/src/components/TicketConversation/components/TicketHeader.tsx +1 -1
- package/src/components/TicketConversation/index.tsx +10 -10
- package/src/endpoints/admin-chat.ts +4 -4
- package/src/endpoints/ai-agent.ts +6 -1
- package/src/endpoints/ai.ts +10 -5
- package/src/endpoints/auth-2fa.ts +6 -7
- package/src/endpoints/auto-close.ts +2 -1
- package/src/endpoints/capabilities.ts +144 -0
- package/src/endpoints/channels.ts +2 -1
- package/src/endpoints/chat.ts +6 -6
- package/src/endpoints/chatbot.ts +4 -4
- package/src/endpoints/client-intelligence.ts +13 -4
- package/src/endpoints/import-conversation.ts +6 -5
- package/src/endpoints/index.ts +35 -14
- package/src/endpoints/invite-collaborator.ts +4 -4
- package/src/endpoints/login.ts +4 -5
- package/src/endpoints/oauth-google.ts +1 -1
- package/src/endpoints/process-digests.ts +2 -1
- package/src/endpoints/process-scheduled.ts +2 -1
- package/src/endpoints/process-snooze.ts +2 -1
- package/src/endpoints/resend-notification.ts +4 -4
- package/src/endpoints/send-reminder.ts +4 -4
- package/src/endpoints/ticket-synthesis.ts +17 -2
- package/src/endpoints/track-open.ts +44 -21
- package/src/endpoints/transfer-ticket.ts +4 -4
- package/src/index.ts +7 -0
- package/src/plugin.ts +16 -1
- package/src/portal/LiveChat.tsx +1 -1
- package/src/portal/auth/ChatWidget.tsx +4 -4
- package/src/portal/auth/ChatbotWidget.tsx +4 -4
- package/src/portal/auth/dashboard/DashboardClient.tsx +2 -2
- package/src/portal/auth/faq/FAQSearch.tsx +1 -1
- package/src/portal/auth/faq/page.tsx +2 -2
- package/src/portal/auth/profile/page.tsx +17 -17
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +1 -1
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +1 -1
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +1 -1
- package/src/portal/auth/tickets/detail/SatisfactionForm.tsx +3 -3
- package/src/portal/auth/tickets/detail/TicketReplyForm.tsx +3 -3
- package/src/portal/auth/tickets/detail/page.tsx +5 -5
- package/src/portal/auth/tickets/new/page.tsx +8 -8
- package/src/portal/forgot-password/page.tsx +3 -3
- package/src/portal/layout.tsx +5 -2
- package/src/portal/login/page.tsx +7 -7
- package/src/portal/page.tsx +9 -9
- package/src/portal/register/page.tsx +4 -4
- package/src/portal/reset-password/page.tsx +3 -3
- package/src/styles/ChatView.module.scss +1 -1
- package/src/styles/TicketDetail.module.scss +3 -3
- package/src/styles/_tokens.scss +1 -1
- package/src/styles/theme.css +6 -6
- package/src/types.ts +71 -0
- package/src/utils/rateLimiter.ts +131 -38
- package/src/utils/slugs.ts +4 -0
- package/src/utils/webhookSecurity.ts +81 -0
- package/src/views/TicketingSettingsView/client.tsx +3 -3
package/src/plugin.ts
CHANGED
|
@@ -27,7 +27,10 @@ import {
|
|
|
27
27
|
createAutomationRulesCollection,
|
|
28
28
|
createSupportTeamCollection,
|
|
29
29
|
createPushSubscriptionCollection,
|
|
30
|
+
createSupportRateLimitsCollection,
|
|
31
|
+
createSupportCountersCollection,
|
|
30
32
|
} from './collections'
|
|
33
|
+
import { PayloadRateLimitStore } from './utils/rateLimiter'
|
|
31
34
|
|
|
32
35
|
function viewConfig(component: string, path: string): AdminViewConfig {
|
|
33
36
|
return { Component: component, path: path as `/${string}` }
|
|
@@ -68,6 +71,9 @@ export function supportPlugin(config?: SupportPluginConfig): Plugin {
|
|
|
68
71
|
...config?.collectionSlugs,
|
|
69
72
|
users: config?.userCollectionSlug || 'users',
|
|
70
73
|
})
|
|
74
|
+
const rateLimitStore = config?.rateLimitStore === 'payload'
|
|
75
|
+
? new PayloadRateLimitStore(slugs.rateLimits)
|
|
76
|
+
: config?.rateLimitStore
|
|
71
77
|
|
|
72
78
|
return (incomingConfig: Config): Config => {
|
|
73
79
|
const existingCollections = incomingConfig.collections || []
|
|
@@ -80,14 +86,17 @@ export function supportPlugin(config?: SupportPluginConfig): Plugin {
|
|
|
80
86
|
projectCollectionSlug: config?.projectCollectionSlug,
|
|
81
87
|
documentsCollectionSlug: config?.documentsCollectionSlug,
|
|
82
88
|
notificationSlug: config?.notificationSlug,
|
|
89
|
+
ticketNumber: config?.ticketNumber,
|
|
90
|
+
capabilities: config?.capabilities,
|
|
83
91
|
}
|
|
84
92
|
const messageOptions = {
|
|
85
93
|
notificationSlug: config?.notificationSlug,
|
|
94
|
+
capabilities: config?.capabilities,
|
|
86
95
|
}
|
|
87
96
|
const supportCollections = [
|
|
88
97
|
createTicketsCollection(slugs, ticketOptions),
|
|
89
98
|
createTicketMessagesCollection(slugs, messageOptions),
|
|
90
|
-
createSupportClientsCollection(slugs),
|
|
99
|
+
createSupportClientsCollection(slugs, { capabilities: config?.capabilities }),
|
|
91
100
|
createCannedResponsesCollection(slugs),
|
|
92
101
|
createTicketActivityLogCollection(slugs),
|
|
93
102
|
createSatisfactionSurveysCollection(slugs),
|
|
@@ -98,7 +107,11 @@ export function supportPlugin(config?: SupportPluginConfig): Plugin {
|
|
|
98
107
|
createAutomationRulesCollection(slugs),
|
|
99
108
|
createSupportTeamCollection(slugs),
|
|
100
109
|
createPushSubscriptionCollection(slugs),
|
|
110
|
+
createSupportCountersCollection(slugs.counters),
|
|
101
111
|
]
|
|
112
|
+
if (config?.rateLimitStore === 'payload') {
|
|
113
|
+
supportCollections.push(createSupportRateLimitsCollection(slugs.rateLimits))
|
|
114
|
+
}
|
|
102
115
|
|
|
103
116
|
// Auth logs (conditional)
|
|
104
117
|
if (features.authLogs !== false) supportCollections.push(createAuthLogsCollection(slugs))
|
|
@@ -150,6 +163,8 @@ export function supportPlugin(config?: SupportPluginConfig): Plugin {
|
|
|
150
163
|
const supportEndpoints = createSupportEndpoints(slugs, {
|
|
151
164
|
oauth: { allowedEmailDomains: config?.allowedEmailDomains },
|
|
152
165
|
features,
|
|
166
|
+
rateLimitStore,
|
|
167
|
+
capabilities: config?.capabilities,
|
|
153
168
|
})
|
|
154
169
|
|
|
155
170
|
return {
|
package/src/portal/LiveChat.tsx
CHANGED
|
@@ -441,7 +441,7 @@ export function LiveChat() {
|
|
|
441
441
|
width: 380,
|
|
442
442
|
maxWidth: 'calc(100vw - 32px)',
|
|
443
443
|
height: screen === 'identify' || screen === 'rating' ? 'auto' : 520,
|
|
444
|
-
maxHeight: 'calc(
|
|
444
|
+
maxHeight: 'calc(100dvh - 32px)',
|
|
445
445
|
borderRadius: 16,
|
|
446
446
|
border: '3px solid #000',
|
|
447
447
|
backgroundColor: '#fff',
|
|
@@ -215,7 +215,7 @@ function ChatWidgetInner() {
|
|
|
215
215
|
{!isOpen && (
|
|
216
216
|
<button
|
|
217
217
|
onClick={() => setIsOpen(true)}
|
|
218
|
-
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full border-3 border-black bg-[#00E5FF] shadow-[4px_4px_0px_#000] transition-
|
|
218
|
+
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full border-3 border-black bg-[#00E5FF] shadow-[4px_4px_0px_#000] transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000] dark:border-gray-600 dark:shadow-[4px_4px_0px_#333]"
|
|
219
219
|
title="Chat en direct"
|
|
220
220
|
>
|
|
221
221
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6">
|
|
@@ -274,7 +274,7 @@ function ChatWidgetInner() {
|
|
|
274
274
|
</p>
|
|
275
275
|
<button
|
|
276
276
|
onClick={startChat}
|
|
277
|
-
className="rounded-xl border-3 border-black bg-[#00E5FF] px-6 py-3 text-sm font-black text-black shadow-[4px_4px_0px_#000] transition-
|
|
277
|
+
className="rounded-xl border-3 border-black bg-[#00E5FF] px-6 py-3 text-sm font-black text-black shadow-[4px_4px_0px_#000] transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000]"
|
|
278
278
|
>
|
|
279
279
|
Démarrer le chat
|
|
280
280
|
</button>
|
|
@@ -320,7 +320,7 @@ function ChatWidgetInner() {
|
|
|
320
320
|
<p className="mb-3 text-sm text-gray-500 dark:text-gray-400">Le chat est terminé.</p>
|
|
321
321
|
<button
|
|
322
322
|
onClick={resetChat}
|
|
323
|
-
className="rounded-xl border-2 border-black bg-[#FFD600] px-4 py-2 text-xs font-bold text-black shadow-[2px_2px_0px_#000] transition-
|
|
323
|
+
className="rounded-xl border-2 border-black bg-[#FFD600] px-4 py-2 text-xs font-bold text-black shadow-[2px_2px_0px_#000] transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:translate-x-[1px] hover:translate-y-[1px] hover:shadow-[1px_1px_0px_#000]"
|
|
324
324
|
>
|
|
325
325
|
Nouveau chat
|
|
326
326
|
</button>
|
|
@@ -344,7 +344,7 @@ function ChatWidgetInner() {
|
|
|
344
344
|
<button
|
|
345
345
|
type="submit"
|
|
346
346
|
disabled={!input.trim() || sending}
|
|
347
|
-
className="rounded-xl border-2 border-black bg-[#00E5FF] px-3 py-2 font-bold text-black transition-
|
|
347
|
+
className="rounded-xl border-2 border-black bg-[#00E5FF] px-3 py-2 font-bold text-black transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:shadow-[2px_2px_0px_#000] disabled:opacity-50 dark:border-gray-600"
|
|
348
348
|
>
|
|
349
349
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4">
|
|
350
350
|
<line x1="22" y1="2" x2="11" y2="13" />
|
|
@@ -106,7 +106,7 @@ export function ChatbotWidget() {
|
|
|
106
106
|
{!isOpen && (
|
|
107
107
|
<button
|
|
108
108
|
onClick={() => setIsOpen(true)}
|
|
109
|
-
className="fixed bottom-24 right-6 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-lg transition-
|
|
109
|
+
className="fixed bottom-24 right-6 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-lg transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 dark:bg-blue-500 dark:hover:bg-blue-600"
|
|
110
110
|
aria-label="Ouvrir l'assistant IA"
|
|
111
111
|
title="Assistant IA"
|
|
112
112
|
>
|
|
@@ -129,7 +129,7 @@ export function ChatbotWidget() {
|
|
|
129
129
|
{/* Chat panel */}
|
|
130
130
|
{isOpen && (
|
|
131
131
|
<div
|
|
132
|
-
className="fixed bottom-24 right-6 z-40 flex w-[360px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-2xl transition-
|
|
132
|
+
className="fixed bottom-24 right-6 z-40 flex w-[360px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-2xl transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none dark:border-gray-700 dark:bg-gray-900"
|
|
133
133
|
style={{ height: '500px' }}
|
|
134
134
|
role="dialog"
|
|
135
135
|
aria-label="Assistant Support"
|
|
@@ -254,12 +254,12 @@ export function ChatbotWidget() {
|
|
|
254
254
|
placeholder="Posez votre question..."
|
|
255
255
|
maxLength={500}
|
|
256
256
|
disabled={loading}
|
|
257
|
-
className="flex-1 rounded-xl border border-gray-300 bg-white px-3.5 py-2.5 text-sm text-gray-900 outline-none transition-
|
|
257
|
+
className="flex-1 rounded-xl border border-gray-300 bg-white px-3.5 py-2.5 text-sm text-gray-900 outline-none transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none placeholder:text-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500 dark:focus:border-blue-400"
|
|
258
258
|
/>
|
|
259
259
|
<button
|
|
260
260
|
type="submit"
|
|
261
261
|
disabled={!input.trim() || loading}
|
|
262
|
-
className="flex h-[42px] w-[42px] flex-shrink-0 items-center justify-center rounded-xl bg-blue-600 text-white transition-
|
|
262
|
+
className="flex h-[42px] w-[42px] flex-shrink-0 items-center justify-center rounded-xl bg-blue-600 text-white transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700 disabled:opacity-40 disabled:hover:bg-blue-600 dark:bg-blue-500 dark:hover:bg-blue-600"
|
|
263
263
|
aria-label="Envoyer"
|
|
264
264
|
>
|
|
265
265
|
<svg
|
|
@@ -224,7 +224,7 @@ export function DashboardClient({ tickets }: { tickets: TicketData[] }) {
|
|
|
224
224
|
</div>
|
|
225
225
|
<Link
|
|
226
226
|
href="/support/tickets/new"
|
|
227
|
-
className="inline-flex flex-shrink-0 items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-
|
|
227
|
+
className="inline-flex flex-shrink-0 items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700 hover:shadow-md active:scale-[0.98]"
|
|
228
228
|
>
|
|
229
229
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4">
|
|
230
230
|
<line x1="12" y1="5" x2="12" y2="19" />
|
|
@@ -447,7 +447,7 @@ export function DashboardClient({ tickets }: { tickets: TicketData[] }) {
|
|
|
447
447
|
{!searchQuery && tab === 'active' && (
|
|
448
448
|
<Link
|
|
449
449
|
href="/support/tickets/new"
|
|
450
|
-
className="mt-5 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-
|
|
450
|
+
className="mt-5 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700"
|
|
451
451
|
>
|
|
452
452
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4">
|
|
453
453
|
<line x1="12" y1="5" x2="12" y2="19" />
|
|
@@ -24,7 +24,7 @@ export function FAQSearch({ articles }: { articles: Article[] }) {
|
|
|
24
24
|
return (
|
|
25
25
|
<div className="relative mb-10">
|
|
26
26
|
{/* Search input */}
|
|
27
|
-
<div className={`relative rounded-2xl border bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm transition-
|
|
27
|
+
<div className={`relative rounded-2xl border bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 ${
|
|
28
28
|
isFocused
|
|
29
29
|
? 'border-blue-500 ring-4 ring-blue-500/10 shadow-md'
|
|
30
30
|
: 'border-slate-200 dark:border-slate-700/50 hover:border-slate-300 dark:hover:border-slate-600'
|
|
@@ -28,7 +28,7 @@ const categoryConfig: Record<string, { label: string; icon: React.ReactNode; col
|
|
|
28
28
|
},
|
|
29
29
|
account: {
|
|
30
30
|
label: 'Compte & Profil',
|
|
31
|
-
color: 'bg-
|
|
31
|
+
color: 'bg-teal-50 dark:bg-teal-900/20 text-teal-700 dark:text-teal-300',
|
|
32
32
|
icon: (
|
|
33
33
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-5 w-5">
|
|
34
34
|
<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" />
|
|
@@ -153,7 +153,7 @@ export default async function FAQPage() {
|
|
|
153
153
|
{items.map((article) => (
|
|
154
154
|
<details
|
|
155
155
|
key={article.id}
|
|
156
|
-
className="group rounded-xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm transition-
|
|
156
|
+
className="group rounded-xl border border-slate-200 dark:border-slate-700/50 bg-white dark:bg-slate-800/50 shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:border-slate-300 dark:hover:border-slate-600 open:shadow-md"
|
|
157
157
|
>
|
|
158
158
|
<summary className="flex cursor-pointer items-center justify-between gap-4 px-5 py-4 text-sm font-semibold text-slate-900 dark:text-white list-none [&::-webkit-details-marker]:hidden">
|
|
159
159
|
<span className="leading-relaxed">{article.title}</span>
|
|
@@ -254,7 +254,7 @@ export default function ProfilePage() {
|
|
|
254
254
|
required
|
|
255
255
|
value={profile.firstName}
|
|
256
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
258
258
|
/>
|
|
259
259
|
</div>
|
|
260
260
|
<div>
|
|
@@ -267,7 +267,7 @@ export default function ProfilePage() {
|
|
|
267
267
|
required
|
|
268
268
|
value={profile.lastName}
|
|
269
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
271
271
|
/>
|
|
272
272
|
</div>
|
|
273
273
|
</div>
|
|
@@ -283,7 +283,7 @@ export default function ProfilePage() {
|
|
|
283
283
|
required
|
|
284
284
|
value={profile.company}
|
|
285
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
287
287
|
/>
|
|
288
288
|
</div>
|
|
289
289
|
|
|
@@ -298,7 +298,7 @@ export default function ProfilePage() {
|
|
|
298
298
|
value={profile.phone}
|
|
299
299
|
onChange={(e) => setProfile({ ...profile, phone: e.target.value })}
|
|
300
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
302
302
|
/>
|
|
303
303
|
</div>
|
|
304
304
|
|
|
@@ -317,7 +317,7 @@ export default function ProfilePage() {
|
|
|
317
317
|
<button
|
|
318
318
|
type="submit"
|
|
319
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-blue-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
321
321
|
>
|
|
322
322
|
{saving ? (
|
|
323
323
|
<>
|
|
@@ -384,8 +384,8 @@ export default function ProfilePage() {
|
|
|
384
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
385
|
<div className="border-b border-slate-100 dark:border-slate-700/50 px-6 py-4 sm:px-8">
|
|
386
386
|
<div className="flex items-center gap-3">
|
|
387
|
-
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-
|
|
388
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-
|
|
387
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-teal-50 dark:bg-teal-900/30">
|
|
388
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-teal-700 dark:text-teal-300">
|
|
389
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
390
|
</svg>
|
|
391
391
|
</div>
|
|
@@ -443,7 +443,7 @@ export default function ProfilePage() {
|
|
|
443
443
|
type="button"
|
|
444
444
|
onClick={handleSaveProfile}
|
|
445
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-blue-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
447
447
|
>
|
|
448
448
|
{saving ? 'Enregistrement...' : 'Sauvegarder les preferences'}
|
|
449
449
|
</button>
|
|
@@ -491,7 +491,7 @@ export default function ProfilePage() {
|
|
|
491
491
|
required
|
|
492
492
|
value={currentPassword}
|
|
493
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
495
495
|
/>
|
|
496
496
|
</div>
|
|
497
497
|
|
|
@@ -506,7 +506,7 @@ export default function ProfilePage() {
|
|
|
506
506
|
minLength={8}
|
|
507
507
|
value={newPassword}
|
|
508
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
510
510
|
/>
|
|
511
511
|
<p className="mt-1.5 text-xs text-slate-400 dark:text-slate-500">Minimum 8 caracteres</p>
|
|
512
512
|
</div>
|
|
@@ -521,14 +521,14 @@ export default function ProfilePage() {
|
|
|
521
521
|
required
|
|
522
522
|
value={confirmPassword}
|
|
523
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
525
525
|
/>
|
|
526
526
|
</div>
|
|
527
527
|
|
|
528
528
|
<button
|
|
529
529
|
type="submit"
|
|
530
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none 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
532
|
>
|
|
533
533
|
{changingPassword ? (
|
|
534
534
|
<>
|
|
@@ -567,7 +567,7 @@ export default function ProfilePage() {
|
|
|
567
567
|
<a
|
|
568
568
|
href="/api/support/export-data"
|
|
569
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none 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
571
|
>
|
|
572
572
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4">
|
|
573
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" />
|
|
@@ -603,7 +603,7 @@ export default function ProfilePage() {
|
|
|
603
603
|
{!showDeleteConfirm ? (
|
|
604
604
|
<button
|
|
605
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-red-50 dark:hover:bg-red-900/20 hover:border-red-300"
|
|
607
607
|
>
|
|
608
608
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4">
|
|
609
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" />
|
|
@@ -651,7 +651,7 @@ export default function ProfilePage() {
|
|
|
651
651
|
value={deletePassword}
|
|
652
652
|
onChange={(e) => setDeletePassword(e.target.value)}
|
|
653
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none focus:border-red-500 focus:ring-4 focus:ring-red-500/10"
|
|
655
655
|
/>
|
|
656
656
|
</div>
|
|
657
657
|
|
|
@@ -684,13 +684,13 @@ export default function ProfilePage() {
|
|
|
684
684
|
}
|
|
685
685
|
}}
|
|
686
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-
|
|
687
|
+
className="rounded-xl bg-red-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-red-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
688
688
|
>
|
|
689
689
|
{deleting ? 'Suppression...' : 'Confirmer la suppression'}
|
|
690
690
|
</button>
|
|
691
691
|
<button
|
|
692
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-
|
|
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-slate-50 dark:hover:bg-slate-700"
|
|
694
694
|
>
|
|
695
695
|
Annuler
|
|
696
696
|
</button>
|
|
@@ -47,7 +47,7 @@ export function CloseTicketButton({ ticketId }: { ticketId: number | string }) {
|
|
|
47
47
|
<button
|
|
48
48
|
onClick={handleClose}
|
|
49
49
|
disabled={loading}
|
|
50
|
-
className={`inline-flex items-center gap-1.5 rounded-xl border px-3.5 py-2 text-sm font-semibold transition-
|
|
50
|
+
className={`inline-flex items-center gap-1.5 rounded-xl border px-3.5 py-2 text-sm font-semibold transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none disabled:opacity-50 ${
|
|
51
51
|
confirming
|
|
52
52
|
? 'border-red-200 dark:border-red-800/50 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/30'
|
|
53
53
|
: 'border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-900/30'
|
|
@@ -42,7 +42,7 @@ export function MarkSolutionButton({
|
|
|
42
42
|
<button
|
|
43
43
|
onClick={handleToggle}
|
|
44
44
|
disabled={loading}
|
|
45
|
-
className={`mt-2 flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-bold transition-
|
|
45
|
+
className={`mt-2 flex items-center gap-1 rounded-lg px-2 py-1 text-xs font-bold transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none ${
|
|
46
46
|
isSolution
|
|
47
47
|
? 'border-2 border-green-500 bg-green-50 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
|
48
48
|
: 'border-2 border-gray-200 text-gray-400 hover:border-green-500 hover:text-green-600 dark:border-gray-600 dark:hover:border-green-500'
|
|
@@ -47,7 +47,7 @@ export function ReopenTicketButton({ ticketId }: { ticketId: number | string })
|
|
|
47
47
|
<button
|
|
48
48
|
onClick={handleReopen}
|
|
49
49
|
disabled={loading}
|
|
50
|
-
className={`inline-flex items-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-semibold transition-
|
|
50
|
+
className={`inline-flex items-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-semibold transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none disabled:opacity-50 ${
|
|
51
51
|
confirming
|
|
52
52
|
? 'border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-900/30'
|
|
53
53
|
: 'border-blue-200 dark:border-blue-800/50 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/30'
|
|
@@ -97,7 +97,7 @@ export function SatisfactionForm({ ticketId }: { ticketId: number | string }) {
|
|
|
97
97
|
onClick={() => setRating(star)}
|
|
98
98
|
onMouseEnter={() => setHoveredRating(star)}
|
|
99
99
|
onMouseLeave={() => setHoveredRating(0)}
|
|
100
|
-
className="rounded-lg p-1 transition-
|
|
100
|
+
className="rounded-lg p-1 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:scale-110 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-400"
|
|
101
101
|
>
|
|
102
102
|
<svg
|
|
103
103
|
className={`h-8 w-8 transition-colors ${
|
|
@@ -131,13 +131,13 @@ export function SatisfactionForm({ ticketId }: { ticketId: number | string }) {
|
|
|
131
131
|
value={comment}
|
|
132
132
|
onChange={(e) => setComment(e.target.value)}
|
|
133
133
|
placeholder="Un commentaire ? (optionnel)"
|
|
134
|
-
className="mb-4 w-full resize-y rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-4 py-3 text-sm text-slate-900 dark:text-slate-200 placeholder-slate-400 dark:placeholder-slate-500 outline-none transition-
|
|
134
|
+
className="mb-4 w-full resize-y rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-4 py-3 text-sm text-slate-900 dark:text-slate-200 placeholder-slate-400 dark:placeholder-slate-500 outline-none transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none focus:border-blue-400 dark:focus:border-blue-500 focus:ring-2 focus:ring-blue-400/20 dark:focus:ring-blue-500/20"
|
|
135
135
|
/>
|
|
136
136
|
|
|
137
137
|
<button
|
|
138
138
|
type="submit"
|
|
139
139
|
disabled={loading || rating === 0}
|
|
140
|
-
className="inline-flex items-center gap-2 rounded-xl bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-
|
|
140
|
+
className="inline-flex items-center gap-2 rounded-xl bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700 hover:shadow-md disabled:opacity-40 disabled:cursor-not-allowed"
|
|
141
141
|
>
|
|
142
142
|
{loading ? (
|
|
143
143
|
<>
|
|
@@ -154,7 +154,7 @@ export function TicketReplyForm({ ticketId }: { ticketId: number | string }) {
|
|
|
154
154
|
onDragOver={handleDragOver}
|
|
155
155
|
onDragLeave={handleDragLeave}
|
|
156
156
|
onDrop={handleDrop}
|
|
157
|
-
className={`rounded-xl transition-
|
|
157
|
+
className={`rounded-xl transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none ${
|
|
158
158
|
isDragging
|
|
159
159
|
? 'bg-blue-50/50 dark:bg-blue-900/20 ring-2 ring-blue-400/20'
|
|
160
160
|
: ''
|
|
@@ -193,7 +193,7 @@ export function TicketReplyForm({ ticketId }: { ticketId: number | string }) {
|
|
|
193
193
|
onChange={(e) => { setBody(e.target.value); setBodyHtml(''); sendTyping() }}
|
|
194
194
|
placeholder="Écrivez votre message..."
|
|
195
195
|
rows={5}
|
|
196
|
-
className="w-full resize-y rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-4 py-3 text-sm leading-relaxed text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 outline-none transition-
|
|
196
|
+
className="w-full resize-y rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-4 py-3 text-sm leading-relaxed text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 outline-none transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10"
|
|
197
197
|
/>
|
|
198
198
|
</div>
|
|
199
199
|
|
|
@@ -222,7 +222,7 @@ export function TicketReplyForm({ ticketId }: { ticketId: number | string }) {
|
|
|
222
222
|
<button
|
|
223
223
|
type="submit"
|
|
224
224
|
disabled={loading || (!body.trim() && !bodyHtml)}
|
|
225
|
-
className="ml-auto inline-flex items-center gap-2 rounded-xl bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-
|
|
225
|
+
className="ml-auto inline-flex items-center gap-2 rounded-xl bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-blue-700 hover:shadow-md disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:shadow-sm"
|
|
226
226
|
>
|
|
227
227
|
{loading ? (
|
|
228
228
|
<>
|
|
@@ -257,7 +257,7 @@ export default async function TicketDetailPage({ params }: { params: Promise<{ i
|
|
|
257
257
|
href={`${process.env.NEXT_PUBLIC_SERVER_URL || ''}/api/documents/file/${quoteDoc.filename}`}
|
|
258
258
|
target="_blank"
|
|
259
259
|
rel="noopener noreferrer"
|
|
260
|
-
className="flex items-center gap-2 rounded-xl border border-amber-200 dark:border-amber-800/50 bg-amber-50 dark:bg-amber-900/20 px-3 py-2 text-sm font-medium text-amber-800 dark:text-amber-300 transition-
|
|
260
|
+
className="flex items-center gap-2 rounded-xl border border-amber-200 dark:border-amber-800/50 bg-amber-50 dark:bg-amber-900/20 px-3 py-2 text-sm font-medium text-amber-800 dark:text-amber-300 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-amber-100 dark:hover:bg-amber-900/30"
|
|
261
261
|
>
|
|
262
262
|
<svg className="h-4 w-4 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
263
263
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
@@ -270,7 +270,7 @@ export default async function TicketDetailPage({ params }: { params: Promise<{ i
|
|
|
270
270
|
href={`${process.env.NEXT_PUBLIC_SERVER_URL || ''}/api/documents/file/${invoiceDoc.filename}`}
|
|
271
271
|
target="_blank"
|
|
272
272
|
rel="noopener noreferrer"
|
|
273
|
-
className="flex items-center gap-2 rounded-xl border border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 px-3 py-2 text-sm font-medium text-emerald-800 dark:text-emerald-300 transition-
|
|
273
|
+
className="flex items-center gap-2 rounded-xl border border-emerald-200 dark:border-emerald-800/50 bg-emerald-50 dark:bg-emerald-900/20 px-3 py-2 text-sm font-medium text-emerald-800 dark:text-emerald-300 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-emerald-100 dark:hover:bg-emerald-900/30"
|
|
274
274
|
>
|
|
275
275
|
<svg className="h-4 w-4 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
276
276
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
@@ -499,7 +499,7 @@ export default async function TicketDetailPage({ params }: { params: Promise<{ i
|
|
|
499
499
|
href={`${process.env.NEXT_PUBLIC_SERVER_URL || ''}/api/documents/file/${quoteDoc.filename}`}
|
|
500
500
|
target="_blank"
|
|
501
501
|
rel="noopener noreferrer"
|
|
502
|
-
className="flex items-center gap-3 rounded-xl border border-amber-200 dark:border-amber-800/50 bg-amber-50 dark:bg-amber-900/20 px-4 py-3 text-sm font-medium text-amber-800 dark:text-amber-300 transition-
|
|
502
|
+
className="flex items-center gap-3 rounded-xl border border-amber-200 dark:border-amber-800/50 bg-amber-50 dark:bg-amber-900/20 px-4 py-3 text-sm font-medium text-amber-800 dark:text-amber-300 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-amber-100 dark:hover:bg-amber-900/30 hover:shadow-sm"
|
|
503
503
|
>
|
|
504
504
|
<svg className="h-5 w-5 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
505
505
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
@@ -518,7 +518,7 @@ export default async function TicketDetailPage({ params }: { params: Promise<{ i
|
|
|
518
518
|
href={`${process.env.NEXT_PUBLIC_SERVER_URL || ''}/api/documents/file/${invoiceDoc.filename}`}
|
|
519
519
|
target="_blank"
|
|
520
520
|
rel="noopener noreferrer"
|
|
521
|
-
className="flex items-center 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 text-sm font-medium text-emerald-800 dark:text-emerald-300 transition-
|
|
521
|
+
className="flex items-center 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 text-sm font-medium text-emerald-800 dark:text-emerald-300 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-emerald-100 dark:hover:bg-emerald-900/30 hover:shadow-sm"
|
|
522
522
|
>
|
|
523
523
|
<svg className="h-5 w-5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
524
524
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
@@ -811,7 +811,7 @@ export default async function TicketDetailPage({ params }: { params: Promise<{ i
|
|
|
811
811
|
<ReopenTicketButton ticketId={ticket.id} />
|
|
812
812
|
<Link
|
|
813
813
|
href="/support/tickets/new"
|
|
814
|
-
className="inline-flex items-center gap-2 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-4 py-2.5 text-sm font-semibold text-slate-700 dark:text-slate-300 transition-
|
|
814
|
+
className="inline-flex items-center gap-2 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-4 py-2.5 text-sm font-semibold text-slate-700 dark:text-slate-300 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:bg-slate-50 dark:hover:bg-slate-700 hover:shadow-sm"
|
|
815
815
|
>
|
|
816
816
|
<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-4 w-4">
|
|
817
817
|
<line x1="12" y1="5" x2="12" y2="19" />
|
|
@@ -317,7 +317,7 @@ export default function NewTicketPage() {
|
|
|
317
317
|
)}
|
|
318
318
|
|
|
319
319
|
{/* Main card */}
|
|
320
|
-
<div className={`rounded-2xl border bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm transition-
|
|
320
|
+
<div className={`rounded-2xl border bg-white dark:bg-slate-800/50 shadow-sm backdrop-blur-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 ${
|
|
321
321
|
isDragging
|
|
322
322
|
? 'border-blue-400 dark:border-blue-500 ring-4 ring-blue-100 dark:ring-blue-900/30 shadow-blue-100/50'
|
|
323
323
|
: 'border-slate-200 dark:border-slate-700/50'
|
|
@@ -335,7 +335,7 @@ export default function NewTicketPage() {
|
|
|
335
335
|
value={subject}
|
|
336
336
|
onChange={(e) => setSubject(e.target.value)}
|
|
337
337
|
placeholder="Ex: Erreur 404 sur la page contact"
|
|
338
|
-
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-
|
|
338
|
+
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
339
339
|
/>
|
|
340
340
|
{/* KB deflection — suggest existing articles before ticket creation */}
|
|
341
341
|
<KbDeflection query={subject} limit={3} />
|
|
@@ -353,7 +353,7 @@ export default function NewTicketPage() {
|
|
|
353
353
|
value={message}
|
|
354
354
|
onChange={(e) => setMessage(e.target.value)}
|
|
355
355
|
placeholder="Decrivez votre probleme en detail. Plus vous donnez d'informations, plus nous pourrons vous aider rapidement."
|
|
356
|
-
className="w-full resize-y rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm leading-relaxed text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 outline-none transition-
|
|
356
|
+
className="w-full resize-y rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-50 dark:bg-slate-900/50 px-4 py-3 text-sm leading-relaxed text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 outline-none transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 focus:border-blue-500 focus:bg-white dark:focus:bg-slate-900 focus:ring-4 focus:ring-blue-500/10"
|
|
357
357
|
/>
|
|
358
358
|
</div>
|
|
359
359
|
|
|
@@ -385,7 +385,7 @@ export default function NewTicketPage() {
|
|
|
385
385
|
<button
|
|
386
386
|
type="button"
|
|
387
387
|
onClick={() => fileInputRef.current?.click()}
|
|
388
|
-
className="group flex w-full items-center justify-center gap-3 rounded-xl border-2 border-dashed border-slate-200 dark:border-slate-600 bg-slate-50/50 dark:bg-slate-900/30 px-6 py-4 transition-
|
|
388
|
+
className="group flex w-full items-center justify-center gap-3 rounded-xl border-2 border-dashed border-slate-200 dark:border-slate-600 bg-slate-50/50 dark:bg-slate-900/30 px-6 py-4 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50/50 dark:hover:bg-blue-900/10"
|
|
389
389
|
>
|
|
390
390
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-100 dark:bg-slate-700 transition-colors group-hover:bg-blue-100 dark:group-hover:bg-blue-900/40">
|
|
391
391
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="h-5 w-5 text-slate-400 group-hover:text-blue-500 transition-colors">
|
|
@@ -461,7 +461,7 @@ export default function NewTicketPage() {
|
|
|
461
461
|
return (
|
|
462
462
|
<label
|
|
463
463
|
key={cat.value}
|
|
464
|
-
className={`group relative flex cursor-pointer flex-col items-start gap-1 rounded-xl border p-3 text-left transition-
|
|
464
|
+
className={`group relative flex cursor-pointer flex-col items-start gap-1 rounded-xl border p-3 text-left transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none ${
|
|
465
465
|
isActive
|
|
466
466
|
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500/20 dark:border-blue-400 dark:bg-blue-950/40'
|
|
467
467
|
: 'border-slate-200 bg-white hover:border-slate-300 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800/50 dark:hover:border-slate-600 dark:hover:bg-slate-800'
|
|
@@ -509,7 +509,7 @@ export default function NewTicketPage() {
|
|
|
509
509
|
return (
|
|
510
510
|
<label
|
|
511
511
|
key={p.value}
|
|
512
|
-
className={`flex cursor-pointer items-start gap-3 rounded-xl border p-3 transition-
|
|
512
|
+
className={`flex cursor-pointer items-start gap-3 rounded-xl border p-3 transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none ${
|
|
513
513
|
isActive
|
|
514
514
|
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500/20 dark:border-blue-400 dark:bg-blue-950/40'
|
|
515
515
|
: 'border-slate-200 bg-white hover:border-slate-300 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800/50 dark:hover:border-slate-600 dark:hover:bg-slate-800'
|
|
@@ -568,7 +568,7 @@ export default function NewTicketPage() {
|
|
|
568
568
|
id="project"
|
|
569
569
|
value={project}
|
|
570
570
|
onChange={(e) => setProject(e.target.value)}
|
|
571
|
-
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2.5 text-sm text-slate-900 dark:text-white outline-none transition-
|
|
571
|
+
className="w-full rounded-xl border border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2.5 text-sm text-slate-900 dark:text-white outline-none transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none focus:border-blue-500 focus:ring-4 focus:ring-blue-500/10"
|
|
572
572
|
>
|
|
573
573
|
<option value="">Aucun projet specifique</option>
|
|
574
574
|
{projects.map((p) => (
|
|
@@ -590,7 +590,7 @@ export default function NewTicketPage() {
|
|
|
590
590
|
<button
|
|
591
591
|
type="submit"
|
|
592
592
|
disabled={loading}
|
|
593
|
-
className="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-8 py-3 text-sm font-semibold text-white shadow-sm transition-
|
|
593
|
+
className="inline-flex items-center justify-center gap-2 rounded-xl bg-blue-600 px-8 py-3 text-sm font-semibold text-white shadow-sm transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none duration-200 hover:bg-blue-700 hover:shadow-md active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
594
594
|
>
|
|
595
595
|
{loading ? (
|
|
596
596
|
<>
|
|
@@ -35,7 +35,7 @@ export default function ForgotPasswordPage() {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
|
-
<div className="flex min-h-
|
|
38
|
+
<div className="flex min-h-[100dvh] items-center justify-center px-4 py-12">
|
|
39
39
|
<div className="w-full max-w-md">
|
|
40
40
|
<div className="mb-8 text-center">
|
|
41
41
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl border-4 border-black bg-[#FFD600] shadow-[4px_4px_0px_#000]">
|
|
@@ -58,7 +58,7 @@ export default function ForgotPasswordPage() {
|
|
|
58
58
|
</p>
|
|
59
59
|
<Link
|
|
60
60
|
href="/support/login"
|
|
61
|
-
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-
|
|
61
|
+
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-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000]"
|
|
62
62
|
>
|
|
63
63
|
Retour à la connexion
|
|
64
64
|
</Link>
|
|
@@ -96,7 +96,7 @@ export default function ForgotPasswordPage() {
|
|
|
96
96
|
<button
|
|
97
97
|
type="submit"
|
|
98
98
|
disabled={loading}
|
|
99
|
-
className="w-full rounded-xl border-3 border-black bg-[#FFD600] px-6 py-3 text-base font-black text-black shadow-[4px_4px_0px_#000] transition-
|
|
99
|
+
className="w-full rounded-xl border-3 border-black bg-[#FFD600] px-6 py-3 text-base font-black text-black shadow-[4px_4px_0px_#000] transition-[color,background-color,border-color,box-shadow,transform] motion-reduce:transition-none hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_#000] disabled:opacity-50"
|
|
100
100
|
>
|
|
101
101
|
{loading ? 'Envoi...' : 'Envoyer le lien'}
|
|
102
102
|
</button>
|