Vendored
-1510
File diff suppressed because one or more lines are too long
Vendored
+1510
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
|
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
|
||||||
<meta name="description" content="Déployez en un clic une démo complète de la plateforme de gestion de commandes et de livraison." />
|
<meta name="description" content="Déployez en un clic une démo complète de la plateforme de gestion de commandes et de livraison." />
|
||||||
<script type="module" crossorigin src="/assets/index-CbXT_Xos.js"></script>
|
<script type="module" crossorigin src="/assets/index-DVA6g9E7.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { CheckIcon, CloseIcon, EditIcon } from '@chakra-ui/icons'
|
import { CheckIcon, CloseIcon, EditIcon } from '@chakra-ui/icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
||||||
import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { api, ApiError } from '../../lib/api'
|
import { api, ApiError } from '../../lib/api'
|
||||||
import { useAuth } from '../../lib/auth'
|
import { useAuth } from '../../lib/auth'
|
||||||
|
import { PasswordInput } from '../../components/PasswordInput'
|
||||||
|
|
||||||
interface MeResponse {
|
interface MeResponse {
|
||||||
user_id: string
|
user_id: string
|
||||||
@@ -106,8 +105,8 @@ export function Profile() {
|
|||||||
const [loggingOut, setLoggingOut] = useState(false)
|
const [loggingOut, setLoggingOut] = useState(false)
|
||||||
const [updatingUsername, setUpdatingUsername] = useState(false)
|
const [updatingUsername, setUpdatingUsername] = useState(false)
|
||||||
const [updatingPassword, setUpdatingPassword] = useState(false)
|
const [updatingPassword, setUpdatingPassword] = useState(false)
|
||||||
const [passwordVersion, setPasswordVersion] = useState(0)
|
const [editingPassword, setEditingPassword] = useState(false)
|
||||||
const [passwordVisible, setPasswordVisible] = useState(false)
|
const [passwordDraft, setPasswordDraft] = useState('')
|
||||||
const [telegram, setTelegramState] = useState('')
|
const [telegram, setTelegramState] = useState('')
|
||||||
const [updatingTelegram, setUpdatingTelegram] = useState(false)
|
const [updatingTelegram, setUpdatingTelegram] = useState(false)
|
||||||
const [addingTelegram, setAddingTelegram] = useState(false)
|
const [addingTelegram, setAddingTelegram] = useState(false)
|
||||||
@@ -237,13 +236,10 @@ export function Profile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdatePassword = async (newPassword: string) => {
|
const handleUpdatePassword = async () => {
|
||||||
const trimmed = newPassword.trim()
|
const trimmed = passwordDraft.trim()
|
||||||
if (!me || trimmed.length < 8) {
|
if (trimmed.length < 8) {
|
||||||
if (trimmed.length > 0) {
|
|
||||||
toast({ status: 'warning', title: 'Le mot de passe doit contenir au moins 8 caractères' })
|
toast({ status: 'warning', title: 'Le mot de passe doit contenir au moins 8 caractères' })
|
||||||
}
|
|
||||||
setPasswordVersion(v => v + 1) // reset le champ même en cas d'annulation
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +247,8 @@ export function Profile() {
|
|||||||
try {
|
try {
|
||||||
await api.updatePassword(trimmed)
|
await api.updatePassword(trimmed)
|
||||||
toast({ status: 'success', title: 'Mot de passe mis à jour' })
|
toast({ status: 'success', title: 'Mot de passe mis à jour' })
|
||||||
|
setEditingPassword(false)
|
||||||
|
setPasswordDraft('')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError && err.status === 401) {
|
if (err instanceof ApiError && err.status === 401) {
|
||||||
navigate('/login')
|
navigate('/login')
|
||||||
@@ -263,9 +261,13 @@ export function Profile() {
|
|||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setUpdatingPassword(false)
|
setUpdatingPassword(false)
|
||||||
setPasswordVersion(v => v + 1) // vide le champ après tentative
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCancelPassword = () => {
|
||||||
|
setPasswordDraft('')
|
||||||
|
setEditingPassword(false)
|
||||||
|
}
|
||||||
const handleUpdateTelegram = async (newTelegram: string) => {
|
const handleUpdateTelegram = async (newTelegram: string) => {
|
||||||
const trimmed = newTelegram.trim()
|
const trimmed = newTelegram.trim()
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@@ -386,32 +388,51 @@ export function Profile() {
|
|||||||
|
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatLabel>Mot de passe</StatLabel>
|
<StatLabel>Mot de passe</StatLabel>
|
||||||
<Editable
|
{editingPassword ? (
|
||||||
key={passwordVersion}
|
|
||||||
defaultValue=""
|
|
||||||
placeholder="••••••••"
|
|
||||||
onSubmit={handleUpdatePassword}
|
|
||||||
isDisabled={updatingPassword}
|
|
||||||
submitOnBlur={false}
|
|
||||||
>
|
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
<EditablePreview as={StatNumber} fontSize="md" fontFamily="mono" />
|
<PasswordInput
|
||||||
<EditableInput
|
size="sm"
|
||||||
type={passwordVisible ? 'text' : 'password'}
|
|
||||||
fontSize="md"
|
|
||||||
fontFamily="mono"
|
fontFamily="mono"
|
||||||
|
fontSize="md"
|
||||||
|
value={passwordDraft}
|
||||||
|
onChange={(e) => setPasswordDraft(e.target.value)}
|
||||||
|
isDisabled={updatingPassword}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<ButtonGroup size="sm" spacing={1}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Enregistrer"
|
||||||
|
icon={<CheckIcon />}
|
||||||
|
isLoading={updatingPassword}
|
||||||
|
onClick={handleUpdatePassword}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={passwordVisible ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
|
aria-label="Annuler"
|
||||||
icon={<FontAwesomeIcon icon={passwordVisible ? faEyeSlash : faEye} />}
|
icon={<CloseIcon />}
|
||||||
|
isDisabled={updatingPassword}
|
||||||
|
onClick={handleCancelPassword}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
</HStack>
|
||||||
|
) : (
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{/* Jamais le vrai mot de passe : ni celui en base (jamais
|
||||||
|
renvoyé par l'API), ni celui qu'on vient de saisir —
|
||||||
|
contrairement à EditablePreview, qui aurait affiché en
|
||||||
|
clair le dernier mot de passe soumis (le "type" masqué
|
||||||
|
d'EditableInput ne s'applique qu'en mode édition). */}
|
||||||
|
<StatNumber fontSize="md" fontFamily="mono">
|
||||||
|
••••••••
|
||||||
|
</StatNumber>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Modifier le mot de passe"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
tabIndex={-1}
|
icon={<EditIcon />}
|
||||||
onClick={() => setPasswordVisible((v) => !v)}
|
onClick={() => setEditingPassword(true)}
|
||||||
/>
|
/>
|
||||||
<EditableUsernameControls />
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</Editable>
|
)}
|
||||||
</Stat>
|
</Stat>
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatLabel>Telegram</StatLabel>
|
<StatLabel>Telegram</StatLabel>
|
||||||
|
|||||||
Reference in New Issue
Block a user