Skip to content

Commit

Permalink
feat: added notification preference ui
Browse files Browse the repository at this point in the history
  • Loading branch information
adeel.tajamul committed May 9, 2023
1 parent 7cf9294 commit 6eb8974
Show file tree
Hide file tree
Showing 16 changed files with 667 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/account-settings/AccountSettingsPage.messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,11 @@ const messages = defineMessages({
defaultMessage: 'No value set.',
description: 'The placeholder for an empty but uneditable field when there is no administrator',
},
'notification.preferences.notifications.text': {
id: 'notification.preferences.notifications.text',
defaultMessage: 'Notifications',
description: 'Notifications Text',
},
});

export default messages;
21 changes: 20 additions & 1 deletion src/account-settings/JumpNav.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { breakpoints, useWindowSize } from '@edx/paragon';
import { breakpoints, useWindowSize, Icon } from '@edx/paragon';
import { OpenInNew } from '@edx/paragon/icons';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { NavHashLink } from 'react-router-hash-link';
import Scrollspy from 'react-scrollspy';
import { Link } from 'react-router-dom';
import messages from './AccountSettingsPage.messages';

const JumpNav = ({
intl,
displayDemographicsLink,
}) => {
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
const showNotificationMenu = false;
return (
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<Scrollspy
Expand Down Expand Up @@ -67,6 +70,22 @@ const JumpNav = ({
</NavHashLink>
</li>
</Scrollspy>
{showNotificationMenu
&& (
<>
<hr className="ml-0" style={{ width: '180px' }} />
<Scrollspy
className="list-unstyled"
>
<li>
<Link to="/notifications" target="_blank" rel="noopener noreferrer">
<span>{intl.formatMessage(messages['notification.preferences.notifications.text'])}</span>
<Icon className="d-inline-block align-bottom ml-1" src={OpenInNew} />
</Link>
</li>
</Scrollspy>
</>
)}
</div>
);
};
Expand Down
5 changes: 5 additions & 0 deletions src/data/reducers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
/* eslint-disable import/no-named-default */
import { combineReducers } from 'redux';

import {
reducer as accountSettingsReducer,
storeName as accountSettingsStoreName,
} from '../account-settings';
import {
default as notificationPreferencesReducer,
} from '../notification-preferences/data/reducers';

const createRootReducer = () => combineReducers({
[accountSettingsStoreName]: accountSettingsReducer,
notificationPreferences: notificationPreferencesReducer,
});
export default createRootReducer;
10 changes: 10 additions & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import messages from './i18n';

import './index.scss';
import Head from './head/Head';
import NotificationCourses from './notification-preferences/NotificationCourses';
import NotificationPreferences from './notification-preferences/NotificationPreferences';

subscribe(APP_READY, () => {
const allowNotificationRoutes = false;
ReactDOM.render(
<AppProvider store={configureStore()}>
<Head />
Expand All @@ -32,6 +35,13 @@ subscribe(APP_READY, () => {
<Header />
<main className="flex-grow-1">
<Switch>
{allowNotificationRoutes
&& (
<>
<Route path="/notifications/:courseId" component={NotificationPreferences} />
<Route path="/notifications" component={NotificationCourses} />
</>
)}
<Route path="/id-verification" component={IdVerificationPage} />
<Route exact path="/" component={AccountSettingsPage} />
<Route path="/notfound" component={NotFoundPage} />
Expand Down
32 changes: 32 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,35 @@ $fa-font-path: "~font-awesome/fonts";
font-weight: normal;
}
}

.notification-heading {
line-height: 36px;
font-weight: 700;
font-size: 32px;
}

.notification-course-title {
line-height: 28px;
font-weight: 700;
font-size: 18px;
}

.w-66 {
width: 66%;
}

.w-34 {
width: 34%;
}

.w-828px {
width: 828px;
}

.notification-help-text {
font-size: 14px;
font-weight: 400;
line-height: 28px;
height: 28px;
color: #707070;
}
52 changes: 52 additions & 0 deletions src/notification-preferences/NotificationCourses.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { ArrowForwardIos } from '@edx/paragon/icons';
import { fetchCourseList } from './data/thunks';
import { courseListStatus, getCourseList } from './data/selectors';
import { IDLE_STATUS } from '../constants';
import { messages } from './messages';

const NotificationCourses = ({ intl }) => {
const dispatch = useDispatch();
const courseStatus = useSelector(courseListStatus());
const coursesList = useSelector(getCourseList());
useEffect(() => {
if (courseStatus === IDLE_STATUS) {
dispatch(fetchCourseList());
}
}, [courseStatus, dispatch]);
return (
<div className="ml-auto mr-auto w-828px">
<h2 className="notification-heading mt-6 mb-5.5">
{intl.formatMessage(messages.notificationHeading)}
</h2>
<div>
{
coursesList.map(course => (
<Link
to={`/notifications/${course.id}`}
>
<div className="mb-4 d-flex text-gray-700">
<span className="ml-0 mr-auto">
{course.name}
</span>
<span className="ml-auto mr-0">
<Icon src={ArrowForwardIos} />
</span>
</div>
</Link>
))
}
</div>
</div>
);
};

NotificationCourses.propTypes = {
intl: intlShape.isRequired,
};

export default injectIntl(NotificationCourses);
70 changes: 70 additions & 0 deletions src/notification-preferences/NotificationPreferenceGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Collapsible } from '@edx/paragon';
import { messages } from './messages';
import ToggleSwitch from './ToggleSwitch';
import {
getCoursePreferenceGroup,
getSelectedCourse,
} from './data/selectors';
import NotificationPreferenceRow from './NotificationPreferenceRow';
import { updateGroupValue } from './data/actions';

const NotificationPreferenceGroup = ({ intl, groupKey }) => {
const dispatch = useDispatch();
const courseId = useSelector(getSelectedCourse());
const preferenceGroup = useSelector(getCoursePreferenceGroup(groupKey));
const [groupToggle, setGroupToggle] = useState(true);
useEffect(() => {}, [courseId]);
const onChangeGroupSettings = (checked) => {
setGroupToggle(checked);
dispatch(updateGroupValue(courseId, groupKey, checked));
};
if (!courseId) {
return null;
}
return (
<Collapsible.Advanced open={groupToggle}>
<Collapsible.Trigger>
<div className="d-flex">
<span className="ml-0 mr-auto">
{intl.formatMessage(messages.notificationGroupTitle, { key: groupKey })}
</span>
<span className="ml-auto mr-0">
<ToggleSwitch value={groupToggle} onChange={onChangeGroupSettings} />
</span>
</div>
<hr />
</Collapsible.Trigger>
<Collapsible.Body>
<div className="d-flex flex-row notification-help-text">
<span className="w-66 ">{intl.formatMessage(messages.notificationHelpType)}</span>
<span className="d-flex w-34">
<span className="ml-0 mr-auto">{intl.formatMessage(messages.notificationHelpWeb)}</span>
<span className="mx-auto">{intl.formatMessage(messages.notificationHelpEmail)}</span>
<span className="ml-auto mr-0 pr-2.5">{intl.formatMessage(messages.notificationHelpPush)}</span>
</span>
</div>
<div className="mt-3 pb-5">
{
Object.keys(preferenceGroup).map(preferenceName => (
<NotificationPreferenceRow
groupKey={groupKey}
preferenceName={preferenceName}
/>
))
}
</div>
</Collapsible.Body>
</Collapsible.Advanced>
);
};

NotificationPreferenceGroup.propTypes = {
intl: intlShape.isRequired,
groupKey: PropTypes.string.isRequired,
};

export default injectIntl(NotificationPreferenceGroup);
51 changes: 51 additions & 0 deletions src/notification-preferences/NotificationPreferenceRow.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { messages } from './messages';
import ToggleSwitch from './ToggleSwitch';
import { getPreferenceAttribute } from './data/selectors';
import { updatePreferenceValue } from './data/actions';

const NotificationPreferenceRow = ({ intl, groupKey, preferenceName }) => {
const dispatch = useDispatch();
const preference = useSelector(getPreferenceAttribute(groupKey, preferenceName));
const onToggle = (checked, notificationFor) => {
dispatch(updatePreferenceValue(groupKey, preferenceName, notificationFor, checked));
};
return (
<div className="d-flex flex-row mb-3">
<span className="w-66">
{intl.formatMessage(messages.notificationTitle, { text: preferenceName })}
</span>
<span className="d-flex w-34">
<span className="ml-0 mr-auto">
<ToggleSwitch
value={preference.web}
onChange={(checked) => onToggle(checked, 'web')}
/>
</span>
<span className="mx-auto">
<ToggleSwitch
value={preference.email}
onChange={(checked) => onToggle(checked, 'email')}
/>
</span>
<span className="ml-auto mr-0">
<ToggleSwitch
value={preference.push}
onChange={(checked) => onToggle(checked, 'push')}
/>
</span>
</span>
</div>
);
};

NotificationPreferenceRow.propTypes = {
intl: intlShape.isRequired,
groupKey: PropTypes.string.isRequired,
preferenceName: PropTypes.string.isRequired,
};

export default injectIntl(NotificationPreferenceRow);
68 changes: 68 additions & 0 deletions src/notification-preferences/NotificationPreferences.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { ArrowBack } from '@edx/paragon/icons';
import {
courseListStatus,
getCourse,
getCoursePreferences,
notificationPreferencesStatus,
} from './data/selectors';
import { fetchCourseList, fetchCourseNotificationPreferences } from './data/thunks';
import { messages } from './messages';
import NotificationPreferenceGroup from './NotificationPreferenceGroup';
import { updateSelectedCourse } from './data/actions';
import { IDLE_STATUS, SUCCESS_STATUS } from '../constants';

const NotificationPreferences = ({ intl }) => {
const { courseId } = useParams();
const dispatch = useDispatch();
const courseStatus = useSelector(courseListStatus());
const notificationStatus = useSelector(notificationPreferencesStatus());
const course = useSelector(getCourse(courseId));
const preferences = useSelector(getCoursePreferences());
useEffect(() => {
dispatch(updateSelectedCourse(courseId));
if (courseStatus !== SUCCESS_STATUS) {
dispatch(fetchCourseList());
}
if (notificationStatus === IDLE_STATUS) {
dispatch(fetchCourseNotificationPreferences(courseId));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [courseId, course, preferences]);
if (notificationStatus === IDLE_STATUS) {
return null;
}
return (
<div className="ml-auto mr-auto w-828px">
<h2 className="notification-heading mt-6 mb-5.5">
{intl.formatMessage(messages.notificationHeading)}
</h2>
<div>
<div className="d-flex mb-4">
<Link to="/notifications">
<Icon className="d-inline-block align-bottom ml-1" src={ArrowBack} />
</Link>
<span className="notification-course-title ml-auto mr-auto">
{course?.name}
</span>
</div>
{
Object.keys(preferences).map(key => (
<NotificationPreferenceGroup groupKey={key} />
))
}
</div>
</div>
);
};

NotificationPreferences.propTypes = {
intl: intlShape.isRequired,
};

export default injectIntl(NotificationPreferences);
Loading

0 comments on commit 6eb8974

Please sign in to comment.