Compare commits

..

36 Commits

Author SHA1 Message Date
edX requirements bot
a1c35f134c chore: update browserslist DB (#1421)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-03-16 00:53:02 +00:00
renovate[bot]
41af76f027 fix(deps): update dependency qs to v6.15.0 (#1418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 04:52:00 +00:00
edX requirements bot
57a3b0963c chore: update browserslist DB (#1417)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-03-09 00:47:58 +00:00
Emad Rad
92dcdd0a98 fix: update button labels in ConfirmationModal for better localization
Close #1415
2026-03-03 15:23:57 -03:00
edX requirements bot
29f0cefc1e chore: update browserslist DB (#1414)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-03-02 00:50:06 +00:00
renovate[bot]
794a1d57b9 fix(deps): update dependency bowser to v2.14.1 (#1411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 22:37:50 +00:00
edX requirements bot
fe46e8a1a6 chore: update browserslist DB (#1413)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-02-23 21:07:21 +00:00
renovate[bot]
a525d3c22e fix(deps): update dependency core-js to v3.48.0 (#1412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 21:00:37 +00:00
renovate[bot]
81a2c3c0d2 chore(deps): update dependency @edx/frontend-platform to v8.5.5 (#1410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 05:49:56 +00:00
renovate[bot]
0018eafdcc chore(deps): update dependency @edx/browserslist-config to v1.5.1 (#1409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 05:49:31 +00:00
renovate[bot]
e7db2ef753 fix(deps): update dependency qs to v6.14.2 [security] (#1408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 01:12:05 +00:00
edX requirements bot
2e986d9b74 chore: update browserslist DB (#1407)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-02-16 00:59:58 +00:00
Brian Smith
7c85195a27 fix(deps): regenerate package-lock.json (#1405)
* fix(deps): regenerate package-lock.json

Co-Authored-By: Claude Code <noreply@anthropic.com>

* test: update snapshots

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-02-12 09:41:39 -05:00
edX requirements bot
b686acf5f5 chore: update browserslist DB (#1406)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-02-09 00:48:51 +00:00
Anton Melser
166deeafbd docs: Generify currently supported node version 2026-01-27 09:57:11 -03:00
renovate[bot]
f33fe7d0e5 chore(deps): update dependency @edx/frontend-platform to v8.5.4 (#1404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 08:50:30 +00:00
edX requirements bot
e2896dbf94 chore: update browserslist DB (#1403)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-01-26 00:42:27 +00:00
renovate[bot]
f07d266a43 chore(deps): update react-router monorepo to v6.30.3 (#1402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 06:45:57 +00:00
edX requirements bot
69cdc5f191 chore: update browserslist DB (#1392)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2026-01-19 00:41:31 +00:00
Awais Ansari
4f51f71acc feat: implemented notifications configurations V3 API (#1401)
* feat: implemented notifications configurations V3 API

* fix: removed default daily email cadence when email toggle is turned on
2026-01-15 18:56:02 +05:00
renovate[bot]
c70eca1fde fix(deps): update dependency qs to v6.14.1 [security] (#1400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-13 20:21:35 +00:00
Diana Villalvazo
39dc5bbbd2 docs: update owner/maintainer (#1398) 2026-01-13 13:27:29 -06:00
renovate[bot]
7fa61f3714 fix(deps): update dependency bowser to v2.13.1 (#1393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-13 18:43:37 +00:00
Diana Villalvazo
8ce7c1599d test: fix X/twitter broken tests (#1399) 2026-01-13 13:39:44 -05:00
Stanislav
03bdcff331 feat: Change Twitter to X (#1215) 2025-12-02 21:46:31 +05:00
Awais Ansari
d73d840e93 feat: added env parse to boolean functionality (#1389) 2025-12-01 19:39:53 +05:00
edX requirements bot
d23b5f53df chore: update browserslist DB (#1388)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2025-12-01 00:44:00 +00:00
Awais Ansari
da98bfa021 refactor: simplify notifications channels flag logic (#1381) 2025-11-24 16:20:58 +05:00
renovate[bot]
c37640aa69 fix(deps): update dependency core-js to v3.47.0 (#1384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 05:10:14 +00:00
renovate[bot]
ea35227389 fix(deps): update dependency bowser to v2.13.0 (#1383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 05:10:00 +00:00
edX requirements bot
c35bf95c1c chore: update browserslist DB (#1382)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2025-11-24 00:39:08 +00:00
renovate[bot]
bd26928154 chore(deps): update react-router monorepo to v6.30.2 (#1379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 05:47:51 +00:00
edX requirements bot
cdc8efe17b chore: update browserslist DB (#1378)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2025-11-17 00:37:06 +00:00
edX requirements bot
8b6535ea58 chore: update browserslist DB (#1372)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2025-11-10 00:37:40 +00:00
renovate[bot]
4c9498971a fix(deps): update dependency redux-saga to v1.4.2 (#1370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 06:43:39 +00:00
edX requirements bot
a6b6a3f940 chore: update browserslist DB (#1369)
Co-authored-by: abdullahwaheed <42172960+abdullahwaheed@users.noreply.github.com>
2025-11-03 00:37:33 +00:00
23 changed files with 3017 additions and 6592 deletions

View File

@@ -20,5 +20,5 @@ Include a link to the sandbox for design changes or screenshot for before and af
#### Post-merge Checklist #### Post-merge Checklist
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/edx-infinity** to do it. * [ ] Deploy the changes to prod after verifying on stage or ask **@jacobo-dominguez-wgu** to do it.
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution. * [ ] 🎉 🙌 Celebrate! Thanks for your contribution.

View File

@@ -89,9 +89,9 @@ Cloning and Startup
``git clone https://github.com/openedx/frontend-app-account.git`` ``git clone https://github.com/openedx/frontend-app-account.git``
2. Use node v18.x. 2. Use the version of Node specified in the ``.nvmrc`` file.
The current version of the micro-frontend build scripts support node 18. The current version of the micro-frontend build scripts supports the version of Node found in ``.nvmrc``.
Using other major versions of node *may* work, but this is unsupported. For Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an .nvmrc file to help in setting the convenience, this repository includes an .nvmrc file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_. correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.

View File

@@ -14,6 +14,6 @@ metadata:
openedx.org/arch-interest-groups: "" openedx.org/arch-interest-groups: ""
openedx.org/release: "master" openedx.org/release: "master"
spec: spec:
owner: group:2u-infinity owner: jacobo-dominguez-wgu
type: 'website' type: 'website'
lifecycle: 'production' lifecycle: 'production'

9470
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,9 +44,9 @@
"@tensorflow-models/blazeface": "0.1.0", "@tensorflow-models/blazeface": "0.1.0",
"@tensorflow/tfjs-converter": "4.22.0", "@tensorflow/tfjs-converter": "4.22.0",
"@tensorflow/tfjs-core": "4.22.0", "@tensorflow/tfjs-core": "4.22.0",
"bowser": "2.12.1", "bowser": "2.14.1",
"classnames": "2.5.1", "classnames": "2.5.1",
"core-js": "3.46.0", "core-js": "3.48.0",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"form-urlencoded": "6.1.6", "form-urlencoded": "6.1.6",
"formdata-polyfill": "4.0.10", "formdata-polyfill": "4.0.10",
@@ -64,7 +64,7 @@
"long": "5.3.2", "long": "5.3.2",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"qs": "6.14.0", "qs": "6.15.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-helmet": "6.1.0", "react-helmet": "6.1.0",
@@ -77,14 +77,14 @@
"redux": "4.2.1", "redux": "4.2.1",
"redux-devtools-extension": "2.13.9", "redux-devtools-extension": "2.13.9",
"redux-logger": "3.0.6", "redux-logger": "3.0.6",
"redux-saga": "1.3.0", "redux-saga": "1.4.2",
"redux-thunk": "2.4.2", "redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1", "regenerator-runtime": "0.14.1",
"reselect": "^5.1.1", "reselect": "^5.1.1",
"universal-cookie": "7.2.2" "universal-cookie": "7.2.2"
}, },
"devDependencies": { "devDependencies": {
"@edx/browserslist-config": "1.5.0", "@edx/browserslist-config": "1.5.1",
"@openedx/frontend-build": "^14.6.2", "@openedx/frontend-build": "^14.6.2",
"@testing-library/jest-dom": "6.9.1", "@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "14.3.1", "@testing-library/react": "14.3.1",

View File

@@ -764,11 +764,11 @@ class AccountSettingsPage extends React.Component {
{...editableFieldProps} {...editableFieldProps}
/> />
<EditableField <EditableField
name="social_link_twitter" name="social_link_x"
type="text" type="text"
value={this.props.formValues.social_link_twitter} value={this.props.formValues.social_link_x}
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter'])} label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter'])}
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter.empty'])} emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter.empty'])}
{...editableFieldProps} {...editableFieldProps}
/> />
</div> </div>
@@ -905,7 +905,7 @@ AccountSettingsPage.propTypes = {
phone_number: PropTypes.string, phone_number: PropTypes.string,
social_link_linkedin: PropTypes.string, social_link_linkedin: PropTypes.string,
social_link_facebook: PropTypes.string, social_link_facebook: PropTypes.string,
social_link_twitter: PropTypes.string, social_link_x: PropTypes.string,
time_zone: PropTypes.string, time_zone: PropTypes.string,
state: PropTypes.string, state: PropTypes.string,
useVerifiedNameForCerts: PropTypes.bool.isRequired, useVerifiedNameForCerts: PropTypes.bool.isRequired,

View File

@@ -509,15 +509,15 @@ const messages = defineMessages({
defaultMessage: 'Delete My Account', defaultMessage: 'Delete My Account',
description: 'Header for the user account deletion area', description: 'Header for the user account deletion area',
}, },
'account.settings.field.social.platform.name.twitter': { 'account.settings.field.social.platform.name.xTwitter': {
id: 'account.settings.field.social.platform.name.twitter', id: 'account.settings.field.social.platform.name.xTwitter',
defaultMessage: 'Twitter', defaultMessage: 'X (Twitter)',
description: 'Label for Twitter', description: 'Label for X (Twitter)',
}, },
'account.settings.field.social.platform.name.twitter.empty': { 'account.settings.field.social.platform.name.xTwitter.empty': {
id: 'account.settings.field.social.platform.name.twitter.empty', id: 'account.settings.field.social.platform.name.xTwitter.empty',
defaultMessage: 'Add Twitter profile', defaultMessage: 'Add X profile',
description: 'Placeholder for an empty Twitter field', description: 'Placeholder for an empty X field',
}, },
'account.settings.field.social.platform.name.facebook': { 'account.settings.field.social.platform.name.facebook': {

View File

@@ -11,7 +11,7 @@ import { postVerifiedNameConfig } from '../certificate-preference/data/service';
import { FIELD_LABELS } from './constants'; import { FIELD_LABELS } from './constants';
const SOCIAL_PLATFORMS = [ const SOCIAL_PLATFORMS = [
{ id: 'twitter', key: 'social_link_twitter' }, { id: 'xTwitter', key: 'social_link_x' },
{ id: 'facebook', key: 'social_link_facebook' }, { id: 'facebook', key: 'social_link_facebook' },
{ id: 'linkedin', key: 'social_link_linkedin' }, { id: 'linkedin', key: 'social_link_linkedin' },
]; ];

View File

@@ -38,24 +38,24 @@ describe('account service', () => {
it('returns unpacked account data', async () => { it('returns unpacked account data', async () => {
const apiResponse = { const apiResponse = {
username: 'testuser', username: 'testuser',
social_links: [{ platform: 'twitter', social_link: 'http://t' }], social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
language_proficiencies: [{ code: 'en' }], language_proficiencies: [{ code: 'en' }],
}; };
mockHttpClient.get.mockResolvedValue({ data: apiResponse }); mockHttpClient.get.mockResolvedValue({ data: apiResponse });
const result = await getAccount('testuser'); const result = await getAccount('testuser');
expect(mockHttpClient.get).toHaveBeenCalledWith('http://lms.test/api/user/v1/accounts/testuser'); expect(mockHttpClient.get).toHaveBeenCalledWith('http://lms.test/api/user/v1/accounts/testuser');
expect(result.social_link_twitter).toEqual('http://t'); expect(result.social_link_x).toEqual('http://t');
expect(result.language_proficiencies).toEqual('en'); expect(result.language_proficiencies).toEqual('en');
}); });
}); });
describe('patchAccount', () => { describe('patchAccount', () => {
it('sends packed commit data and returns unpacked response', async () => { it('sends packed commit data and returns unpacked response', async () => {
const commit = { social_link_twitter: 'http://t' }; const commit = { social_link_x: 'http://t' };
const apiResponse = { const apiResponse = {
username: 'testuser', username: 'testuser',
social_links: [{ platform: 'twitter', social_link: 'http://t' }], social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
language_proficiencies: [], language_proficiencies: [],
}; };
mockHttpClient.patch.mockResolvedValue({ data: apiResponse }); mockHttpClient.patch.mockResolvedValue({ data: apiResponse });
@@ -63,10 +63,10 @@ describe('account service', () => {
const result = await patchAccount('testuser', commit); const result = await patchAccount('testuser', commit);
expect(mockHttpClient.patch).toHaveBeenCalledWith( expect(mockHttpClient.patch).toHaveBeenCalledWith(
'http://lms.test/api/user/v1/accounts/testuser', 'http://lms.test/api/user/v1/accounts/testuser',
expect.objectContaining({ social_links: [{ platform: 'twitter', social_link: 'http://t' }] }), expect.objectContaining({ social_links: [{ platform: 'xTwitter', social_link: 'http://t' }] }),
expect.any(Object), expect.any(Object),
); );
expect(result.social_link_twitter).toEqual('http://t'); expect(result.social_link_x).toEqual('http://t');
}); });
}); });

View File

@@ -81,8 +81,8 @@ export class ConfirmationModal extends Component {
isOverflowVisible isOverflowVisible
footerNode={( footerNode={(
<ActionRow> <ActionRow>
<Button variant="link" onClick={onCancel}>Cancel</Button> <Button variant="link" onClick={onCancel}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.cancel'])}</Button>
<Button variant="danger" onClick={onSubmit}>Yes, Delete</Button> <Button variant="danger" onClick={onSubmit}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.delete'])}</Button>
</ActionRow> </ActionRow>
)} )}
> >

View File

@@ -38,6 +38,7 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
data-testid="modal-backdrop" data-testid="modal-backdrop"
onClick={[MockFunction]} onClick={[MockFunction]}
onKeyDown={[MockFunction]} onKeyDown={[MockFunction]}
role="presentation"
/> />
<div <div
aria-label="Are you sure?" aria-label="Are you sure?"
@@ -247,15 +248,17 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
"width": "1px", "width": "1px",
} }
} }
tabIndex={-1} tabIndex={0}
/>, />,
<div <div
className="pgn__modal-layer" className="pgn__modal-layer"
data-focus-lock-disabled="disabled" data-focus-lock-disabled={false}
onBlur={[Function]} onBlur={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseDown={[Function]}
onScrollCapture={[Function]} onScrollCapture={[Function]}
onTouchMoveCapture={[Function]} onTouchMoveCapture={[Function]}
onTouchStart={[Function]}
onWheelCapture={[Function]} onWheelCapture={[Function]}
> >
<div <div
@@ -266,6 +269,7 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
data-testid="modal-backdrop" data-testid="modal-backdrop"
onClick={[MockFunction]} onClick={[MockFunction]}
onKeyDown={[MockFunction]} onKeyDown={[MockFunction]}
role="presentation"
/> />
<div <div
aria-label="Are you sure?" aria-label="Are you sure?"
@@ -393,7 +397,7 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
"width": "1px", "width": "1px",
} }
} }
tabIndex={-1} tabIndex={0}
/>, />,
] ]
`; `;

View File

@@ -23,15 +23,17 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
"width": "1px", "width": "1px",
} }
} }
tabIndex={-1} tabIndex={0}
/>, />,
<div <div
className="pgn__modal-layer" className="pgn__modal-layer"
data-focus-lock-disabled="disabled" data-focus-lock-disabled={false}
onBlur={[Function]} onBlur={[Function]}
onFocus={[Function]} onFocus={[Function]}
onMouseDown={[Function]}
onScrollCapture={[Function]} onScrollCapture={[Function]}
onTouchMoveCapture={[Function]} onTouchMoveCapture={[Function]}
onTouchStart={[Function]}
onWheelCapture={[Function]} onWheelCapture={[Function]}
> >
<div <div
@@ -42,6 +44,7 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
data-testid="modal-backdrop" data-testid="modal-backdrop"
onClick={[MockFunction]} onClick={[MockFunction]}
onKeyDown={[MockFunction]} onKeyDown={[MockFunction]}
role="presentation"
/> />
<div <div
className="mw-sm p-5 bg-white mx-auto my-3" className="mw-sm p-5 bg-white mx-auto my-3"
@@ -84,7 +87,7 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
"width": "1px", "width": "1px",
} }
} }
tabIndex={-1} tabIndex={0}
/>, />,
] ]
`; `;

View File

@@ -11,6 +11,7 @@ import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import AccountSettingsPage from '../AccountSettingsPage'; import AccountSettingsPage from '../AccountSettingsPage';
import mockData from './mockData'; import mockData from './mockData';
import messages from '../AccountSettingsPage.messages';
const mockDispatch = jest.fn(); const mockDispatch = jest.fn();
jest.mock('@edx/frontend-platform/analytics', () => ({ jest.mock('@edx/frontend-platform/analytics', () => ({
@@ -189,7 +190,7 @@ describe('AccountSettingsPage', () => {
expect(screen.getByText('https://linkedin.com/in/testuser')).toBeInTheDocument(); expect(screen.getByText('https://linkedin.com/in/testuser')).toBeInTheDocument();
expect(screen.getByText('Add Facebook profile')).toBeInTheDocument(); expect(screen.getByText('Add Facebook profile')).toBeInTheDocument();
expect(screen.getByText('Add Twitter profile')).toBeInTheDocument(); expect(screen.getByText(messages['account.settings.field.social.platform.name.xTwitter.empty'].defaultMessage)).toBeInTheDocument();
}); });
it('renders Site Preferences section with correct field values', () => { it('renders Site Preferences section with correct field values', () => {

View File

@@ -71,5 +71,5 @@ export function useFeedbackWrapper() {
export function useIsOnMobile() { export function useIsOnMobile() {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
return windowSize.width <= breakpoints.small.minWidth; return windowSize.width <= breakpoints.small.maxWidth;
} }

View File

@@ -18,9 +18,7 @@ import {
selectUpdatePreferencesStatus, selectUpdatePreferencesStatus,
} from './data/selectors'; } from './data/selectors';
import { notificationChannels, shouldHideAppPreferences } from './data/utils'; import { notificationChannels, shouldHideAppPreferences } from './data/utils';
import { import { EMAIL, EMAIL_CADENCE } from './data/constants';
EMAIL, EMAIL_CADENCE, EMAIL_CADENCE_PREFERENCES, MIXED,
} from './data/constants';
const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => { const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -39,13 +37,11 @@ const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => {
return checked; return checked;
}, []); }, []);
const getEmailCadence = useCallback((notificationChannel, checked, innerText, emailCadence) => { const getEmailCadence = useCallback((notificationChannel, innerText, emailCadence) => {
if (notificationChannel === EMAIL_CADENCE) { if (notificationChannel === EMAIL_CADENCE) {
return innerText; return innerText;
} }
if (notificationChannel === EMAIL && checked) {
return EMAIL_CADENCE_PREFERENCES.DAILY;
}
return emailCadence; return emailCadence;
}, []); }, []);
@@ -56,7 +52,6 @@ const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => {
const value = getValue(notificationChannel, innerText, checked); const value = getValue(notificationChannel, innerText, checked);
const emailCadence = getEmailCadence( const emailCadence = getEmailCadence(
notificationChannel, notificationChannel,
checked,
innerText, innerText,
appNotificationPreference.emailCadence, appNotificationPreference.emailCadence,
); );
@@ -66,12 +61,11 @@ const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => {
notificationType, notificationType,
notificationChannel, notificationChannel,
value, value,
emailCadence !== MIXED ? emailCadence : undefined, emailCadence,
)); ));
}, [appPreferences, getValue, getEmailCadence, dispatch, appId]); }, [appPreferences, getValue, getEmailCadence, dispatch, appId]);
const renderPreference = (preference) => ( const renderPreference = (preference) => (
(preference?.coreNotificationTypes?.length > 0 || preference.id !== 'core') && (
<div <div
key={`${preference.id}-${channel}`} key={`${preference.id}-${channel}`}
id={`${preference.id}-${channel}`} id={`${preference.id}-${channel}`}
@@ -100,7 +94,6 @@ const NotificationPreferenceColumn = ({ appId, channel, appPreference }) => {
/> />
)} )}
</div> </div>
)
); );
return ( return (

View File

@@ -249,7 +249,7 @@ describe('Notification Preferences API v2 Logic', () => {
describe('getNotificationPreferences', () => { describe('getNotificationPreferences', () => {
it('should call the v2 configurations URL', async () => { it('should call the v2 configurations URL', async () => {
const expectedUrl = `${LMS_BASE_URL}/api/notifications/v2/configurations/`; const expectedUrl = `${LMS_BASE_URL}/api/notifications/v3/configurations/`;
await getNotificationPreferences(); await getNotificationPreferences();
@@ -260,7 +260,7 @@ describe('Notification Preferences API v2 Logic', () => {
describe('postPreferenceToggle', () => { describe('postPreferenceToggle', () => {
it('should call the v2 configurations URL with PUT method', async () => { it('should call the v2 configurations URL with PUT method', async () => {
const expectedUrl = `${LMS_BASE_URL}/api/notifications/v2/configurations/`; const expectedUrl = `${LMS_BASE_URL}/api/notifications/v3/configurations/`;
const testArgs = ['app_name', 'notification_type', 'web', true, 'daily']; const testArgs = ['app_name', 'notification_type', 'web', true, 'daily'];
await postPreferenceToggle(...testArgs); await postPreferenceToggle(...testArgs);

View File

@@ -23,7 +23,6 @@ const NotificationTypes = ({ appId }) => {
return ( return (
<div className="d-flex flex-column mr-auto px-0"> <div className="d-flex flex-column mr-auto px-0">
{preferences.map(preference => ( {preferences.map(preference => (
(preference?.coreNotificationTypes?.length > 0 || preference.id !== 'core') && (
<> <>
<div <div
key={preference.id} key={preference.id}
@@ -56,8 +55,6 @@ const NotificationTypes = ({ appId }) => {
</div> </div>
)} )}
</> </>
)
))} ))}
</div> </div>
); );

View File

@@ -5,7 +5,6 @@ export const EMAIL_CADENCE_PREFERENCES = {
}; };
export const EMAIL_CADENCE = 'email_cadence'; export const EMAIL_CADENCE = 'email_cadence';
export const EMAIL = 'email'; export const EMAIL = 'email';
export const MIXED = 'Mixed';
export const RequestStatus = /** @type {const} */ ({ export const RequestStatus = /** @type {const} */ ({
IN_PROGRESS: 'in-progress', IN_PROGRESS: 'in-progress',
SUCCESSFUL: 'successful', SUCCESSFUL: 'successful',

View File

@@ -3,7 +3,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import snakeCase from 'lodash.snakecase'; import snakeCase from 'lodash.snakecase';
export const getNotificationPreferences = async () => { export const getNotificationPreferences = async () => {
const url = `${getConfig().LMS_BASE_URL}/api/notifications/v2/configurations/`; const url = `${getConfig().LMS_BASE_URL}/api/notifications/v3/configurations/`;
const { data } = await getAuthenticatedHttpClient().get(url); const { data } = await getAuthenticatedHttpClient().get(url);
return data; return data;
}; };
@@ -16,13 +16,13 @@ export const postPreferenceToggle = async (
emailCadence, emailCadence,
) => { ) => {
const patchData = snakeCaseObject({ const patchData = snakeCaseObject({
notificationApp, notificationApp: snakeCase(notificationApp),
notificationType: snakeCase(notificationType), notificationType: snakeCase(notificationType),
notificationChannel, notificationChannel,
value, value,
emailCadence, emailCadence,
}); });
const url = `${getConfig().LMS_BASE_URL}/api/notifications/v2/configurations/`; const url = `${getConfig().LMS_BASE_URL}/api/notifications/v3/configurations/`;
const { data } = await getAuthenticatedHttpClient().put(url, patchData); const { data } = await getAuthenticatedHttpClient().put(url, patchData);
return data; return data;
}; };

View File

@@ -38,7 +38,7 @@ describe('Notification Preferences Service', () => {
const result = await getNotificationPreferences(); const result = await getNotificationPreferences();
expect(mockHttpClient.get).toHaveBeenCalledWith( expect(mockHttpClient.get).toHaveBeenCalledWith(
'http://test.lms/api/notifications/v2/configurations/', 'http://test.lms/api/notifications/v3/configurations/',
); );
expect(result).toEqual(mockData); expect(result).toEqual(mockData);
}); });
@@ -50,7 +50,7 @@ describe('Notification Preferences Service', () => {
mockHttpClient.put.mockResolvedValue({ data: mockData }); mockHttpClient.put.mockResolvedValue({ data: mockData });
const result = await postPreferenceToggle( const result = await postPreferenceToggle(
'appName', 'app_name',
'someType', 'someType',
'email', 'email',
true, true,
@@ -58,9 +58,9 @@ describe('Notification Preferences Service', () => {
); );
expect(mockHttpClient.put).toHaveBeenCalledWith( expect(mockHttpClient.put).toHaveBeenCalledWith(
'http://test.lms/api/notifications/v2/configurations/', 'http://test.lms/api/notifications/v3/configurations/',
expect.objectContaining({ expect.objectContaining({
notification_app: 'appName', notification_app: 'app_name',
notification_type: 'some_type', notification_type: 'some_type',
notification_channel: 'email', notification_channel: 'email',
value: true, value: true,

View File

@@ -35,7 +35,7 @@ const normalizePreferences = (responseData) => {
const apps = appKeys.map((appId) => ({ const apps = appKeys.map((appId) => ({
id: appId, id: appId,
enabled: preferences[appId].enabled, enabled: preferences[appId].enabled,
})); })).sort((a, b) => a.id.localeCompare(b.id));
const nonEditable = {}; const nonEditable = {};
const preferenceList = appKeys.map(appId => { const preferenceList = appKeys.map(appId => {
@@ -50,7 +50,6 @@ const normalizePreferences = (responseData) => {
info: preferences[appId].notificationTypes[preferenceId].info || '', info: preferences[appId].notificationTypes[preferenceId].info || '',
emailCadence: preferences[appId].notificationTypes[preferenceId].emailCadence emailCadence: preferences[appId].notificationTypes[preferenceId].emailCadence
|| EMAIL_CADENCE_PREFERENCES.DAILY, || EMAIL_CADENCE_PREFERENCES.DAILY,
coreNotificationTypes: preferences[appId].coreNotificationTypes || [],
} }
)); ));
nonEditable[appId] = preferences[appId].nonEditable; nonEditable[appId] = preferences[appId].nonEditable;
@@ -122,7 +121,7 @@ export const updatePreferenceToggle = (
const emailCadenceData = await togglePreference( const emailCadenceData = await togglePreference(
EMAIL_CADENCE, EMAIL_CADENCE,
value, value,
EMAIL_CADENCE_PREFERENCES.DAILY, emailCadence,
); );
handleSuccessResponse(emailCadenceData); handleSuccessResponse(emailCadenceData);

View File

@@ -10,12 +10,5 @@ export const notificationChannels = () => ({
export const shouldHideAppPreferences = (preferences, appId) => { export const shouldHideAppPreferences = (preferences, appId) => {
const appPreferences = preferences.filter(pref => pref.appId === appId); const appPreferences = preferences.filter(pref => pref.appId === appId);
return appPreferences.length === 0;
if (appPreferences.length !== 1) {
return false;
}
const firstPreference = appPreferences[0];
return firstPreference?.id === 'core' && (!firstPreference.coreNotificationTypes?.length);
}; };

View File

@@ -22,7 +22,7 @@ const messages = defineMessages({
id: 'notification.preference.title', id: 'notification.preference.title',
defaultMessage: `{ defaultMessage: `{
text, select, text, select,
core {Activity notifications} groupedNotification {Activity notifications}
newDiscussionPost {New discussion posts} newDiscussionPost {New discussion posts}
newQuestionPost {New question posts} newQuestionPost {New question posts}
contentReported {Reported content} contentReported {Reported content}