From 2cc532a64dad642a4109d23862bf58ea6671b22c Mon Sep 17 00:00:00 2001 From: Nuxgrid Date: Wed, 29 Jul 2026 09:28:33 +0200 Subject: [PATCH] feat: add update username --- web/package-lock.json | 40 ++++++------- web/package.json | 1 + web/src/pages/backoffice/Profile.tsx | 84 +++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 23 deletions(-) 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 && (