Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pause ALL my notifications #910

Merged
merged 13 commits into from
Jun 17, 2024
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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's quick, can you type the props as well?

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 = {
tomaskikutis marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -44,8 +44,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you type props here?

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
Loading