Skip to content

Commit

Permalink
Pause ALL my notifications (#910)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzonidoo authored Jun 17, 2024
1 parent 84db92c commit 6072e7a
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 88 deletions.
173 changes: 116 additions & 57 deletions assets/components/NotificationList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {get} from 'lodash';
import {Tooltip} from 'bootstrap';

import {gettext} from 'utils';
import {formatDate, gettext} from 'utils';
import {isTouchDevice} from '../utils';
import NotificationListItem from './NotificationListItem';
import {IUser} from 'interfaces';

interface IState {
displayItems: boolean;
connected: boolean;
}

class NotificationList extends React.Component<any, any> {
static propTypes: any;
interface IProps {
items: any;
count: number;
fullUser: IUser;
notifications: Array<any>;
loading: boolean;
clearNotification(id: IUser['_id']): void;
clearAll(): void;
loadNotifications(): void;
resumeNotifications(): void;
}

class NotificationList extends React.Component<IProps, IState> {
tooltip: any;
elem: any;

Expand Down Expand Up @@ -60,7 +75,7 @@ class NotificationList extends React.Component<any, any> {
}

toggleDisplay() {
this.setState({displayItems:!this.state.displayItems});
this.setState({displayItems: !this.state.displayItems});
if (!this.state.displayItems) {
this.props.loadNotifications();
(document.getElementById('header-notification') as HTMLElement).classList.add('navbar-notifications--open');
Expand All @@ -70,9 +85,15 @@ class NotificationList extends React.Component<any, any> {
}

render() {
let notificationArePaused = false;

if (this.props.fullUser.notification_schedule?.pauseFrom) {
notificationArePaused = new Date(this.props.fullUser.notification_schedule.pauseFrom) < new Date();
}

return (
<div className="navbar-notifications__inner">
<h3 className="a11y-only">Notification Bell</h3>
<h3 className="a11y-only">{gettext('Notifications')}</h3>
{this.props.count > 0 &&
<div className="navbar-notifications__badge">
{this.props.count}
Expand All @@ -85,63 +106,101 @@ class NotificationList extends React.Component<any, any> {
{'navbar-notifications__inner-circle--disconnected': !this.state.connected}
)}
ref={(elem: any) => this.elem = elem}
title={gettext('Notifications')}>
<h3 className="a11y-only">Notification bell</h3>
title={gettext('Notifications')}
>
<h3 className="a11y-only">{gettext('Notifications')}</h3>
<i className='icon--alert' onClick={this.toggleDisplay} />
</span>

{!this.state.displayItems ? null : this.props.count === 0 ? (
<div className="notif__list dropdown-menu dropdown-menu-right show">
<div className='notif__list__header d-flex'>
<span className='notif__list__header-headline ms-3'>{gettext('Notifications')}</span>
</div>
<div className='notif__list__message'>
{gettext('No new notifications!')}
</div>
</div>
) : (
<div className="notif__list dropdown-menu dropdown-menu-right show">
<div className='notif__list__header d-flex'>
<span className='notif__list__header-headline ms-3'>{gettext('Notifications')}</span>
<button
type="button"
className="button-pill ms-auto me-3"
onClick={this.props.clearAll}>{gettext('Clear All')}
</button>
</div>
{this.props.loading ? (
<div className='notif__list__message'>
{gettext('Loading...')}
{(() => {
if (this.state.displayItems !== true) {
return null;
}

if (this.props.fullUser.notification_schedule != null && this.props.fullUser.notification_schedule.pauseFrom != '' && this.props.fullUser.notification_schedule.pauseTo != '' && notificationArePaused) {
return (
<div className="notif__list dropdown-menu dropdown-menu-right show">
<div className='notif__list__header d-flex'>
<span className='notif__list__header-headline ms-3'>{gettext('Notifications')}</span>
</div>

<div className='d-flex flex-column gap-2 p-3'>
<div className='nh-container nh-container__text--alert p-2'>
{gettext('All notifications are paused until {{date}}', {date: formatDate(this.props.fullUser.notification_schedule.pauseTo)})}
</div>

<button
type="button"
className="nh-button nh-button--small nh-button--tertiary"
onClick={() => {
this.props.resumeNotifications();
}}
>
{gettext('Resume all notifications')}
</button>
</div>
</div>
) : (
this.props.notifications.map((notification: any) => (
<NotificationListItem
key={get(this.props.items, `${notification.item}._id`, 'test')}
item={get(
this.props.items,
`${notification.item}`,
get(notification, 'data.item', {})
)}
notification={notification}
clearNotification={this.props.clearNotification}
/>
))
)}
</div>
)}
);
} else {
if (this.props.count === 0) {
return (
<div className="notif__list dropdown-menu dropdown-menu-right show">
<div className='notif__list__header d-flex'>
<span className='notif__list__header-headline ms-3'>{gettext('Notifications')}</span>
</div>

<div className='notif__list__message'>
{gettext('No new notifications!')}
</div>
</div>
);
} else {
<div className="notif__list dropdown-menu dropdown-menu-right show">
<div className='notif__list__header d-flex'>
<span className='notif__list__header-headline ms-3'>{gettext('Notifications')}</span>

<button
type="button"
className="button-pill ms-auto me-3"
onClick={this.props.clearAll}
>
{gettext('Clear All')}
</button>
</div>

{(this.props.fullUser.notification_schedule != null && this.props.fullUser.notification_schedule.pauseFrom != '' && this.props.fullUser.notification_schedule.pauseTo != '' && !notificationArePaused) && (
<div className='p-3'>
<div className='nh-container nh-container__text--info p-2'>
{gettext('All notifications are set to be paused from {{dateFrom}} to {{dateTo}}', {dateFrom: formatDate(this.props.fullUser.notification_schedule.pauseFrom), dateTo: formatDate(this.props.fullUser.notification_schedule.pauseTo)})}
</div>
</div>
)}

{this.props.loading ? (
<div className='notif__list__message'>
{gettext('Loading...')}
</div>
) : (
this.props.notifications.map((notification: any) => (
<NotificationListItem
key={get(this.props.items, `${notification.item}._id`, 'test')}
item={get(
this.props.items,
`${notification.item}`,
get(notification, 'data.item', {})
)}
notification={notification}
clearNotification={this.props.clearNotification}
/>
))
)}
</div>;
}
}})()
}
</div>
);
}
}

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;
10 changes: 10 additions & 0 deletions assets/helpers/notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {IUser} from 'interfaces';
import server from 'server';
import {notify} from 'utils';

export function postNotificationSchedule(userId: string, schedule: Omit<IUser['notification_schedule'], 'last_run_time'>, message: string):Promise<void> {
return server.post(`/users/${userId}/notification_schedules`, schedule)
.then(() => {
notify.success(message);
});
}
4 changes: 3 additions & 1 deletion assets/interfaces/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ export interface IUser {
timezone: string;
times: Array<string>;
last_run_time?: TDatetime;
pauseFrom?: string;
pauseTo?: string;
};
}

type IUserProfileEditable = Pick<IUser, 'first_name' | 'last_name' | 'phone' | 'mobile' | 'role' | 'locale' | 'receive_email' | 'receive_app_notifications'>;
export type IUserProfileUpdates = Partial<IUserProfileEditable>;
export type IUserProfileUpdates = Partial<IUserProfileEditable>;
21 changes: 19 additions & 2 deletions assets/notifications/actions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import {get} from 'lodash';
import {gettext, notify, errorHandler} from 'utils';
import server from 'server';
import {IUser} from 'interfaces';

export const UPDATE_NOTIFICATION_COUNT = 'UPDATE_NOTIFICATION_COUNT';
export function updateNotificationCount(count: any) {
return {type: UPDATE_NOTIFICATION_COUNT, count};
}

export const INIT_DATA = 'INIT_DATA';
export function initData(data: any) {
return {type: INIT_DATA, data};
export function initData(notificationData: any, profileData: any) {
return {type: INIT_DATA, data: {...notificationData, fullUser: profileData.user}};
}

export const GET_USER = 'GET_USER';
export function getUser(user: IUser): {type: string, user: IUser} {
return {type: GET_USER, user};
}


Expand Down Expand Up @@ -108,3 +114,14 @@ export function pushNotification(push: any): any {
}
};
}

export function updateFullUser(userId: string) {
return (dispatch: any, getState: any) => {
return server.get(`/users/${userId}`)
.then((data: IUser) => {
dispatch(getUser(data));
return data;
})
.catch((error: any) => errorHandler(error, dispatch));
};
}
47 changes: 31 additions & 16 deletions assets/notifications/components/NotificationsApp.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {
deleteNotification,
deleteAllNotifications,
loadNotifications,
updateFullUser,
} from '../actions';

import {store as userProfileStore} from '../../user-profile/store';
import {getUser as getUserProfileUser} from 'user-profile/actions';
import NotificationList from 'components/NotificationList';
import {postNotificationSchedule} from 'helpers/notification';
import {gettext} from 'utils';
import {IUser} from 'interfaces/user';

interface IProps {
fullUser: IUser;
items: any;
notifications: Array<any>;
count: number;
loading: boolean;
clearNotification(id: IUser['_id']): void;
clearAll(): void;
loadNotifications(): void;
updateUser(id: IUser['_id']): void;
}

class NotificationsApp extends React.Component<any, any> {
static propTypes: any;
class NotificationsApp extends React.Component<IProps, any> {
constructor(props: any, context: any) {
super(props, context);
}
Expand All @@ -26,24 +41,19 @@ class NotificationsApp extends React.Component<any, any> {
clearAll={this.props.clearAll}
loadNotifications={this.props.loadNotifications}
loading={this.props.loading}
fullUser={this.props.fullUser}
resumeNotifications={() => {
postNotificationSchedule(this.props.fullUser._id, {pauseFrom: '', pauseTo: ''}, gettext('Notifications resumed')).then(() =>
this.props.updateUser(this.props.fullUser._id)
);
}}
/>,
];
}
}

NotificationsApp.propTypes = {
user: PropTypes.string,
items: PropTypes.object,
notifications: PropTypes.arrayOf(PropTypes.object),
count: PropTypes.number,
clearNotification: PropTypes.func,
clearAll: PropTypes.func,
loadNotifications: PropTypes.func,
loading: PropTypes.bool,
};

const mapStateToProps = (state: any) => ({
user: state.user,
fullUser: state.fullUser,
items: state.items,
notifications: state.notifications,
count: state.notificationCount,
Expand All @@ -54,6 +64,11 @@ const mapDispatchToProps = (dispatch: any) => ({
clearNotification: (id: any) => dispatch(deleteNotification(id)),
clearAll: () => dispatch(deleteAllNotifications()),
loadNotifications: () => dispatch(loadNotifications()),
updateUser: (userId: string) => {
dispatch(updateFullUser(userId)).then((fullUser: IUser) => {
userProfileStore.dispatch(getUserProfileUser(fullUser));
});
}
});

export default connect(mapStateToProps, mapDispatchToProps)(NotificationsApp);
2 changes: 1 addition & 1 deletion assets/notifications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const store = createStore(notificationReducer, 'Notifications');


if (window.notificationData) {
store.dispatch(initData(window.notificationData));
store.dispatch(initData(window.notificationData, window.profileData));
}


Expand Down
Loading

0 comments on commit 6072e7a

Please sign in to comment.