feat: add update username
ci-web / test (push) Has been cancelled

This commit is contained in:
Nuxgrid
2026-07-29 09:28:33 +02:00
parent ef280799c1
commit 2cc532a64d
3 changed files with 102 additions and 23 deletions
+20 -20
View File
@@ -8,6 +8,7 @@
"name": "omnex-web",
"version": "0.1.0",
"dependencies": {
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.10.4",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
@@ -88,7 +89,6 @@
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.29.7",
@@ -368,12 +368,20 @@
"react": ">=18"
}
},
"node_modules/@chakra-ui/icons": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.2.4.tgz",
"integrity": "sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==",
"peerDependencies": {
"@chakra-ui/react": ">=2.0.0",
"react": ">=18"
}
},
"node_modules/@chakra-ui/react": {
"version": "2.10.10",
"resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.10.10.tgz",
"integrity": "sha512-uahxAfb83/O9fcgBm3ew/nVeQiNgE0uQf8NdQQp3I6u/PCuR2OjSEneV4Ba86W8z50Ppwcxvf9XDk5btYceggQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@chakra-ui/hooks": "2.4.6",
"@chakra-ui/styled-system": "2.12.5",
@@ -399,7 +407,6 @@
"resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.12.5.tgz",
"integrity": "sha512-sy39LJWnOvGcOHYBXQxT3bC5zj+R7GVUgxzkkoqx+auV5KVKOOE5aZD+POsciWEt86bqdEa+LpFm+E5pcdbkyg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@chakra-ui/utils": "2.2.6",
"csstype": "^3.1.2"
@@ -534,7 +541,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -558,7 +564,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -621,7 +626,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -665,7 +669,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1736,7 +1739,6 @@
"resolved": "https://registry.npmjs.org/@saas-ui/react/-/react-2.11.4.tgz",
"integrity": "sha512-rSit+RrsV1xkrli4tne1TjkEjGbkyMefNhP34iNfjbrWMuyu1F/csRx8F6tarYq7NRFmYbF6AnTY6ijgDQ2WBQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@chakra-ui/utils": "^2.2.3",
"@saas-ui/core": "2.8.1",
@@ -1946,7 +1948,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -2034,7 +2037,6 @@
"integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -2046,7 +2048,6 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
@@ -2287,6 +2288,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -2297,6 +2299,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -2391,7 +2394,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.44",
"caniuse-lite": "^1.0.30001806",
@@ -2659,7 +2661,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/dunder-proto": {
"version": "1.0.1",
@@ -2881,7 +2884,6 @@
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
"integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
"license": "MIT",
"peer": true,
"dependencies": {
"motion-dom": "^11.18.1",
"motion-utils": "^11.18.1",
@@ -3184,7 +3186,6 @@
"integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssstyle": "^4.1.0",
"data-urls": "^5.0.0",
@@ -3298,6 +3299,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -3537,6 +3539,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -3551,7 +3554,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/prop-types": {
"version": "15.8.1",
@@ -3579,7 +3583,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -3625,7 +3628,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -3668,7 +3670,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.82.0.tgz",
"integrity": "sha512-Zw/uFZ2dO+02GHlBn7JFGn8kZJ7LdM33B/0BXOovzFay+CMhf94JMw5BVu+F1tVkUKjNvBuaE3fz5BJhga10Tg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -4232,7 +4233,6 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
+1
View File
@@ -11,6 +11,7 @@
"test": "vitest run"
},
"dependencies": {
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.10.4",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
+81 -3
View File
@@ -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 && (