feat(client): Add ability for admins to edit user avatars (#1604)

This commit is contained in:
Sebastian Dichler
2026-03-25 11:24:27 +01:00
committed by GitHub
parent 952a078aa1
commit d3ff3e2db8
44 changed files with 101 additions and 31 deletions
@@ -3,20 +3,22 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
import { FilePicker, Popup } from '../../../../lib/custom-ui';
import { FilePicker, Popup } from '../../../lib/custom-ui';
import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import styles from './EditAvatarStep.module.scss';
import styles from './EditUserAvatarStep.module.scss';
const EditAvatarStep = React.memo(({ onClose }) => {
const defaultValue = useSelector((state) => selectors.selectCurrentUser(state).avatar);
const EditUserAvatarStep = React.memo(({ id, onBack, onClose }) => {
const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);
const avatar = useSelector((state) => selectUserById(state, id).avatar);
const dispatch = useDispatch();
const [t] = useTranslation();
@@ -26,33 +28,33 @@ const EditAvatarStep = React.memo(({ onClose }) => {
const handleFileSelect = useCallback(
(file) => {
dispatch(
entryActions.updateCurrentUserAvatar({
entryActions.updateUserAvatar(id, {
file,
}),
);
onClose();
},
[onClose, dispatch],
[dispatch, id, onClose],
);
const handleDeleteClick = useCallback(() => {
dispatch(
entryActions.updateCurrentUser({
entryActions.updateUser(id, {
avatar: null,
}),
);
onClose();
}, [onClose, dispatch]);
}, [dispatch, id, onClose]);
useEffect(() => {
fieldRef.current.focus();
fieldRef.current?.focus?.();
}, []);
return (
<>
<Popup.Header>
<Popup.Header onBack={onBack}>
{t('common.editAvatar', {
context: 'title',
})}
@@ -67,7 +69,7 @@ const EditAvatarStep = React.memo(({ onClose }) => {
/>
</FilePicker>
</div>
{defaultValue && (
{avatar && (
<Button negative content={t('action.deleteAvatar')} onClick={handleDeleteClick} />
)}
</Popup.Content>
@@ -75,8 +77,14 @@ const EditAvatarStep = React.memo(({ onClose }) => {
);
});
EditAvatarStep.propTypes = {
EditUserAvatarStep.propTypes = {
id: PropTypes.string.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
export default EditAvatarStep;
EditUserAvatarStep.defaultProps = {
onBack: undefined,
};
export default EditUserAvatarStep;
@@ -3,6 +3,6 @@
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import AccountPane from './AccountPane';
import EditUserAvatarStep from './EditUserAvatarStep';
export default AccountPane;
export default EditUserAvatarStep;
@@ -8,16 +8,16 @@ import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button, Divider, Dropdown, Header, Tab } from 'semantic-ui-react';
import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
import { usePopupInClosableContext } from '../../../../hooks';
import locales from '../../../../locales';
import EditAvatarStep from './EditAvatarStep';
import EditUserInformation from '../../EditUserInformation';
import EditUserUsernameStep from '../../EditUserUsernameStep';
import EditUserEmailStep from '../../EditUserEmailStep';
import EditUserPasswordStep from '../../EditUserPasswordStep';
import UserAvatar from '../../UserAvatar';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import { usePopupInClosableContext } from '../../../hooks';
import locales from '../../../locales';
import EditUserInformation from '../EditUserInformation';
import EditUserAvatarStep from '../EditUserAvatarStep';
import EditUserUsernameStep from '../EditUserUsernameStep';
import EditUserEmailStep from '../EditUserEmailStep';
import EditUserPasswordStep from '../EditUserPasswordStep';
import UserAvatar from '../UserAvatar';
import styles from './AccountPane.module.scss';
@@ -34,7 +34,7 @@ const AccountPane = React.memo(() => {
[dispatch],
);
const EditAvatarPopup = usePopupInClosableContext(EditAvatarStep);
const EditUserAvatarPopup = usePopupInClosableContext(EditUserAvatarStep);
const EditUserUsernamePopup = usePopupInClosableContext(EditUserUsernameStep);
const EditUserEmailPopup = usePopupInClosableContext(EditUserEmailStep);
const EditUserPasswordPopup = usePopupInClosableContext(EditUserPasswordStep);
@@ -45,9 +45,9 @@ const AccountPane = React.memo(() => {
return (
<Tab.Pane attached={false} className={styles.wrapper}>
<EditAvatarPopup>
<EditUserAvatarPopup id={user.id}>
<UserAvatar id={user.id} size="massive" isDisabled={user.isAvatarUpdating} />
</EditAvatarPopup>
</EditUserAvatarPopup>
<br />
<br />
<EditUserInformation id={user.id} />