frontend tweaks for password dialog

This commit is contained in:
Josh Hawkins 2025-12-07 19:56:17 -06:00
parent b05a7ccd66
commit 122dfb3e78
3 changed files with 92 additions and 10 deletions

View File

@ -42,19 +42,37 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`; const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`;
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
const [passwordError, setPasswordError] = useState<string | null>(null);
const Container = isDesktop ? DropdownMenu : Drawer; const Container = isDesktop ? DropdownMenu : Drawer;
const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger; const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
const Content = isDesktop ? DropdownMenuContent : DrawerContent; const Content = isDesktop ? DropdownMenuContent : DrawerContent;
const MenuItem = isDesktop ? DropdownMenuItem : DrawerClose; const MenuItem = isDesktop ? DropdownMenuItem : DrawerClose;
const handlePasswordSave = async (password: string) => { const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
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; if (!profile?.username || profile.username === "anonymous") return;
axios axios
.put(`users/${profile.username}/password`, { password }) .put(`users/${profile.username}/password`, {
password,
old_password: oldPassword,
})
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
setPasswordDialogOpen(false); setPasswordDialogOpen(false);
setPasswordError(null);
toast.success(t("users.toast.success.updatePassword"), { toast.success(t("users.toast.success.updatePassword"), {
position: "top-center", position: "top-center",
}); });
@ -65,6 +83,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
error.response?.data?.message || error.response?.data?.message ||
error.response?.data?.detail || error.response?.data?.detail ||
"Unknown error"; "Unknown error";
setPasswordDialogOpen(false);
setPasswordError(null);
toast.error( toast.error(
t("users.toast.error.setPasswordFailed", { t("users.toast.error.setPasswordFailed", {
errorMessage, errorMessage,
@ -154,7 +175,12 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
<SetPasswordDialog <SetPasswordDialog
show={passwordDialogOpen} show={passwordDialogOpen}
onSave={handlePasswordSave} onSave={handlePasswordSave}
onCancel={() => setPasswordDialogOpen(false)} onCancel={() => {
setPasswordDialogOpen(false);
setPasswordError(null);
}}
onVerifyOldPassword={verifyOldPassword}
initialError={passwordError}
username={profile?.username} username={profile?.username}
/> />
</Container> </Container>

View File

@ -116,13 +116,32 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
const SubItemContent = isDesktop ? DropdownMenuSubContent : DialogContent; const SubItemContent = isDesktop ? DropdownMenuSubContent : DialogContent;
const Portal = isDesktop ? DropdownMenuPortal : DialogPortal; const Portal = isDesktop ? DropdownMenuPortal : DialogPortal;
const handlePasswordSave = async (password: string) => { const [passwordError, setPasswordError] = useState<string | null>(null);
const verifyOldPassword = async (oldPassword: string): Promise<boolean> => {
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; if (!profile?.username || profile.username === "anonymous") return;
axios axios
.put(`users/${profile.username}/password`, { password }) .put(`users/${profile.username}/password`, {
password,
old_password: oldPassword,
})
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
setPasswordDialogOpen(false); setPasswordDialogOpen(false);
setPasswordError(null);
toast.success( toast.success(
t("users.toast.success.updatePassword", { t("users.toast.success.updatePassword", {
ns: "views/settings", ns: "views/settings",
@ -138,6 +157,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
error.response?.data?.message || error.response?.data?.message ||
error.response?.data?.detail || error.response?.data?.detail ||
"Unknown error"; "Unknown error";
setPasswordDialogOpen(false);
setPasswordError(null);
toast.error( toast.error(
t("users.toast.error.setPasswordFailed", { t("users.toast.error.setPasswordFailed", {
ns: "views/settings", ns: "views/settings",
@ -554,7 +576,12 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<SetPasswordDialog <SetPasswordDialog
show={passwordDialogOpen} show={passwordDialogOpen}
onSave={handlePasswordSave} onSave={handlePasswordSave}
onCancel={() => setPasswordDialogOpen(false)} onCancel={() => {
setPasswordDialogOpen(false);
setPasswordError(null);
}}
onVerifyOldPassword={verifyOldPassword}
initialError={passwordError}
username={profile?.username} username={profile?.username}
/> />
</> </>

View File

@ -57,6 +57,7 @@ export default function AuthenticationView({
const [showCreateRole, setShowCreateRole] = useState(false); const [showCreateRole, setShowCreateRole] = useState(false);
const [showEditRole, setShowEditRole] = useState(false); const [showEditRole, setShowEditRole] = useState(false);
const [showDeleteRole, setShowDeleteRole] = useState(false); const [showDeleteRole, setShowDeleteRole] = useState(false);
const [passwordError, setPasswordError] = useState<string | null>(null);
const [selectedUser, setSelectedUser] = useState<string>(); const [selectedUser, setSelectedUser] = useState<string>();
const [selectedUserRole, setSelectedUserRole] = useState<string>(); const [selectedUserRole, setSelectedUserRole] = useState<string>();
@ -69,13 +70,30 @@ export default function AuthenticationView({
document.title = t("documentTitle.authentication"); document.title = t("documentTitle.authentication");
}, [t]); }, [t]);
const onVerifyOldPassword = useCallback(
async (oldPassword: string): Promise<boolean> => {
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( const onSavePassword = useCallback(
(user: string, password: string) => { (user: string, password: string, oldPassword?: string) => {
axios axios
.put(`users/${user}/password`, { password }) .put(`users/${user}/password`, { password, old_password: oldPassword })
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
setShowSetPassword(false); setShowSetPassword(false);
setPasswordError(null);
toast.success(t("users.toast.success.updatePassword"), { toast.success(t("users.toast.success.updatePassword"), {
position: "top-center", position: "top-center",
}); });
@ -86,6 +104,10 @@ export default function AuthenticationView({
error.response?.data?.message || error.response?.data?.message ||
error.response?.data?.detail || error.response?.data?.detail ||
"Unknown error"; "Unknown error";
// Close dialog and show toast for any errors
setShowSetPassword(false);
setPasswordError(null);
toast.error( toast.error(
t("users.toast.error.setPasswordFailed", { t("users.toast.error.setPasswordFailed", {
errorMessage, errorMessage,
@ -563,8 +585,15 @@ export default function AuthenticationView({
</div> </div>
<SetPasswordDialog <SetPasswordDialog
show={showSetPassword} show={showSetPassword}
onCancel={() => setShowSetPassword(false)} onCancel={() => {
onSave={(password) => onSavePassword(selectedUser!, password)} setShowSetPassword(false);
setPasswordError(null);
}}
onVerifyOldPassword={onVerifyOldPassword}
initialError={passwordError}
onSave={(password, oldPassword) =>
onSavePassword(selectedUser!, password, oldPassword)
}
/> />
<DeleteUserDialog <DeleteUserDialog
show={showDelete} show={showDelete}