diff --git a/web/package-lock.json b/web/package-lock.json
index 328318e..0c6e074 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -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",
diff --git a/web/package.json b/web/package.json
index 1db0f91..555b313 100644
--- a/web/package.json
+++ b/web/package.json
@@ -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",
diff --git a/web/src/pages/backoffice/Profile.tsx b/web/src/pages/backoffice/Profile.tsx
index 1361593..46dd678 100644
--- a/web/src/pages/backoffice/Profile.tsx
+++ b/web/src/pages/backoffice/Profile.tsx
@@ -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 ? (
+
+ }
+ {...getSubmitButtonProps()}
+ />
+ }
+ {...getCancelButtonProps()}
+ />
+
+ ) : (
+ }
+ {...getEditButtonProps()}
+ />
+ )
+}
+
export function Profile() {
const toast = useToast()
const navigate = useNavigate()
@@ -63,6 +98,7 @@ export function Profile() {
const [me, setMe] = useState(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() {
-
- {me.username}
-
+
+
+
+
+
+
+
{roleLabel(me.role)}
{me.type_abonnement && (