From 0eac0f155b427c448acc36f548bd54d28369ee45 Mon Sep 17 00:00:00 2001 From: Mike Joyce Date: Mon, 18 Mar 2019 13:02:57 -0400 Subject: [PATCH] Add toast notifications for favoriting actions --- src/Components/Favorite/Favorite.jsx | 5 +++- src/Components/FavoriteMessages/Success.jsx | 13 +++++++++ .../FavoriteMessages/Success.test.jsx | 11 +++++++ src/Constants/SystemMessages.js | 10 +++++++ src/Containers/Toast/Toast.jsx | 14 +++++---- src/actions/toast.js | 6 ++-- src/actions/userProfile.js | 29 ++++++++++++++++--- src/reducers/toast/toast.js | 6 ++-- 8 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 src/Components/FavoriteMessages/Success.jsx create mode 100644 src/Components/FavoriteMessages/Success.test.jsx diff --git a/src/Components/Favorite/Favorite.jsx b/src/Components/Favorite/Favorite.jsx index 04bc6d2491..74b672aa03 100644 --- a/src/Components/Favorite/Favorite.jsx +++ b/src/Components/Favorite/Favorite.jsx @@ -67,7 +67,8 @@ class Favorite extends Component { } isUpdate = (oldState !== newState) || - (this.state.loading !== nextState.isLoading); + (this.state.loading !== nextState.isLoading) || + nextProps.hasErrored; return isUpdate; } @@ -194,6 +195,7 @@ Favorite.propTypes = { hideText: PropTypes.bool, compareArray: FAVORITE_POSITIONS_ARRAY.isRequired, isLoading: PropTypes.bool, + hasErrored: PropTypes.bool, hasBorder: PropTypes.bool, useLongText: PropTypes.bool, useButtonClass: PropTypes.bool, @@ -207,6 +209,7 @@ Favorite.defaultProps = { as: 'div', hideText: false, isLoading: false, + hasErrored: false, compareArray: [], hasBorder: false, useLongText: false, diff --git a/src/Components/FavoriteMessages/Success.jsx b/src/Components/FavoriteMessages/Success.jsx new file mode 100644 index 0000000000..b6d10c1d98 --- /dev/null +++ b/src/Components/FavoriteMessages/Success.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { POSITION_DETAILS } from '../../Constants/PropTypes'; + +const Success = ({ pos }) => ( + {pos.title} ({pos.position_number}) has been successfully added to Favorites. Go to Favorites. +); + +Success.propTypes = { + pos: POSITION_DETAILS.isRequired, +}; + +export default Success; diff --git a/src/Components/FavoriteMessages/Success.test.jsx b/src/Components/FavoriteMessages/Success.test.jsx new file mode 100644 index 0000000000..b1d296e020 --- /dev/null +++ b/src/Components/FavoriteMessages/Success.test.jsx @@ -0,0 +1,11 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import Success from './Success'; +import detailsObject from '../../__mocks__/detailsObject'; + +describe('Success', () => { + it('is defined', () => { + const wrapper = shallow(); + expect(wrapper).toBeDefined(); + }); +}); diff --git a/src/Constants/SystemMessages.js b/src/Constants/SystemMessages.js index 483723e2e5..feffb73604 100644 --- a/src/Constants/SystemMessages.js +++ b/src/Constants/SystemMessages.js @@ -1,3 +1,5 @@ +import FavoriteSuccess from '../Components/FavoriteMessages/Success'; + export const DEFAULT_TEXT = 'None listed'; export const NO_ASSIGNMENT_DATE = DEFAULT_TEXT; @@ -37,6 +39,14 @@ export const DELETE_BID_ITEM_ERROR = 'Error trying to delete this bid.'; export const ADD_BID_ITEM_SUCCESS = 'Bid successfully added.'; export const ADD_BID_ITEM_ERROR = 'Error trying to add this bid.'; +export const ADD_FAVORITE_TITLE = 'Favorite Added'; +export const DELETE_FAVORITE_TITLE = 'Favorite Removed'; +export const ERROR_FAVORITE_TITLE = 'Favorite Error'; +export const DELETE_FAVORITE_SUCCESS = pos => `${pos.title} (${pos.position_number}) has been successfully removed from favorites.`; +export const DELETE_FAVORITE_ERROR = () => "We're experiencing an error attemtping to remove this position to your Favorites. Please try again."; +export const ADD_FAVORITE_SUCCESS = pos => FavoriteSuccess({ pos }); +export const ADD_FAVORITE_ERROR = () => "We're experiencing an error attemtping to add this position to your Favorites. Please try again."; + export const ACCEPT_BID_SUCCESS = 'Bid successfully accepted.'; export const ACCEPT_BID_ERROR = 'Error trying to accept this bid.'; export const DECLINE_BID_SUCCESS = 'Bid successfully declined.'; diff --git a/src/Containers/Toast/Toast.jsx b/src/Containers/Toast/Toast.jsx index 264645cb62..838851cf77 100644 --- a/src/Containers/Toast/Toast.jsx +++ b/src/Containers/Toast/Toast.jsx @@ -17,12 +17,13 @@ export class Toast extends Component { } } - notify({ type = 'success', message = 'Message' }) { // eslint-disable-line - let title; - if (type === 'success') { title = 'Success'; } - if (type === 'error') { title = 'Error'; } + notify({ type = 'success', message = 'Message', title = '' }) { // eslint-disable-line + let title$; + if (type === 'success') { title$ = 'Success'; } + if (type === 'error') { title$ = 'Error'; } + if (title) { title$ = title; } toast[type]( - , + , ); } @@ -36,7 +37,8 @@ export class Toast extends Component { Toast.propTypes = { toastData: PropTypes.shape({ type: PropTypes.string, - message: PropTypes.string, + message: PropTypes.node, + title: PropTypes.string, }), }; diff --git a/src/actions/toast.js b/src/actions/toast.js index 749325200e..8e234b8d54 100644 --- a/src/actions/toast.js +++ b/src/actions/toast.js @@ -1,13 +1,15 @@ -export function toastSuccess(toast) { +export function toastSuccess(toast, title) { return { type: 'TOAST_NOTIFICATION_SUCCESS', toast, + title, }; } -export function toastError(toast) { +export function toastError(toast, title) { return { type: 'TOAST_NOTIFICATION_ERROR', toast, + title, }; } diff --git a/src/actions/userProfile.js b/src/actions/userProfile.js index 28aa5a7bf3..ad4a7ee6cd 100644 --- a/src/actions/userProfile.js +++ b/src/actions/userProfile.js @@ -3,6 +3,8 @@ import { indexOf } from 'lodash'; import api from '../api'; import { favoritePositionsFetchData } from './favoritePositions'; +import { toastSuccess, toastError } from './toast'; +import * as SystemMessages from '../Constants/SystemMessages'; export function userProfileHasErrored(bool) { return { @@ -107,21 +109,40 @@ export function userProfileToggleFavoritePosition(id, remove, refreshFavorites = url: `/position/${idString}/favorite/`, }; + /** + * create functions for creating the action and fetching position data to supply to message + */ + // action + const getAction = () => api(config); + + // position + const getPosition = () => api.get(`/position/${id}/`); + dispatch(userProfileFavoritePositionIsLoading(true)); dispatch(userProfileFavoritePositionHasErrored(false)); - api(config) - .then(() => { + axios.all([getAction(), getPosition()]) + .then(axios.spread((action, position) => { + const pos = position.data; + const message = remove ? + SystemMessages.DELETE_FAVORITE_SUCCESS(pos) : SystemMessages.ADD_FAVORITE_SUCCESS(pos); + const title = remove ? SystemMessages.DELETE_FAVORITE_TITLE + : SystemMessages.ADD_FAVORITE_TITLE; dispatch(userProfileFetchData(true)); dispatch(userProfileFavoritePositionIsLoading(false)); dispatch(userProfileFavoritePositionHasErrored(false)); + dispatch(toastSuccess(message, title)); if (refreshFavorites) { dispatch(favoritePositionsFetchData()); } - }) + })) .catch(() => { - dispatch(userProfileFavoritePositionHasErrored(true)); + const message = remove ? + SystemMessages.DELETE_FAVORITE_ERROR() : SystemMessages.ADD_FAVORITE_ERROR(); + const title = SystemMessages.ERROR_FAVORITE_TITLE; dispatch(userProfileFavoritePositionIsLoading(false)); + dispatch(userProfileFavoritePositionHasErrored(true)); + dispatch(toastError(message, title)); }); }; } diff --git a/src/reducers/toast/toast.js b/src/reducers/toast/toast.js index 6ef75182c5..ae92449461 100644 --- a/src/reducers/toast/toast.js +++ b/src/reducers/toast/toast.js @@ -1,9 +1,9 @@ -export default function toast(state = { type: 'success', message: '' }, action) { +export default function toast(state = { type: 'success', message: '', title: '' }, action) { switch (action.type) { case 'TOAST_NOTIFICATION_SUCCESS': - return { type: 'success', message: action.toast }; + return { type: 'success', message: action.toast, title: action.title }; case 'TOAST_NOTIFICATION_ERROR': - return { type: 'error', message: action.toast }; + return { type: 'error', message: action.toast, title: action.title }; default: return state; }