feat: integrate notification preferences api (#794)
This commit is contained in:
committed by
GitHub
parent
09cce6802d
commit
be8570edac
1
package-lock.json
generated
1
package-lock.json
generated
@@ -39,6 +39,7 @@
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.pickby": "4.6.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"long": "5.2.3",
|
||||
"memoize-one": "5.2.1",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.pickby": "4.6.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"long": "5.2.3",
|
||||
"memoize-one": "5.2.1",
|
||||
"prop-types": "15.8.1",
|
||||
|
||||
@@ -5,21 +5,32 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Container, Icon, Spinner } from '@edx/paragon';
|
||||
import { ArrowForwardIos } from '@edx/paragon/icons';
|
||||
import { fetchCourseList } from './data/thunks';
|
||||
import { courseListStatus, getCourseList } from './data/selectors';
|
||||
import { IDLE_STATUS, LOADING_STATUS } from '../constants';
|
||||
import { selectCourseListStatus, selectCourseList } from './data/selectors';
|
||||
import {
|
||||
IDLE_STATUS,
|
||||
LOADING_STATUS,
|
||||
SUCCESS_STATUS,
|
||||
} from '../constants';
|
||||
import { messages } from './messages';
|
||||
import { NotFoundPage } from '../account-settings';
|
||||
|
||||
const NotificationCourses = ({ intl }) => {
|
||||
const dispatch = useDispatch();
|
||||
const courseStatus = useSelector(courseListStatus());
|
||||
const coursesList = useSelector(getCourseList());
|
||||
const coursesList = useSelector(selectCourseList());
|
||||
const courseListStatus = useSelector(selectCourseListStatus());
|
||||
|
||||
useEffect(() => {
|
||||
if (courseStatus === IDLE_STATUS || coursesList.length === 0) {
|
||||
if (courseListStatus === IDLE_STATUS) {
|
||||
dispatch(fetchCourseList());
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [courseStatus]);
|
||||
if (courseStatus === LOADING_STATUS) {
|
||||
}, []);
|
||||
|
||||
if (courseListStatus === SUCCESS_STATUS && coursesList.length === 0) {
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
|
||||
if (courseListStatus === LOADING_STATUS) {
|
||||
return (
|
||||
<div className="d-flex h-100">
|
||||
<Spinner
|
||||
@@ -40,6 +51,7 @@ const NotificationCourses = ({ intl }) => {
|
||||
{
|
||||
coursesList.map(course => (
|
||||
<Link
|
||||
key={course.id}
|
||||
to={`/notifications/${course.id}`}
|
||||
>
|
||||
<div className="mb-4 d-flex text-gray-700">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -6,46 +6,50 @@ import { Collapsible } from '@edx/paragon';
|
||||
import { messages } from './messages';
|
||||
import ToggleSwitch from './ToggleSwitch';
|
||||
import {
|
||||
getPreferenceGroup,
|
||||
getSelectedCourse,
|
||||
selectPreferenceAppToggleValue,
|
||||
selectPreferencesOfApp,
|
||||
selectSelectedCourseId,
|
||||
} from './data/selectors';
|
||||
import NotificationPreferenceRow from './NotificationPreferenceRow';
|
||||
import { updateGroupValue } from './data/actions';
|
||||
import { updateAppPreferenceToggle } from './data/thunks';
|
||||
|
||||
const NotificationPreferenceGroup = ({ groupId }) => {
|
||||
const NotificationPreferenceApp = ({ appId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const courseId = useSelector(getSelectedCourse());
|
||||
const preferenceGroup = useSelector(getPreferenceGroup(groupId));
|
||||
const [groupToggle, setGroupToggle] = useState(true);
|
||||
const courseId = useSelector(selectSelectedCourseId());
|
||||
const appPreferences = useSelector(selectPreferencesOfApp(appId));
|
||||
const appToggle = useSelector(selectPreferenceAppToggleValue(appId));
|
||||
|
||||
const preferences = useMemo(() => (
|
||||
preferenceGroup.map(preference => (
|
||||
appPreferences.map(preference => (
|
||||
<NotificationPreferenceRow
|
||||
key={preference.id}
|
||||
groupId={groupId}
|
||||
appId={appId}
|
||||
preferenceName={preference.id}
|
||||
/>
|
||||
))), [groupId, preferenceGroup]);
|
||||
))), [appId, appPreferences]);
|
||||
|
||||
const onChangeGroupSettings = useCallback((checked) => {
|
||||
setGroupToggle(checked);
|
||||
dispatch(updateGroupValue(courseId, groupId, checked));
|
||||
const onChangeAppSettings = useCallback((event) => {
|
||||
dispatch(updateAppPreferenceToggle(courseId, appId, event.target.checked));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [groupId]);
|
||||
}, [appId]);
|
||||
|
||||
if (!courseId) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Collapsible.Advanced open={groupToggle}>
|
||||
<Collapsible.Advanced open={appToggle}>
|
||||
<Collapsible.Trigger>
|
||||
<div className="d-flex">
|
||||
<span className="ml-0 mr-auto">
|
||||
{intl.formatMessage(messages.notificationGroupTitle, { key: groupId })}
|
||||
{intl.formatMessage(messages.notificationAppTitle, { key: appId })}
|
||||
</span>
|
||||
<span className="ml-auto mr-0">
|
||||
<ToggleSwitch value={groupToggle} onChange={onChangeGroupSettings} />
|
||||
<ToggleSwitch
|
||||
name={appId}
|
||||
value={appToggle}
|
||||
onChange={onChangeAppSettings}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
@@ -67,8 +71,8 @@ const NotificationPreferenceGroup = ({ groupId }) => {
|
||||
);
|
||||
};
|
||||
|
||||
NotificationPreferenceGroup.propTypes = {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
NotificationPreferenceApp.propTypes = {
|
||||
appId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NotificationPreferenceGroup);
|
||||
export default React.memo(NotificationPreferenceApp);
|
||||
@@ -1,50 +1,91 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
|
||||
import { InfoOutline } from '@edx/paragon/icons';
|
||||
import { messages } from './messages';
|
||||
import ToggleSwitch from './ToggleSwitch';
|
||||
import { getPreferenceAttribute } from './data/selectors';
|
||||
import { updatePreferenceValue } from './data/actions';
|
||||
import {
|
||||
selectPreference,
|
||||
selectPreferenceNonEditableChannels,
|
||||
selectSelectedCourseId,
|
||||
} from './data/selectors';
|
||||
import { updatePreferenceToggle } from './data/thunks';
|
||||
|
||||
const NotificationPreferenceRow = ({ groupId, preferenceName }) => {
|
||||
const NotificationPreferenceRow = ({ appId, preferenceName }) => {
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const preference = useSelector(getPreferenceAttribute(groupId, preferenceName));
|
||||
const onToggle = useCallback((checked, notificationChannel) => {
|
||||
dispatch(updatePreferenceValue(groupId, preferenceName, notificationChannel, checked));
|
||||
}, [dispatch, groupId, preferenceName]);
|
||||
const courseId = useSelector(selectSelectedCourseId());
|
||||
const preference = useSelector(selectPreference(appId, preferenceName));
|
||||
const nonEditable = useSelector(selectPreferenceNonEditableChannels(appId, preferenceName));
|
||||
|
||||
const onToggle = useCallback((event) => {
|
||||
const {
|
||||
checked,
|
||||
name: notificationChannel,
|
||||
} = event.target;
|
||||
dispatch(updatePreferenceToggle(
|
||||
courseId,
|
||||
appId,
|
||||
preferenceName,
|
||||
notificationChannel,
|
||||
checked,
|
||||
));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appId, preferenceName]);
|
||||
|
||||
const tooltipId = `${preferenceName}-tooltip`;
|
||||
return (
|
||||
<div className="d-flex flex-row mb-3">
|
||||
<span className="col-8 px-0">
|
||||
<span className="d-flex align-items-center col-8 px-0">
|
||||
{intl.formatMessage(messages.notificationTitle, { text: preferenceName })}
|
||||
{
|
||||
preference.info !== '' && (
|
||||
<OverlayTrigger
|
||||
id={tooltipId}
|
||||
className="d-inline"
|
||||
placement="top"
|
||||
overlay={(
|
||||
<Tooltip id={tooltipId}>
|
||||
{preference.info}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<span className="ml-2">
|
||||
<Icon src={InfoOutline} />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span className="d-flex col-4 px-0">
|
||||
<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>
|
||||
{
|
||||
['web', 'email', 'push'].map((channel) => (
|
||||
<span
|
||||
className={classNames(
|
||||
{ 'ml-0 mr-auto': channel === 'web' },
|
||||
{ 'mx-auto': channel === 'email' },
|
||||
{ 'ml-auto mr-0': channel === 'push' },
|
||||
)}
|
||||
>
|
||||
<ToggleSwitch
|
||||
name={channel}
|
||||
value={preference[channel]}
|
||||
onChange={onToggle}
|
||||
disabled={nonEditable.includes(channel)}
|
||||
/>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NotificationPreferenceRow.propTypes = {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
appId: PropTypes.string.isRequired,
|
||||
preferenceName: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,40 +6,44 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Container, Icon, Spinner } from '@edx/paragon';
|
||||
import { ArrowBack } from '@edx/paragon/icons';
|
||||
import {
|
||||
courseListStatus,
|
||||
getCourse,
|
||||
getPreferenceGroupIds,
|
||||
notificationPreferencesStatus,
|
||||
selectCourseListStatus,
|
||||
selectCourse,
|
||||
selectPreferenceAppsId,
|
||||
selectNotificationPreferencesStatus,
|
||||
selectCourseList,
|
||||
} from './data/selectors';
|
||||
import { fetchCourseList, fetchCourseNotificationPreferences } from './data/thunks';
|
||||
import { messages } from './messages';
|
||||
import NotificationPreferenceGroup from './NotificationPreferenceGroup';
|
||||
import { updateSelectedCourse } from './data/actions';
|
||||
import { LOADING_STATUS, SUCCESS_STATUS } from '../constants';
|
||||
import NotificationPreferenceApp from './NotificationPreferenceApp';
|
||||
import {
|
||||
FAILURE_STATUS,
|
||||
IDLE_STATUS,
|
||||
LOADING_STATUS,
|
||||
SUCCESS_STATUS,
|
||||
} from '../constants';
|
||||
import { NotFoundPage } from '../account-settings';
|
||||
|
||||
const NotificationPreferences = () => {
|
||||
const { courseId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const courseStatus = useSelector(courseListStatus());
|
||||
const notificationStatus = useSelector(notificationPreferencesStatus());
|
||||
const course = useSelector(getCourse(courseId));
|
||||
const preferenceGroups = useSelector(getPreferenceGroupIds());
|
||||
const courseStatus = useSelector(selectCourseListStatus());
|
||||
const coursesList = useSelector(selectCourseList());
|
||||
const course = useSelector(selectCourse(courseId));
|
||||
const notificationStatus = useSelector(selectNotificationPreferencesStatus());
|
||||
const preferenceAppsIds = useSelector(selectPreferenceAppsId());
|
||||
|
||||
const preferencesList = useMemo(() => (
|
||||
preferenceGroups.map(key => (
|
||||
<NotificationPreferenceGroup groupId={key} key={key} />
|
||||
preferenceAppsIds.map(appId => (
|
||||
<NotificationPreferenceApp appId={appId} key={appId} />
|
||||
))
|
||||
), [preferenceGroups]);
|
||||
), [preferenceAppsIds]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(updateSelectedCourse(courseId));
|
||||
if (courseStatus !== SUCCESS_STATUS) {
|
||||
if ([IDLE_STATUS, FAILURE_STATUS].includes(courseStatus)) {
|
||||
dispatch(fetchCourseList());
|
||||
}
|
||||
if (notificationStatus !== SUCCESS_STATUS) {
|
||||
dispatch(fetchCourseNotificationPreferences(courseId));
|
||||
}
|
||||
dispatch(fetchCourseNotificationPreferences(courseId));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [courseId]);
|
||||
|
||||
@@ -55,6 +59,14 @@ const NotificationPreferences = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(courseStatus === SUCCESS_STATUS && coursesList.length === 0)
|
||||
|| (notificationStatus === FAILURE_STATUS && coursesList.length !== 0)
|
||||
) {
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container size="md">
|
||||
<h2 className="notification-heading mt-6 mb-5.5">
|
||||
|
||||
@@ -2,17 +2,30 @@ import { Form } from '@edx/paragon';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ToggleSwitch = ({ value, onChange }) => (
|
||||
<Form.Switch checked={value} onChange={(event) => onChange(event.target.checked)} />
|
||||
const ToggleSwitch = ({
|
||||
name,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
}) => (
|
||||
<Form.Switch
|
||||
name={name}
|
||||
checked={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
ToggleSwitch.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.bool.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
ToggleSwitch.defaultProps = {
|
||||
onChange: () => null,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export default React.memo(ToggleSwitch);
|
||||
|
||||
@@ -7,7 +7,7 @@ export const Actions = {
|
||||
FAILED_COURSE_LIST: 'failedCourseList',
|
||||
UPDATE_SELECTED_COURSE: 'updateSelectedCourse',
|
||||
UPDATE_PREFERENCE: 'updatePreference',
|
||||
UPDATE_GROUP_PREFERENCE: 'updateGroupValue',
|
||||
UPDATE_APP_PREFERENCE: 'updateAppValue',
|
||||
};
|
||||
|
||||
export const fetchNotificationPreferenceSuccess = (courseId, payload) => dispatch => (
|
||||
@@ -38,21 +38,21 @@ export const updateSelectedCourse = courseId => dispatch => (
|
||||
dispatch({ type: Actions.UPDATE_SELECTED_COURSE, courseId })
|
||||
);
|
||||
|
||||
export const updatePreferenceValue = (groupName, preferenceName, notificationChannel, value) => dispatch => (
|
||||
export const updatePreferenceValue = (appId, preferenceName, notificationChannel, value) => dispatch => (
|
||||
dispatch({
|
||||
type: Actions.UPDATE_PREFERENCE,
|
||||
groupName,
|
||||
appId,
|
||||
preferenceName,
|
||||
notificationChannel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
|
||||
export const updateGroupValue = (courseId, groupName, value) => dispatch => (
|
||||
export const updateAppToggle = (courseId, appId, value) => dispatch => (
|
||||
dispatch({
|
||||
type: Actions.UPDATE_GROUP_PREFERENCE,
|
||||
type: Actions.UPDATE_APP_PREFERENCE,
|
||||
courseId,
|
||||
groupName,
|
||||
appId,
|
||||
value,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -15,13 +15,14 @@ export const defaultState = {
|
||||
status: IDLE_STATUS,
|
||||
selectedCourse: null,
|
||||
preferences: [],
|
||||
groups: [],
|
||||
apps: [],
|
||||
notEditable: {},
|
||||
},
|
||||
};
|
||||
|
||||
const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
const {
|
||||
courseId, groupName, notificationChannel, preferenceName, value,
|
||||
courseId, appId, notificationChannel, preferenceName, value,
|
||||
} = action;
|
||||
switch (action.type) {
|
||||
case Actions.FETCHING_COURSE_LIST:
|
||||
@@ -54,7 +55,9 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
preferences: {
|
||||
...state.preferences,
|
||||
status: LOADING_STATUS,
|
||||
preferences: {},
|
||||
preferences: [],
|
||||
apps: [],
|
||||
notEditable: {},
|
||||
},
|
||||
};
|
||||
case Actions.FETCHED_PREFERENCES:
|
||||
@@ -70,8 +73,11 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
return {
|
||||
...state,
|
||||
preferences: {
|
||||
...state.preferences,
|
||||
status: FAILURE_STATUS,
|
||||
preferences: {},
|
||||
preferences: [],
|
||||
apps: [],
|
||||
notEditable: {},
|
||||
},
|
||||
};
|
||||
case Actions.UPDATE_SELECTED_COURSE:
|
||||
@@ -87,27 +93,22 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
|
||||
...state,
|
||||
preferences: {
|
||||
...state.preferences,
|
||||
preferences: state.preferences.preferences.map((element) => (
|
||||
element.id === preferenceName
|
||||
? { ...element, [notificationChannel]: value }
|
||||
: element
|
||||
preferences: state.preferences.preferences.map((preference) => (
|
||||
preference.id === preferenceName
|
||||
? { ...preference, [notificationChannel]: value }
|
||||
: preference
|
||||
)),
|
||||
},
|
||||
};
|
||||
case Actions.UPDATE_GROUP_PREFERENCE:
|
||||
case Actions.UPDATE_APP_PREFERENCE:
|
||||
return {
|
||||
...state,
|
||||
preferences: {
|
||||
...state.preferences,
|
||||
preferences: state.preferences.preferences.map((element) => (
|
||||
element.groupId === groupName
|
||||
? {
|
||||
...element,
|
||||
web: value,
|
||||
email: value,
|
||||
push: value,
|
||||
}
|
||||
: element
|
||||
apps: state.preferences.apps.map(app => (
|
||||
app.id === appId
|
||||
? { ...app, enabled: value }
|
||||
: app
|
||||
)),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,41 +1,55 @@
|
||||
export const notificationPreferencesStatus = () => state => (
|
||||
export const selectNotificationPreferencesStatus = () => state => (
|
||||
state.notificationPreferences.preferences.status
|
||||
);
|
||||
|
||||
export const getPreferences = () => state => (
|
||||
state.notificationPreferences?.preferences?.preferences
|
||||
export const selectPreferences = () => state => (
|
||||
state.notificationPreferences.preferences?.preferences
|
||||
);
|
||||
|
||||
export const courseListStatus = () => state => (
|
||||
export const selectCourseListStatus = () => state => (
|
||||
state.notificationPreferences.courses.status
|
||||
);
|
||||
|
||||
export const getCourseList = () => state => (
|
||||
export const selectCourseList = () => state => (
|
||||
state.notificationPreferences.courses.courses
|
||||
);
|
||||
|
||||
export const getCourse = courseId => state => (
|
||||
getCourseList()(state).find(
|
||||
element => element.id === courseId,
|
||||
export const selectCourse = courseId => state => (
|
||||
selectCourseList()(state).find(
|
||||
course => course.id === courseId,
|
||||
)
|
||||
);
|
||||
|
||||
export const getPreferenceGroupIds = () => state => (
|
||||
state.notificationPreferences.preferences.groups
|
||||
export const selectPreferenceAppsId = () => state => (
|
||||
state.notificationPreferences.preferences.apps.map(app => app.id)
|
||||
);
|
||||
|
||||
export const getPreferenceGroup = group => state => (
|
||||
getPreferences()(state).filter((preference) => (
|
||||
preference.groupId === group
|
||||
export const selectPreferencesOfApp = appId => state => (
|
||||
selectPreferences()(state).filter(preference => (
|
||||
preference.appId === appId
|
||||
))
|
||||
);
|
||||
|
||||
export const getPreferenceAttribute = (group, name) => state => (
|
||||
getPreferenceGroup(group)(state).find((preference) => (
|
||||
preference.id === name
|
||||
export const selectPreferenceApp = appId => state => (
|
||||
state.notificationPreferences.preferences.apps.find(app => (
|
||||
app.id === appId
|
||||
))
|
||||
);
|
||||
|
||||
export const getSelectedCourse = () => state => (
|
||||
export const selectPreferenceAppToggleValue = appId => state => (
|
||||
selectPreferenceApp(appId)(state).enabled
|
||||
);
|
||||
|
||||
export const selectPreference = (appId, name) => state => (
|
||||
selectPreferences()(state).find((preference) => (
|
||||
preference.id === name && preference.appId === appId
|
||||
))
|
||||
);
|
||||
|
||||
export const selectPreferenceNonEditableChannels = (appId, name) => state => (
|
||||
state?.notificationPreferences.preferences.notEditable[appId]?.[name] || []
|
||||
);
|
||||
|
||||
export const selectSelectedCourseId = () => state => (
|
||||
state.notificationPreferences.preferences.selectedCourse
|
||||
);
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
// import { getConfig } from '@edx/frontend-platform';
|
||||
// import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
export async function getCourseNotificationPreferences(courseId) {
|
||||
// const url = `${getConfig().LMS_BASE_URL}/api/notifications/${courseId}`;
|
||||
// const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
const data = {
|
||||
discussion: {
|
||||
new_post: {
|
||||
web: true,
|
||||
push: false,
|
||||
email: false,
|
||||
},
|
||||
new_comment: {
|
||||
web: true,
|
||||
push: false,
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
coursework: {
|
||||
new_assignment: {
|
||||
web: true,
|
||||
push: false,
|
||||
email: false,
|
||||
},
|
||||
new_grade: {
|
||||
web: true,
|
||||
push: false,
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
export const getCourseNotificationPreferences = async (courseId) => {
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/notifications/configurations/${courseId}`;
|
||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export async function getCourseList() {
|
||||
// const url = `${getConfig().LMS_BASE_URL}/api/notifications/${courseId}`;
|
||||
// const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
return [
|
||||
{ id: 'course-v1:edX+Supply+Demo_Course', name: 'Supply Chain Analytics' },
|
||||
{ id: 'course-v1:edX+Happiness+At+Work_Course', name: 'The Foundation of Happiness At Work' },
|
||||
{ id: 'course-v1:edX+Empathy+At+Work_Course', name: 'Empathy and Emotional Intelligence At Work' },
|
||||
];
|
||||
}
|
||||
export const getCourseList = async () => {
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/notifications/enrollments/`;
|
||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const patchAppPreferenceToggle = async (courseId, appId, value) => {
|
||||
const patchData = snakeCaseObject({
|
||||
notificationApp: appId,
|
||||
value,
|
||||
});
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/notifications/configurations/${courseId}`;
|
||||
const { data } = await getAuthenticatedHttpClient().patch(url, patchData);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const patchPreferenceToggle = async (
|
||||
courseId,
|
||||
notificationApp,
|
||||
notificationType,
|
||||
notificationChannel,
|
||||
value,
|
||||
) => {
|
||||
const patchData = snakeCaseObject({
|
||||
notificationApp,
|
||||
notificationType: snakeCase(notificationType),
|
||||
notificationChannel,
|
||||
value,
|
||||
});
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/notifications/configurations/${courseId}`;
|
||||
const { data } = await getAuthenticatedHttpClient().patch(url, patchData);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -6,30 +6,56 @@ import {
|
||||
fetchNotificationPreferenceFailed,
|
||||
fetchNotificationPreferenceFetching,
|
||||
fetchNotificationPreferenceSuccess,
|
||||
updateAppToggle,
|
||||
updatePreferenceValue,
|
||||
updateSelectedCourse,
|
||||
} from './actions';
|
||||
import {
|
||||
getCourseList,
|
||||
getCourseNotificationPreferences,
|
||||
patchAppPreferenceToggle,
|
||||
patchPreferenceToggle,
|
||||
} from './service';
|
||||
|
||||
const normalizePreferences = (preferences) => {
|
||||
const groups = Object.keys(preferences);
|
||||
const preferenceList = groups.map(groupId => {
|
||||
const preferencesKeys = Object.keys(preferences[groupId]);
|
||||
const normalizeCourses = (responseData) => (
|
||||
responseData.map((enrollment) => ({
|
||||
id: enrollment.course.id,
|
||||
name: enrollment.course.displayName,
|
||||
}))
|
||||
);
|
||||
|
||||
const normalizePreferences = (responseData) => {
|
||||
const preferences = responseData.notificationPreferenceConfig;
|
||||
|
||||
const appKeys = Object.keys(preferences);
|
||||
const apps = appKeys.map((appId) => ({
|
||||
id: appId,
|
||||
enabled: preferences[appId].enabled,
|
||||
}));
|
||||
|
||||
const notEditable = {};
|
||||
const preferenceList = appKeys.map(appId => {
|
||||
const preferencesKeys = Object.keys(preferences[appId].notificationTypes);
|
||||
const flatPreferences = preferencesKeys.map(preferenceId => (
|
||||
{
|
||||
id: preferenceId,
|
||||
groupId,
|
||||
web: preferences?.[groupId]?.[preferenceId].web,
|
||||
push: preferences?.[groupId]?.[preferenceId].push,
|
||||
mobile: preferences?.[groupId]?.[preferenceId].mobile,
|
||||
appId,
|
||||
web: preferences[appId].notificationTypes[preferenceId].web,
|
||||
push: preferences[appId].notificationTypes[preferenceId].push,
|
||||
email: preferences[appId].notificationTypes[preferenceId].email,
|
||||
info: preferences[appId].notificationTypes[preferenceId].info || '',
|
||||
}
|
||||
));
|
||||
|
||||
notEditable[appId] = preferences[appId].notEditable;
|
||||
|
||||
return flatPreferences;
|
||||
}).flat();
|
||||
|
||||
const normalizedPreferences = {
|
||||
groups,
|
||||
apps,
|
||||
preferences: preferenceList,
|
||||
notEditable,
|
||||
};
|
||||
return normalizedPreferences;
|
||||
};
|
||||
@@ -39,7 +65,8 @@ export const fetchCourseList = () => (
|
||||
try {
|
||||
dispatch(fetchCourseListFetching());
|
||||
const data = await getCourseList();
|
||||
dispatch(fetchCourseListSuccess(data));
|
||||
const normalizedData = normalizeCourses(camelCaseObject(data));
|
||||
dispatch(fetchCourseListSuccess(normalizedData));
|
||||
} catch (errors) {
|
||||
dispatch(fetchCourseListFailed());
|
||||
}
|
||||
@@ -49,6 +76,7 @@ export const fetchCourseList = () => (
|
||||
export const fetchCourseNotificationPreferences = (courseId) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(updateSelectedCourse(courseId));
|
||||
dispatch(fetchNotificationPreferenceFetching());
|
||||
const data = await getCourseNotificationPreferences(courseId);
|
||||
const normalizedData = normalizePreferences(camelCaseObject(data));
|
||||
@@ -58,3 +86,53 @@ export const fetchCourseNotificationPreferences = (courseId) => (
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const updateAppPreferenceToggle = (courseId, appId, value) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(updateAppToggle(courseId, appId, value));
|
||||
const data = await patchAppPreferenceToggle(courseId, appId, value);
|
||||
const normalizedData = normalizePreferences(camelCaseObject(data));
|
||||
dispatch(fetchNotificationPreferenceSuccess(courseId, normalizedData));
|
||||
} catch (errors) {
|
||||
dispatch(updateAppToggle(courseId, appId, !value));
|
||||
dispatch(fetchNotificationPreferenceFailed());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const updatePreferenceToggle = (
|
||||
courseId,
|
||||
notificationApp,
|
||||
notificationType,
|
||||
notificationChannel,
|
||||
value,
|
||||
) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(updatePreferenceValue(
|
||||
notificationApp,
|
||||
notificationType,
|
||||
notificationChannel,
|
||||
value,
|
||||
));
|
||||
const data = await patchPreferenceToggle(
|
||||
courseId,
|
||||
notificationApp,
|
||||
notificationType,
|
||||
notificationChannel,
|
||||
value,
|
||||
);
|
||||
const normalizedData = normalizePreferences(camelCaseObject(data));
|
||||
dispatch(fetchNotificationPreferenceSuccess(courseId, normalizedData));
|
||||
} catch (errors) {
|
||||
dispatch(updatePreferenceValue(
|
||||
notificationApp,
|
||||
notificationType,
|
||||
notificationChannel,
|
||||
!value,
|
||||
));
|
||||
dispatch(fetchNotificationPreferenceFailed());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -7,8 +7,8 @@ export const messages = defineMessages({
|
||||
defaultMessage: 'Notifications',
|
||||
description: 'Notification title',
|
||||
},
|
||||
notificationGroupTitle: {
|
||||
id: 'notification.preference.group.title',
|
||||
notificationAppTitle: {
|
||||
id: 'notification.preference.app.title',
|
||||
defaultMessage: `{
|
||||
key, select,
|
||||
discussion {Discussions}
|
||||
@@ -21,8 +21,12 @@ export const messages = defineMessages({
|
||||
id: 'notification.preference.title',
|
||||
defaultMessage: `{
|
||||
text, select,
|
||||
core {Core}
|
||||
newPost {New Post}
|
||||
newComment {New Comment}
|
||||
newCommentOnPost {New Comment On Post}
|
||||
newResponseOnPost {New Response On Post}
|
||||
newResponseOnComment {New Response On Comment}
|
||||
newAssignment {New Assignment}
|
||||
newGrade {New Grade}
|
||||
other {{text}}
|
||||
|
||||
Reference in New Issue
Block a user