feat: add update password
ci-api / test (push) Successful in 24m40s
ci-web / test (push) Canceled after 8m5s

This commit is contained in:
Nuxgrid
2026-07-29 14:18:14 +02:00
parent eda6c0e98a
commit 5a0c1bfe3f
5 changed files with 105 additions and 2 deletions
+5 -1
View File
@@ -104,7 +104,9 @@ export interface UpdateUsername {
username: string
}
export interface UpdatePassword {
password: string
}
// --- Endpoints ---
export const api = {
@@ -155,4 +157,6 @@ export const api = {
request<DemoDetails>('POST', '/demos/details', { username }),
updateUsername: (username: string) =>
request<UpdateUsername>('POST', '/profile/username', { username }),
updatePassword: (password: string) =>
request<UpdatePassword>('POST', '/profile/password', { password })
}
+51 -1
View File
@@ -99,7 +99,8 @@ export function Profile() {
const [loading, setLoading] = useState(true)
const [loggingOut, setLoggingOut] = useState(false)
const [updatingUsername, setUpdatingUsername] = useState(false)
const [updatingPassword, setUpdatingPassword] = useState(false)
const [passwordVersion, setPasswordVersion] = useState(0)
useEffect(() => {
let cancelled = false
;(async () => {
@@ -145,6 +146,36 @@ export function Profile() {
}
}
const handleUpdatePassword = async (newPassword: string) => {
const trimmed = newPassword.trim()
if (!me || trimmed.length < 8) {
if (trimmed.length > 0) {
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
}
setUpdatingPassword(true)
try {
await api.updatePassword(trimmed)
toast({ status: 'success', title: 'Mot de passe mis à jour' })
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
navigate('/login')
return
}
toast({
status: 'error',
title: 'Impossible de mettre à jour le mot de passe',
description: err instanceof ApiError ? err.message : undefined,
})
} finally {
setUpdatingPassword(false)
setPasswordVersion(v => v + 1) // vide le champ après tentative
}
}
const handleLogout = async () => {
setLoggingOut(true)
try {
@@ -236,6 +267,25 @@ export function Profile() {
<StatLabel>Type d'abonnement</StatLabel>
<StatNumber fontSize="md">{me.type_abonnement || ''}</StatNumber>
</Stat>
<Stat>
<StatLabel>Mot de passe</StatLabel>
<Editable
key={passwordVersion}
defaultValue=""
placeholder="••••••••"
onSubmit={handleUpdatePassword}
isDisabled={updatingPassword}
submitOnBlur={false}
>
<HStack spacing={2}>
<EditablePreview as={StatNumber} fontSize="md" fontFamily="mono" />
<EditableInput type="password" fontSize="md" fontFamily="mono" />
<EditableUsernameControls />
</HStack>
</Editable>
</Stat>
<Stat>
<StatLabel>Expire le</StatLabel>
<StatNumber fontSize="md">{formatDate(me.expired_at)}</StatNumber>