diff --git a/frontend/src/component/admin/users/UsersList/ResetPassword/ResetPassword.tsx b/frontend/src/component/admin/users/UsersList/ResetPassword/ResetPassword.tsx new file mode 100644 index 0000000000..2aa1ecbd23 --- /dev/null +++ b/frontend/src/component/admin/users/UsersList/ResetPassword/ResetPassword.tsx @@ -0,0 +1,123 @@ +import React, { useState } from 'react'; +import classnames from 'classnames'; +import { Box, styled, TextField, Typography } from '@mui/material'; +import { modalStyles } from 'component/admin/users/util'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; +import { useThemeStyles } from 'themes/themeStyles'; +import { IUser } from 'interfaces/user'; +import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; +import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import useToast from 'hooks/useToast'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import { LinkField } from '../../LinkField/LinkField'; + +const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({ + width: theme.spacing(5), + height: theme.spacing(5), + margin: 0, +})); + +interface IChangePasswordProps { + showDialog: boolean; + closeDialog: () => void; + user: IUser; +} + +const ResetPassword = ({ + showDialog, + closeDialog, + user, +}: IChangePasswordProps) => { + const { classes: themeStyles } = useThemeStyles(); + const { resetPassword } = useAdminUsersApi(); + const { setToastApiError } = useToast(); + const [resetLink, setResetLink] = useState(''); + + const submit = async (event: React.SyntheticEvent) => { + event.preventDefault(); + if (!user.email) { + setToastApiError( + "You can't reset the password of a user who doesn't have an email address.", + ); + return; + } + + try { + const token = await resetPassword(user.email).then((res) => + res.ok ? res.json() : undefined, + ); + + if (token) { + setResetLink(token.resetPasswordUrl); + } else { + setToastApiError( + 'Could not reset password. This may be to prevent too many resets. Try again in a minute.', + ); + } + } catch (error) { + setToastApiError(formatUnknownError(error)); + } + }; + + const onCancel = (event: React.SyntheticEvent) => { + event.preventDefault(); + closeDialog(); + }; + + const closeConfirm = () => { + setResetLink(''); + closeDialog(); + }; + + return ( + +
+ + Resetting password for user + +
+ + + {user.username || user.email} + +
+
+ + + + + The user can use this link to reset their password. You + should not share this link with anyone but the user in + question. + + + + +
+ ); +}; + +export default ResetPassword; diff --git a/frontend/src/component/admin/users/UsersList/UsersActionsCell/UsersActionsCell.tsx b/frontend/src/component/admin/users/UsersList/UsersActionsCell/UsersActionsCell.tsx index 720b2679ea..a79b1cbfbe 100644 --- a/frontend/src/component/admin/users/UsersList/UsersActionsCell/UsersActionsCell.tsx +++ b/frontend/src/component/admin/users/UsersList/UsersActionsCell/UsersActionsCell.tsx @@ -1,4 +1,4 @@ -import { Delete, Edit, Lock } from '@mui/icons-material'; +import { Delete, Edit, Lock, LockReset } from '@mui/icons-material'; import { Box, styled } from '@mui/material'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; @@ -12,12 +12,14 @@ const StyledBox = styled(Box)(() => ({ interface IUsersActionsCellProps { onEdit: (event: React.SyntheticEvent) => void; onChangePassword: (event: React.SyntheticEvent) => void; + onResetPassword: (event: React.SyntheticEvent) => void; onDelete: (event: React.SyntheticEvent) => void; } export const UsersActionsCell: VFC = ({ onEdit, onChangePassword, + onResetPassword, onDelete, }) => { return ( @@ -42,6 +44,16 @@ export const UsersActionsCell: VFC = ({ > + + + { const [pwDialog, setPwDialog] = useState<{ open: boolean; user?: IUser }>({ open: false, }); + const [resetPwDialog, setResetPwDialog] = useState<{ + open: boolean; + user?: IUser; + }>({ + open: false, + }); const { isEnterprise } = useUiConfig(); const [delDialog, setDelDialog] = useState(false); const [showConfirm, setShowConfirm] = useState(false); @@ -75,10 +82,20 @@ const UsersList = () => { setPwDialog({ open: true, user }); }; + const openResetPwDialog = + (user: IUser) => (e: React.SyntheticEvent) => { + e.preventDefault(); + setResetPwDialog({ open: true, user }); + }; + const closePwDialog = () => { setPwDialog({ open: false }); }; + const closeResetPwDialog = () => { + setResetPwDialog({ open: false }); + }; + const onDeleteUser = async (user: IUser) => { try { await removeUser(user.id); @@ -180,10 +197,11 @@ const UsersList = () => { navigate(`/admin/users/${user.id}/edit`); }} onChangePassword={openPwDialog(user)} + onResetPassword={openResetPwDialog(user)} onDelete={openDelDialog(user)} /> ), - width: 150, + width: 200, disableSortBy: true, }, // Always hidden -- for search @@ -334,6 +352,18 @@ const UsersList = () => { /> )} /> + + ( + + )} + /> + { return makeRequest(req.caller, req.id); }; + const resetPassword = async (email: string) => { + const requestId = 'resetPassword'; + const req = createRequest( + 'api/admin/user-admin/reset-password', + { + method: 'POST', + body: JSON.stringify({ id: email }), + }, + requestId, + ); + + return makeRequest(req.caller, req.id); + }; + return { addUser, updateUser, removeUser, changePassword, validatePassword, + resetPassword, userApiErrors: errors, userLoading: loading, };