@@ -4,11 +4,16 @@ import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Container,
|
||||
Divider,
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
IconButton,
|
||||
SimpleGrid,
|
||||
Spinner,
|
||||
Stack,
|
||||
@@ -16,8 +21,10 @@ import {
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
Text,
|
||||
useEditableControls,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { CheckIcon, CloseIcon, EditIcon } from '@chakra-ui/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { api, ApiError } from '../../lib/api'
|
||||
import { useAuth } from '../../lib/auth'
|
||||
@@ -56,6 +63,34 @@ function formatDate(iso: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function EditableUsernameControls() {
|
||||
const { isEditing, getSubmitButtonProps, getCancelButtonProps, getEditButtonProps } =
|
||||
useEditableControls()
|
||||
|
||||
return isEditing ? (
|
||||
<ButtonGroup size="sm" spacing={1}>
|
||||
<IconButton
|
||||
aria-label="Enregistrer"
|
||||
icon={<CheckIcon />}
|
||||
{...getSubmitButtonProps()}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="Annuler"
|
||||
icon={<CloseIcon />}
|
||||
{...getCancelButtonProps()}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
<IconButton
|
||||
aria-label="Modifier le nom d'utilisateur"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<EditIcon />}
|
||||
{...getEditButtonProps()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Profile() {
|
||||
const toast = useToast()
|
||||
const navigate = useNavigate()
|
||||
@@ -63,6 +98,7 @@ export function Profile() {
|
||||
const [me, setMe] = useState<MeResponse | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loggingOut, setLoggingOut] = useState(false)
|
||||
const [updatingUsername, setUpdatingUsername] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -85,6 +121,30 @@ export function Profile() {
|
||||
}
|
||||
}, [navigate, toast])
|
||||
|
||||
const handleUpdateUsername = async (newUsername: string) => {
|
||||
const trimmed = newUsername.trim()
|
||||
if (!me || !trimmed || trimmed === me.username) return
|
||||
|
||||
setUpdatingUsername(true)
|
||||
try {
|
||||
const res = await api.updateUsername(trimmed)
|
||||
setMe({ ...me, username: res.username })
|
||||
toast({ status: 'success', title: "Nom d'utilisateur mis à jour" })
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 401) {
|
||||
navigate('/login')
|
||||
return
|
||||
}
|
||||
toast({
|
||||
status: 'error',
|
||||
title: "Impossible de mettre à jour le nom d'utilisateur",
|
||||
description: err instanceof ApiError ? err.message : undefined,
|
||||
})
|
||||
} finally {
|
||||
setUpdatingUsername(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
setLoggingOut(true)
|
||||
try {
|
||||
@@ -127,9 +187,27 @@ export function Profile() {
|
||||
<Flex align="center" gap={5} wrap="wrap">
|
||||
<Avatar name={me.username} size="xl" />
|
||||
<Box>
|
||||
<Heading size="md" fontFamily="mono">
|
||||
{me.username}
|
||||
</Heading>
|
||||
<Editable
|
||||
key={me.username}
|
||||
defaultValue={me.username}
|
||||
onSubmit={handleUpdateUsername}
|
||||
isDisabled={updatingUsername}
|
||||
submitOnBlur={false}
|
||||
>
|
||||
<HStack spacing={2}>
|
||||
<EditablePreview
|
||||
as={Heading}
|
||||
size="md"
|
||||
fontFamily="mono"
|
||||
/>
|
||||
<EditableInput
|
||||
fontFamily="mono"
|
||||
fontSize="md"
|
||||
fontWeight="bold"
|
||||
/>
|
||||
<EditableUsernameControls />
|
||||
</HStack>
|
||||
</Editable>
|
||||
<HStack mt={2} spacing={2}>
|
||||
<Badge colorScheme={roleColor(me.role)}>{roleLabel(me.role)}</Badge>
|
||||
{me.type_abonnement && (
|
||||
|
||||
Reference in New Issue
Block a user