feat: integrate notification preferences api (#794)

This commit is contained in:
Muhammad Adeel Tajamul
2023-06-06 11:04:23 +05:00
committed by GitHub
parent 09cce6802d
commit be8570edac
13 changed files with 350 additions and 172 deletions

1
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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">

View File

@@ -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);

View File

@@ -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,
};

View File

@@ -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">

View File

@@ -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);

View File

@@ -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,
})
);

View File

@@ -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
)),
},
};

View File

@@ -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
);

View File

@@ -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;
};

View File

@@ -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());
}
}
);

View File

@@ -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}}