diff --git a/package.json b/package.json index 70cf57b24..bfeb3bfcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@entando/app-builder", - "version": "7.3.0", + "version": "7.3.0-SNAPSHOT", "author": "Entando", "homepage": "https://github.com/entando/app-builder", "license": "MIT", diff --git a/src/api/avatar.js b/src/api/avatar.js new file mode 100644 index 000000000..35b331652 --- /dev/null +++ b/src/api/avatar.js @@ -0,0 +1,39 @@ +import { makeRequest, METHODS } from '@entando/apimanager'; +import { FILE_BROWSER_FILE } from 'test/mocks/fileBrowser'; + +const AVATAR_ENDPOINT = '/api/userProfiles/avatar'; + +export const getAvatar = () => + makeRequest({ + uri: AVATAR_ENDPOINT, + method: METHODS.GET, + mockResponse: FILE_BROWSER_FILE, + useAuthentication: true, + }); + +export const postAvatar = avatar => + makeRequest({ + uri: AVATAR_ENDPOINT, + method: METHODS.POST, + body: avatar, + mockResponse: FILE_BROWSER_FILE, + useAuthentication: true, + }); + +export const updateAvatar = avatar => + makeRequest({ + uri: AVATAR_ENDPOINT, + method: METHODS.PUT, + body: avatar, + mockResponse: FILE_BROWSER_FILE, + useAuthentication: true, + }); + +export const deleteAvatar = () => + makeRequest({ + uri: AVATAR_ENDPOINT, + method: METHODS.DELETE, + mockResponse: FILE_BROWSER_FILE, + useAuthentication: true, + }); + diff --git a/src/state/avatar/actions.js b/src/state/avatar/actions.js new file mode 100644 index 000000000..05fb9a974 --- /dev/null +++ b/src/state/avatar/actions.js @@ -0,0 +1,51 @@ +import { postAvatar, getAvatar } from 'api/avatar'; +import { getBase64 } from 'state/file-browser/actions'; +import { toggleLoading } from 'state/loading/actions'; +import { addToast, addErrors, TOAST_SUCCESS, TOAST_ERROR } from '@entando/messages'; +import { SET_AVATAR_FILE_NAME } from './types'; + +const setAvatarFilename = filename => ({ + type: SET_AVATAR_FILE_NAME, + payload: { + filename, + }, +}); + + +export const createFileObject = async (avatar) => { + const base64 = await getBase64(avatar); + return { filename: avatar.name, base64 }; +}; + +export const uploadAvatar = + (avatar, loader = 'uploadAvatar') => + async (dispatch) => { + try { + dispatch(toggleLoading(loader)); + const requestObject = await createFileObject(avatar); + const response = await postAvatar(requestObject); + const avatarCreated = await response.json(); + dispatch(setAvatarFilename(avatarCreated.payload.filename)); + dispatch(toggleLoading(loader)); + dispatch(addToast({ id: 'fileBrowser.uploadFileComplete' }, TOAST_SUCCESS)); + } catch (error) { + dispatch(toggleLoading(loader)); + const message = { id: 'fileBrowser.uploadFileError', values: { errmsg: error } }; + dispatch(addErrors(error)); + dispatch(addToast(message, TOAST_ERROR)); + } + }; + +export const fetchAvatar = (loader = 'fetchAvatar') => async (dispatch) => { + try { + dispatch(toggleLoading(loader)); + const response = await getAvatar(); + const avatar = await response.json(); + if (avatar.payload.filename) dispatch(setAvatarFilename(avatar.payload.filename)); + dispatch(toggleLoading(loader)); + } catch (error) { + dispatch(toggleLoading(loader)); + dispatch(addErrors(error)); + dispatch(addToast(error.message, TOAST_ERROR)); + } +}; diff --git a/src/state/avatar/reducer.js b/src/state/avatar/reducer.js new file mode 100644 index 000000000..95d4372e8 --- /dev/null +++ b/src/state/avatar/reducer.js @@ -0,0 +1,20 @@ +import { combineReducers } from 'redux'; +import { SET_AVATAR_FILE_NAME } from './types'; + +const initialState = { + filename: '', +}; + +const filename = (state = initialState.filename, action = {}) => { + switch (action.type) { + case SET_AVATAR_FILE_NAME: { + return action.payload.filename; + } + default: + return state; + } +}; + +export default combineReducers({ + filename, +}); diff --git a/src/state/avatar/selectors.js b/src/state/avatar/selectors.js new file mode 100644 index 000000000..65fe4c3f9 --- /dev/null +++ b/src/state/avatar/selectors.js @@ -0,0 +1,8 @@ +import { createSelector } from 'reselect'; + +export const getAvatar = state => state.avatar; + +export const getAvatarFilename = createSelector( + getAvatar, + avatar => avatar.filename, +); diff --git a/src/state/avatar/types.js b/src/state/avatar/types.js new file mode 100644 index 000000000..6cbffea83 --- /dev/null +++ b/src/state/avatar/types.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const SET_AVATAR_FILE_NAME = 'avatar/filename'; diff --git a/src/state/file-browser/actions.js b/src/state/file-browser/actions.js index fbd8b8c73..4d7555877 100644 --- a/src/state/file-browser/actions.js +++ b/src/state/file-browser/actions.js @@ -34,7 +34,7 @@ const wrapApiCall = apiFunc => (...args) => async (dispatch) => { // thunks -const getBase64 = file => ( +export const getBase64 = file => ( new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file); diff --git a/src/state/rootReducer.js b/src/state/rootReducer.js index 2b52b0217..c0fc0f633 100644 --- a/src/state/rootReducer.js +++ b/src/state/rootReducer.js @@ -44,6 +44,7 @@ import editContent from 'state/edit-content/reducer'; import contents from 'state/contents/reducer'; import system from 'state/system/reducer'; import currentTenant from 'state/multi-tenancy/reducer'; +import avatar from 'state/avatar/reducer'; import entandoApps from 'entando-apps'; import hub from 'state/component-repository/hub/reducer'; @@ -103,6 +104,7 @@ const reducerDef = { mfe, currentTenant, currentSystemConfiguration, + avatar, }; // app root reducer diff --git a/src/ui/users/common/ProfileImageUploader.js b/src/ui/users/common/ProfileImageUploader.js index ae3a87b6b..a156ddde3 100644 --- a/src/ui/users/common/ProfileImageUploader.js +++ b/src/ui/users/common/ProfileImageUploader.js @@ -4,8 +4,8 @@ import { Dropdown, MenuItem } from 'patternfly-react'; import { useDispatch } from 'react-redux'; import md5 from 'md5'; -import { uploadFile } from 'state/file-browser/actions'; import { useDynamicResourceUrl } from 'hooks/useDynamicResourceUrl'; +import { uploadAvatar } from 'state/avatar/actions'; const FILE_BROWSER_PATH = 'static/profile'; const GRAVATAR = 'GRAVATAR'; @@ -24,7 +24,7 @@ const ProfileImageUploader = ({ const imageProvider = useDynamicResourceUrl(FILE_BROWSER_PATH); const onFileChange = ({ target: { files } }) => { - dispatch(uploadFile(files[0], FILE_BROWSER_PATH)).then(() => onChange(files[0].name)); + dispatch(uploadAvatar(files[0])).then(() => onChange(files[0].name)); }; const handleUploadClick = () => { diff --git a/src/ui/users/my-profile/MyProfileEditForm.js b/src/ui/users/my-profile/MyProfileEditForm.js index 899767e16..ae4f91102 100644 --- a/src/ui/users/my-profile/MyProfileEditForm.js +++ b/src/ui/users/my-profile/MyProfileEditForm.js @@ -151,7 +151,7 @@ export class MyProfileEditFormBody extends Component { render() { const { profileTypesAttributes, defaultLanguage, languages, intl, userEmail, onChangeProfilePicture, - userProfileForm, + avatar, } = this.props; const { editMode } = this.state; @@ -226,13 +226,12 @@ export class MyProfileEditFormBody extends Component { return field(intl, attribute, !editMode); }); - const { profilepicture } = userProfileForm; return (