Skip to content

Commit

Permalink
pasue notification
Browse files Browse the repository at this point in the history
  • Loading branch information
dzonidoo committed May 22, 2024
1 parent 33fb7dc commit d9640be
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 35 deletions.
110 changes: 80 additions & 30 deletions assets/components/NotificationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ 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 {postNotificationSchedule} from 'user-profile/actions';

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

class NotificationList extends React.Component<any, any> {
class NotificationList extends React.Component<any, IState> {
static propTypes: any;
tooltip: any;
elem: any;
Expand Down Expand Up @@ -60,7 +65,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,45 +75,66 @@ class NotificationList extends React.Component<any, any> {
}

render() {
return (
<div className="navbar-notifications__inner">
<h3 className="a11y-only">Notification Bell</h3>
{this.props.count > 0 &&
<div className="navbar-notifications__badge">
{this.props.count}
</div>
}

<span
className={classNames(
'navbar-notifications__inner-circle',
{'navbar-notifications__inner-circle--disconnected': !this.state.connected}
)}
ref={(elem: any) => this.elem = elem}
title={gettext('Notifications')}>
<h3 className="a11y-only">Notification bell</h3>
<i className='icon--alert' onClick={this.toggleDisplay} />
</span>

{!this.state.displayItems ? null : this.props.count === 0 ? (
const notificationPopUp = () => {
if (this.props.fullUser.notification_schedule?.pauseFrom != '' && this.props.fullUser.notification_schedule?.pauseFrom != '') {
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 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 {{pauseTo}}', {pauseTo: formatDate(this.props.fullUser.notification_schedule?.pauseTo)})}
</div>

<button
type="button"
className="nh-button nh-button--small nh-button--tertiary"
onClick={() => {
postNotificationSchedule(this.props.user, {pauseFrom: '', pauseTo: ''}).then(() =>
this.props.updateUserNotificationPause()
);
}}
>
{gettext('Resume all notifications')}
</button>
</div>
</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')}
onClick={this.props.clearAll}
>
{gettext('Clear All')}
</button>
</div>

<div className='p-3'>
<div className='nh-container nh-container__text--info p-2'>
{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)})}
</div>
</div>

{this.props.loading ? (
<div className='notif__list__message'>
{gettext('Loading...')}
Expand All @@ -127,8 +153,32 @@ class NotificationList extends React.Component<any, any> {
/>
))
)}
</div>;
}
}
};

return (
<div className="navbar-notifications__inner">
<h3 className="a11y-only">Notification Bell</h3>
{this.props.count > 0 &&
<div className="navbar-notifications__badge">
{this.props.count}
</div>
)}
}

<span
className={classNames(
'navbar-notifications__inner-circle',
{'navbar-notifications__inner-circle--disconnected': !this.state.connected}
)}
ref={(elem: any) => this.elem = elem}
title={gettext('Notifications')}>
<h3 className="a11y-only">Notification bell</h3>
<i className='icon--alert' onClick={this.toggleDisplay} />
</span>

{this.state.displayItems && notificationPopUp()}
</div>
);
}
Expand Down
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>;
27 changes: 25 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,20 @@ export function pushNotification(push: any): any {
}
};
}

export function updateUserNotificationPause() {
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));
};
}
9 changes: 9 additions & 0 deletions assets/notifications/components/NotificationsApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
deleteNotification,
deleteAllNotifications,
loadNotifications,
updateUserNotificationPause,
} from '../actions';

import NotificationList from 'components/NotificationList';
Expand All @@ -26,13 +27,17 @@ class NotificationsApp extends React.Component<any, any> {
clearAll={this.props.clearAll}
loadNotifications={this.props.loadNotifications}
loading={this.props.loading}
fullUser={this.props.fullUser}
user={this.props.user}
updateUserNotificationPause={this.props.updateUserNotificationPause}
/>,
];
}
}

NotificationsApp.propTypes = {
user: PropTypes.string,
fullUser: PropTypes.object,
items: PropTypes.object,
notifications: PropTypes.arrayOf(PropTypes.object),
count: PropTypes.number,
Expand All @@ -44,6 +49,7 @@ NotificationsApp.propTypes = {

const mapStateToProps = (state: any) => ({
user: state.user,
fullUser: state.fullUser,
items: state.items,
notifications: state.notifications,
count: state.notificationCount,
Expand All @@ -54,6 +60,9 @@ const mapDispatchToProps = (dispatch: any) => ({
clearNotification: (id: any) => dispatch(deleteNotification(id)),
clearAll: () => dispatch(deleteAllNotifications()),
loadNotifications: () => dispatch(loadNotifications()),
updateUserNotificationPause: () => (
dispatch(updateUserNotificationPause())
),
});

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
10 changes: 10 additions & 0 deletions assets/notifications/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
INIT_DATA,
CLEAR_NOTIFICATION,
CLEAR_ALL_NOTIFICATIONS,
GET_USER,
} from './actions';

const initialState: any = {
user: null,
fullUser: null,
items: {},
notifications: [],
notificationCount: 0,
Expand Down Expand Up @@ -63,13 +65,21 @@ export default function notificationReducer(state: any = initialState, action: a
return {
...state,
user: action.data.user || null,
fullUser: action.data.fullUser || null,
items: {},
notifications: [],
notificationCount: action.data.notificationCount,
loading: false,
};
}

case GET_USER: {
return {
...state,
fullUser: action.user,
};
}

default:
return state;
}
Expand Down
2 changes: 2 additions & 0 deletions assets/search/components/TopicEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
hideModal,
setTopicEditorFullscreen,
openEditTopicNotificationsModal,
openPauseNotificationModal,
setTopicSubscribers,
saveFolder,
} from 'user-profile/actions';
Expand Down Expand Up @@ -596,6 +597,7 @@ const mapDispatchToProps = (dispatch: any) => ({
dispatch(loadMyWireTopic(topic._id)),
setTopicEditorFullscreen: (fullscreen: boolean) => dispatch(setTopicEditorFullscreen(fullscreen)),
openEditTopicNotificationsModal: () => dispatch(openEditTopicNotificationsModal()),
openPauseNotificationModal: () => dispatch(openPauseNotificationModal()),
setTopicSubscribers: (topic: ITopic, subscribers: ITopic['subscribers']) =>
dispatch(setTopicSubscribers(topic, subscribers)),
});
Expand Down
8 changes: 8 additions & 0 deletions assets/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4148,6 +4148,14 @@ article.list {
border-radius: var(--border-radius--m);
padding: var(--space--2);
}
&.nh-container__text--alert {
color: var(--color-alert);
background-color: alpha(var(--color-alert), 0.25);
}
&.nh-container__text--info {
color: var(--color-info);
background-color: alpha(var(--color-info), 0.25);
}
}

.a11y-only {
Expand Down
28 changes: 28 additions & 0 deletions assets/user-profile/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ export function openEditTopicNotificationsModal() {
};
}

export function openPauseNotificationModal() {
return (dispatch: any, getState: any) => {
const user = getState().user;

dispatch(renderModal('pauseNotificationModal', {user}));
};
}

/**
* Submit share followed topic form and close modal if that works
*
Expand Down Expand Up @@ -356,6 +364,26 @@ export function updateUserNotificationSchedules(schedule: Omit<IUser['notificati
};
}

export function postNotificationSchedule(userId: string, schedule: Omit<IUser['notification_schedule'], 'last_run_time'>):Promise<void> {
return server.post(`/users/${userId}/notification_schedules`, schedule)
.then(() => {
notify.success(gettext('pause notification updated'));
});
}

export function updateUserNotificationPause(schedule: Omit<IUser['notification_schedule'], 'last_run_time'>) {
return (dispatch: any, getState: any) => {
const user = getState().user;

return postNotificationSchedule(user._id, schedule)
.then(() => {
dispatch(fetchUser(user._id));
dispatch(closeModal());
})
.catch((error) => errorHandler(error, dispatch, setError(error)));
};
}

export function setTopicSubscribers(topic: ITopic, subscribers: ITopic['subscribers']) {
return (dispatch: any, getState: any) => {
const user = getState().user;
Expand Down
Loading

0 comments on commit d9640be

Please sign in to comment.