From 122dfb3e7856f4527324e2b292832e6681c9bb0f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:56:17 -0600 Subject: [PATCH] frontend tweaks for password dialog --- web/src/components/menu/AccountSettings.tsx | 32 ++++++++++++++-- web/src/components/menu/GeneralSettings.tsx | 33 +++++++++++++++-- web/src/views/settings/AuthenticationView.tsx | 37 +++++++++++++++++-- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/web/src/components/menu/AccountSettings.tsx b/web/src/components/menu/AccountSettings.tsx index 723aa0ccc..cb1d5fefa 100644 --- a/web/src/components/menu/AccountSettings.tsx +++ b/web/src/components/menu/AccountSettings.tsx @@ -42,19 +42,37 @@ export default function AccountSettings({ className }: AccountSettingsProps) { const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`; const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); + const [passwordError, setPasswordError] = useState(null); const Container = isDesktop ? DropdownMenu : Drawer; const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger; const Content = isDesktop ? DropdownMenuContent : DrawerContent; const MenuItem = isDesktop ? DropdownMenuItem : DrawerClose; - const handlePasswordSave = async (password: string) => { + const verifyOldPassword = async (oldPassword: string): Promise => { + if (!profile?.username || profile.username === "anonymous") return false; + try { + const response = await axios.post("login", { + user: profile.username, + password: oldPassword, + }); + return response.status === 200; + } catch (error) { + return false; + } + }; + + const handlePasswordSave = async (password: string, oldPassword?: string) => { if (!profile?.username || profile.username === "anonymous") return; axios - .put(`users/${profile.username}/password`, { password }) + .put(`users/${profile.username}/password`, { + password, + old_password: oldPassword, + }) .then((response) => { if (response.status === 200) { setPasswordDialogOpen(false); + setPasswordError(null); toast.success(t("users.toast.success.updatePassword"), { position: "top-center", }); @@ -65,6 +83,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) { error.response?.data?.message || error.response?.data?.detail || "Unknown error"; + + setPasswordDialogOpen(false); + setPasswordError(null); toast.error( t("users.toast.error.setPasswordFailed", { errorMessage, @@ -154,7 +175,12 @@ export default function AccountSettings({ className }: AccountSettingsProps) { setPasswordDialogOpen(false)} + onCancel={() => { + setPasswordDialogOpen(false); + setPasswordError(null); + }} + onVerifyOldPassword={verifyOldPassword} + initialError={passwordError} username={profile?.username} /> diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index 16c6eb9f8..ca7fa9bab 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -116,13 +116,32 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { const SubItemContent = isDesktop ? DropdownMenuSubContent : DialogContent; const Portal = isDesktop ? DropdownMenuPortal : DialogPortal; - const handlePasswordSave = async (password: string) => { + const [passwordError, setPasswordError] = useState(null); + + const verifyOldPassword = async (oldPassword: string): Promise => { + if (!profile?.username || profile.username === "anonymous") return false; + try { + const response = await axios.post("login", { + user: profile.username, + password: oldPassword, + }); + return response.status === 200; + } catch (error) { + return false; + } + }; + + const handlePasswordSave = async (password: string, oldPassword?: string) => { if (!profile?.username || profile.username === "anonymous") return; axios - .put(`users/${profile.username}/password`, { password }) + .put(`users/${profile.username}/password`, { + password, + old_password: oldPassword, + }) .then((response) => { if (response.status === 200) { setPasswordDialogOpen(false); + setPasswordError(null); toast.success( t("users.toast.success.updatePassword", { ns: "views/settings", @@ -138,6 +157,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { error.response?.data?.message || error.response?.data?.detail || "Unknown error"; + + setPasswordDialogOpen(false); + setPasswordError(null); toast.error( t("users.toast.error.setPasswordFailed", { ns: "views/settings", @@ -554,7 +576,12 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { setPasswordDialogOpen(false)} + onCancel={() => { + setPasswordDialogOpen(false); + setPasswordError(null); + }} + onVerifyOldPassword={verifyOldPassword} + initialError={passwordError} username={profile?.username} /> diff --git a/web/src/views/settings/AuthenticationView.tsx b/web/src/views/settings/AuthenticationView.tsx index 124348813..29aa138ca 100644 --- a/web/src/views/settings/AuthenticationView.tsx +++ b/web/src/views/settings/AuthenticationView.tsx @@ -57,6 +57,7 @@ export default function AuthenticationView({ const [showCreateRole, setShowCreateRole] = useState(false); const [showEditRole, setShowEditRole] = useState(false); const [showDeleteRole, setShowDeleteRole] = useState(false); + const [passwordError, setPasswordError] = useState(null); const [selectedUser, setSelectedUser] = useState(); const [selectedUserRole, setSelectedUserRole] = useState(); @@ -69,13 +70,30 @@ export default function AuthenticationView({ document.title = t("documentTitle.authentication"); }, [t]); + const onVerifyOldPassword = useCallback( + async (oldPassword: string): Promise => { + if (!selectedUser) return false; + try { + const response = await axios.post("login", { + user: selectedUser, + password: oldPassword, + }); + return response.status === 200; + } catch (error) { + return false; + } + }, + [selectedUser], + ); + const onSavePassword = useCallback( - (user: string, password: string) => { + (user: string, password: string, oldPassword?: string) => { axios - .put(`users/${user}/password`, { password }) + .put(`users/${user}/password`, { password, old_password: oldPassword }) .then((response) => { if (response.status === 200) { setShowSetPassword(false); + setPasswordError(null); toast.success(t("users.toast.success.updatePassword"), { position: "top-center", }); @@ -86,6 +104,10 @@ export default function AuthenticationView({ error.response?.data?.message || error.response?.data?.detail || "Unknown error"; + + // Close dialog and show toast for any errors + setShowSetPassword(false); + setPasswordError(null); toast.error( t("users.toast.error.setPasswordFailed", { errorMessage, @@ -563,8 +585,15 @@ export default function AuthenticationView({ setShowSetPassword(false)} - onSave={(password) => onSavePassword(selectedUser!, password)} + onCancel={() => { + setShowSetPassword(false); + setPasswordError(null); + }} + onVerifyOldPassword={onVerifyOldPassword} + initialError={passwordError} + onSave={(password, oldPassword) => + onSavePassword(selectedUser!, password, oldPassword) + } />