diff --git a/assets/components/NotificationList.tsx b/assets/components/NotificationList.tsx index 393c79ec1..6549bef51 100644 --- a/assets/components/NotificationList.tsx +++ b/assets/components/NotificationList.tsx @@ -6,14 +6,28 @@ import {Tooltip} from 'bootstrap'; import {formatDate, gettext} from 'utils'; import {isTouchDevice} from '../utils'; import NotificationListItem from './NotificationListItem'; -import {postNotificationSchedule} from 'user-profile/actions'; +import {IUser} from 'interfaces'; interface IState { displayItems: boolean; connected: boolean; } -class NotificationList extends React.Component { +interface IProps { + items: any; + count: number; + fullUser: IUser; + notifications: Array; + loading: boolean; + clearNotification(id: IUser['_id']): void; + clearAll(): void; + loadNotifications(): void; + resumeNotifications(): void; +} + +const NOTIFICATION_ARIA_LABEL = 'Notifications'; + +class NotificationList extends React.Component { static propTypes: any; tooltip: any; elem: any; @@ -75,92 +89,9 @@ class NotificationList extends React.Component { } render() { - const notificationPopUp = () => { - if (this.props.fullUser.notification_schedule?.pauseFrom != '' && this.props.fullUser.notification_schedule?.pauseFrom != '') { - return ( -
-
- {gettext('Notifications')} -
- -
-
- {gettext('All notifications are paused until {{pauseTo}}', {pauseTo: formatDate(this.props.fullUser.notification_schedule?.pauseTo)})} -
- - -
-
- ); - } else { - if (this.props.count === 0) { - return ( -
-
- {gettext('Notifications')} -
- -
- {gettext('No new notifications!')} -
-
- ); - } else { -
-
- {gettext('Notifications')} - - -
- -
-
- {gettext('All notifications are set to be paused from {{pauseFrom}} to {{pauseTo}}', {pauseFrom: formatDate(this.props.fullUser.notification_schedule?.pauseFrom), pauseTo: formatDate(this.props.fullUser.notification_schedule?.pauseTo)})} -
-
- - {this.props.loading ? ( -
- {gettext('Loading...')} -
- ) : ( - this.props.notifications.map((notification: any) => ( - - )) - )} -
; - } - } - }; - return (
-

Notification Bell

+

{gettext('{{label}}', {label: NOTIFICATION_ARIA_LABEL})}

{this.props.count > 0 &&
{this.props.count} @@ -175,24 +106,94 @@ class NotificationList extends React.Component { ref={(elem: any) => this.elem = elem} title={gettext('Notifications')} > -

{gettext('Notification bell')}

+

{gettext('{{label}}', {label: NOTIFICATION_ARIA_LABEL})}

- {this.state.displayItems && notificationPopUp()} + {this.state.displayItems + && (() => { + if (this.props.fullUser.notification_schedule?.pauseFrom != '' && this.props.fullUser.notification_schedule?.pauseTo != '') { + return ( +
+
+ {gettext('Notifications')} +
+ +
+
+ {gettext('All notifications are paused until {{date}}', {date: formatDate(this.props.fullUser.notification_schedule?.pauseTo)})} +
+ + +
+
+ ); + } else { + if (this.props.count === 0) { + return ( +
+
+ {gettext('Notifications')} +
+ +
+ {gettext('No new notifications!')} +
+
+ ); + } else { +
+
+ {gettext('Notifications')} + + +
+ +
+
+ {gettext('All notifications are set to be paused from {{pauseFrom}} to {{pauseTo}}', {pauseFrom: formatDate(this.props.fullUser.notification_schedule.pauseFrom), pauseTo: formatDate(this.props.fullUser.notification_schedule.pauseTo)})} +
+
+ + {this.props.loading ? ( +
+ {gettext('Loading...')} +
+ ) : ( + this.props.notifications.map((notification: any) => ( + + )) + )} +
; + } + }})() + }
); } } -NotificationList.propTypes = { - items: PropTypes.object, - count: PropTypes.number, - notifications: PropTypes.array, - clearNotification: PropTypes.func, - clearAll: PropTypes.func, - loadNotifications: PropTypes.func, - loading: PropTypes.bool, -}; - export default NotificationList; diff --git a/assets/helpers/notification.tsx b/assets/helpers/notification.tsx new file mode 100644 index 000000000..bdda38aab --- /dev/null +++ b/assets/helpers/notification.tsx @@ -0,0 +1,10 @@ +import {IUser} from 'interfaces'; +import server from 'server'; +import {gettext, notify} from 'utils'; + +export function postNotificationSchedule(userId: string, schedule: Omit, message: string):Promise { + return server.post(`/users/${userId}/notification_schedules`, schedule) + .then(() => { + notify.success(gettext(message)); + }); +} diff --git a/assets/notifications/actions.ts b/assets/notifications/actions.ts index 419b6e5f4..8e4eb17c7 100644 --- a/assets/notifications/actions.ts +++ b/assets/notifications/actions.ts @@ -115,19 +115,13 @@ export function pushNotification(push: any): any { }; } -export function updateUserNotificationPause() { +export function updateFullUser() { return (dispatch: any, getState: any) => { const fullUser = getState().fullUser; - return dispatch(fetchUser(fullUser._id)); - }; -} - -export function fetchUser(id: any) { - return function (dispatch: any) { - return server.get(`/users/${id}`) - .then((data: IUser) => { - dispatch(getUser(data)); - }) - .catch((error: any) => errorHandler(error, dispatch)); + return server.get(`/users/${fullUser._id}`) + .then((data: IUser) => { + dispatch(getUser(data)); + }) + .catch((error: any) => errorHandler(error, dispatch)); }; } diff --git a/assets/notifications/components/NotificationsApp.tsx b/assets/notifications/components/NotificationsApp.tsx index 2421d354f..88652ed07 100644 --- a/assets/notifications/components/NotificationsApp.tsx +++ b/assets/notifications/components/NotificationsApp.tsx @@ -5,10 +5,12 @@ import { deleteNotification, deleteAllNotifications, loadNotifications, - updateUserNotificationPause, + updateFullUser, } from '../actions'; import NotificationList from 'components/NotificationList'; +import {postNotificationSchedule} from 'helpers/notification'; +import {gettext} from 'utils'; class NotificationsApp extends React.Component { static propTypes: any; @@ -28,15 +30,17 @@ class NotificationsApp extends React.Component { loadNotifications={this.props.loadNotifications} loading={this.props.loading} fullUser={this.props.fullUser} - user={this.props.user} - updateUserNotificationPause={this.props.updateUserNotificationPause} + resumeNotifications={() => { + postNotificationSchedule(this.props.fullUser._id, {pauseFrom: '', pauseTo: ''}, gettext('Notifications resumed')).then(() => + this.props.resumeNotifications() + ); + }} />, ]; } } NotificationsApp.propTypes = { - user: PropTypes.string, fullUser: PropTypes.object, items: PropTypes.object, notifications: PropTypes.arrayOf(PropTypes.object), @@ -48,7 +52,6 @@ NotificationsApp.propTypes = { }; const mapStateToProps = (state: any) => ({ - user: state.user, fullUser: state.fullUser, items: state.items, notifications: state.notifications, @@ -60,8 +63,8 @@ const mapDispatchToProps = (dispatch: any) => ({ clearNotification: (id: any) => dispatch(deleteNotification(id)), clearAll: () => dispatch(deleteAllNotifications()), loadNotifications: () => dispatch(loadNotifications()), - updateUserNotificationPause: () => ( - dispatch(updateUserNotificationPause()) + resumeNotifications: () => ( + dispatch(updateFullUser()) ), }); diff --git a/assets/user-profile/actions.ts b/assets/user-profile/actions.ts index aa4a12a64..2604e1c94 100644 --- a/assets/user-profile/actions.ts +++ b/assets/user-profile/actions.ts @@ -8,6 +8,7 @@ import {getLocale} from '../utils'; import {reloadMyTopics as reloadMyAgendaTopics} from '../agenda/actions'; import {reloadMyTopics as reloadMyWireTopics} from '../wire/actions'; import {IUserProfileUpdates} from 'interfaces/user'; +import {postNotificationSchedule} from 'helpers/notification'; export const GET_TOPICS = 'GET_TOPICS'; export function getTopics(topics: any) { @@ -350,32 +351,11 @@ export function moveTopic(topicId: any, folder: ITopicFolder | null) { }; } -export function updateUserNotificationSchedules(schedule: Omit) { +export function updateUserNotificationSchedule(schedule: Omit, message: string) { return (dispatch: any, getState: any) => { const user = getState().user; - return server.post(`/users/${user._id}/notification_schedules`, schedule) - .then(() => { - notify.success(gettext('Global schedule updated')); - dispatch(fetchUser(user._id)); - dispatch(closeModal()); - }) - .catch((error) => errorHandler(error, dispatch, setError(error))); - }; -} - -export function postNotificationSchedule(userId: string, schedule: Omit):Promise { - return server.post(`/users/${userId}/notification_schedules`, schedule) - .then(() => { - notify.success(gettext('pause notification updated')); - }); -} - -export function updateUserNotificationPause(schedule: Omit) { - return (dispatch: any, getState: any) => { - const user = getState().user; - - return postNotificationSchedule(user._id, schedule) + return postNotificationSchedule(user._id, schedule, message) .then(() => { dispatch(fetchUser(user._id)); dispatch(closeModal()); diff --git a/assets/user-profile/components/EditNotificationScheduleModal.tsx b/assets/user-profile/components/EditNotificationScheduleModal.tsx index ffd233393..c168486f3 100644 --- a/assets/user-profile/components/EditNotificationScheduleModal.tsx +++ b/assets/user-profile/components/EditNotificationScheduleModal.tsx @@ -5,7 +5,7 @@ import moment from 'moment-timezone'; import {IUser} from 'interfaces'; import {gettext, getScheduledNotificationConfig, TIME_FORMAT} from 'utils'; import {modalFormInvalid, modalFormValid} from 'actions'; -import {updateUserNotificationSchedules} from 'user-profile/actions'; +import {updateUserNotificationSchedule} from 'user-profile/actions'; import Modal from 'components/Modal'; import {TimezoneInput} from 'components/TimezoneInput'; @@ -14,7 +14,7 @@ import {TimePicker} from 'components/cards/TimePicker'; interface IProps { modalFormInvalid(): void; modalFormValid(): void; - updateUserNotificationSchedules(schedule: Omit): void; + updateUserNotificationSchedules(schedule: Omit, message: string): void; data: { user: IUser; }; @@ -101,7 +101,7 @@ class EditNotificationScheduleModalComponent extends React.Component ({ modalFormInvalid: () => dispatch(modalFormInvalid()), modalFormValid: () => dispatch(modalFormValid()), - updateUserNotificationSchedules: (schedule: Omit) => ( - dispatch(updateUserNotificationSchedules(schedule)) + updateUserNotificationSchedules: (schedule: Omit, message: string) => ( + dispatch(updateUserNotificationSchedule(schedule, message)) ), }); diff --git a/assets/user-profile/components/PauseNotificationModal.tsx b/assets/user-profile/components/PauseNotificationModal.tsx index 78652a9b6..d6eaaa767 100644 --- a/assets/user-profile/components/PauseNotificationModal.tsx +++ b/assets/user-profile/components/PauseNotificationModal.tsx @@ -1,29 +1,31 @@ import * as React from 'react'; import {connect} from 'react-redux'; -import {IUser} from 'interfaces'; +import {IEvent, IUser} from 'interfaces'; import {gettext} from 'utils'; import {modalFormInvalid, modalFormValid} from 'actions'; -import {updateUserNotificationPause} from 'user-profile/actions'; +import {updateUserNotificationSchedule} from 'user-profile/actions'; import Modal from 'components/Modal'; interface IProps { modalFormInvalid(): void; modalFormValid(): void; - updateUserNotificationPause(schedule: Omit): void; + updateUserNotificationPause(schedule: Omit, message: string): void; data: { user: IUser; }; } interface IState { - pauseFrom?: string; - pauseTo?: string; + pauseFrom: string; + pauseTo: string; } const disabledOptionDateFrom = new Date().toISOString().split('T')[0]; const ONE_DAY = 1; // representing one day, can be used for adding or subtracting a day depending on the context of the function +const DATE_ID = 'date-from'; + class PauseNotificationModalComponent extends React.Component { formRef: React.RefObject; constructor(props: IProps) { @@ -53,25 +55,22 @@ class PauseNotificationModalComponent extends React.Component { const inputs = this.formRef.current.querySelectorAll('input') as NodeListOf; - const hasErrors = [...inputs].some(input => { + const hasErrors = Array.from(inputs).some(input => { return input.checkValidity() !== true; }); if (hasErrors === true) { this.formRef.current.reportValidity(); } else { - this.props.updateUserNotificationPause(this.state); + this.props.updateUserNotificationPause(this.state, gettext('Notifications paused')); } } - updateDate(event: any, state: 'pauseFrom' | 'pauseTo') { - const stateClone: IState = {}; - - stateClone[state] = event?.target.value; - this.setState(stateClone); + updateDate(event: React.ChangeEvent, pauseType: 'pauseFrom' | 'pauseTo') { + this.setState({...this.state, [pauseType]: event.target.value}); } - disabledMinOption(state: string | undefined) { + disabledPrevOptions(state: string | undefined) { if (state != '' && state != undefined) { const newMaxDate = new Date(state); newMaxDate.setDate(newMaxDate.getDate() + ONE_DAY); @@ -81,7 +80,7 @@ class PauseNotificationModalComponent extends React.Component { } } - disabledMaxOption(state: string | undefined) { + disabledNextOptions(state: string | undefined) { if (state != '' && state != undefined) { const newMaxDate = new Date(state); newMaxDate.setDate(newMaxDate.getDate() - ONE_DAY); @@ -107,18 +106,18 @@ class PauseNotificationModalComponent extends React.Component { >
- + { this.updateDate(event, 'pauseFrom'); }} value={this.state.pauseFrom} min={disabledOptionDateFrom} - max={this.disabledMaxOption(this.state.pauseTo)} + max={this.disabledNextOptions(this.state.pauseTo)} />
@@ -133,7 +132,7 @@ class PauseNotificationModalComponent extends React.Component { this.updateDate(event, 'pauseTo'); }} value={this.state.pauseTo} - min={this.disabledMinOption(this.state.pauseFrom)} + min={this.disabledPrevOptions(this.state.pauseFrom)} />
@@ -147,8 +146,8 @@ class PauseNotificationModalComponent extends React.Component { const mapDispatchToProps = (dispatch: any) => ({ modalFormInvalid: () => dispatch(modalFormInvalid()), modalFormValid: () => dispatch(modalFormValid()), - updateUserNotificationPause: (schedule: Omit) => ( - dispatch(updateUserNotificationPause(schedule)) + updateUserNotificationPause: (schedule: Omit, message: string) => ( + dispatch(updateUserNotificationSchedule(schedule, message)) ), }); diff --git a/assets/user-profile/components/profile/UserProfile.tsx b/assets/user-profile/components/profile/UserProfile.tsx index 6e36f1049..fc1ab88b5 100644 --- a/assets/user-profile/components/profile/UserProfile.tsx +++ b/assets/user-profile/components/profile/UserProfile.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {connect} from 'react-redux'; import {IUser} from 'interfaces'; -import {gettext, getSubscriptionTimesString} from 'utils'; +import {gettext, getSubscriptionTimesString, formatDate} from 'utils'; import TextInput from 'components/TextInput'; import SelectInput from 'components/SelectInput'; @@ -16,6 +16,7 @@ import { setError, openEditTopicNotificationsModal, openPauseNotificationModal, + updateUserNotificationSchedule, } from '../../actions'; import {IUserProfileState} from 'user-profile/reducers'; import {IUserProfileUpdates} from 'interfaces/user'; @@ -30,6 +31,7 @@ interface IProps { openEditTopicNotificationsModal(): void; openPauseNotificationModal(): void; authProviderFeatures: IUserProfileState['authProviderFeatures']; + updateUserNotificationPause(schedule: Omit, message: string): void; } class UserProfile extends React.PureComponent { @@ -185,15 +187,39 @@ class UserProfile extends React.PureComponent {
-
- -
+ {this.props.user.notification_schedule?.pauseFrom != '' && this.props.user.notification_schedule?.pauseTo != '' + ? ( +
+
+
+ {gettext('All notifications will be paused from {{dateFrom}} to {{dateTo}}', {dateFrom: formatDate(this.props.user.notification_schedule?.pauseFrom), dateTo: formatDate(this.props.user.notification_schedule?.pauseTo)})} +
+
+ +
+
+
+ ) + : ( +
+ +
+ ) + }
@@ -247,6 +273,9 @@ const mapDispatchToProps = (dispatch: any) => ({ setError: (errors: {[key: string]: string}) => dispatch(setError(errors)), openEditTopicNotificationsModal: () => dispatch(openEditTopicNotificationsModal()), openPauseNotificationModal: () => dispatch(openPauseNotificationModal()), + updateUserNotificationPause: (schedule: Omit, message: string) => ( + dispatch(updateUserNotificationSchedule(schedule, message)) + ), }); export default connect(mapStateToProps, mapDispatchToProps)(UserProfile); diff --git a/assets/utils.tsx b/assets/utils.tsx index c7a81a2ae..84fab4af7 100644 --- a/assets/utils.tsx +++ b/assets/utils.tsx @@ -737,7 +737,7 @@ export function getSubscriptionTimesString(user: IUser): string { const timezoneAbbreviation = now.zoneAbbr(); const timeStrings = []; - for (const time of schedule.times) { + for (const time of (schedule.times ?? [])) { const time_parts = time.split(':'); now.hours(parseInt(time_parts[0], 10))