-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added UI for one click unsubscribe flow (#1444)
* feat: added UI for one click unsubscribe flow --------- Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
- Loading branch information
1 parent
7cbbc72
commit a681333
Showing
7 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { getConfig } from '@edx/frontend-platform'; | ||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; | ||
|
||
export const getUnsubscribeUrl = (userToken, updatePatch) => ( | ||
`${getConfig().LMS_BASE_URL}/api/notifications/preferences/update/${userToken}/${updatePatch}/` | ||
); | ||
|
||
export async function unsubscribeNotificationPreferences(userToken, updatePatch) { | ||
const url = getUnsubscribeUrl(userToken, updatePatch); | ||
return getAuthenticatedHttpClient().get(url); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
|
||
import { Container, Icon, Hyperlink } from '@openedx/paragon'; | ||
import { CheckCircleLightOutline, ErrorOutline } from '@openedx/paragon/icons'; | ||
import { useParams } from 'react-router-dom'; | ||
|
||
import Header from '@edx/frontend-component-header'; | ||
import { getConfig } from '@edx/frontend-platform'; | ||
import { sendTrackEvent } from '@edx/frontend-platform/analytics'; | ||
import { logError } from '@edx/frontend-platform/logging'; | ||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; | ||
|
||
import { LOADED, LOADING, FAILED } from '../constants'; | ||
import PageLoading from '../generic/PageLoading'; | ||
import { unsubscribeNotificationPreferences } from './data/api'; | ||
import messages from './messages'; | ||
|
||
const PreferencesUnsubscribe = () => { | ||
const intl = useIntl(); | ||
const { userToken, updatePatch } = useParams(); | ||
const [status, setStatus] = useState(LOADING); | ||
|
||
useEffect(() => { | ||
unsubscribeNotificationPreferences(userToken, updatePatch).then( | ||
() => setStatus(LOADED), | ||
(error) => { | ||
setStatus(FAILED); | ||
logError(error); | ||
}, | ||
); | ||
sendTrackEvent('edx.ui.lms.notifications.preferences.unsubscribe', { userToken, updatePatch }); | ||
}, []); | ||
|
||
const pageContent = { | ||
icon: CheckCircleLightOutline, | ||
iconClass: 'text-success', | ||
headingText: messages.unsubscribeSuccessHeading, | ||
bodyText: messages.unsubscribeSuccessMessage, | ||
}; | ||
|
||
if (status === FAILED) { | ||
pageContent.icon = ErrorOutline; | ||
pageContent.iconClass = 'text-danger'; | ||
pageContent.headingText = messages.unsubscribeFailedHeading; | ||
pageContent.bodyText = messages.unsubscribeFailedMessage; | ||
} | ||
|
||
return ( | ||
<div style={{ height: '100vh' }}> | ||
<Header /> | ||
<Container size="xs" className="h-75 mx-auto my-auto"> | ||
<div className="d-flex flex-row h-100"> | ||
<div className="mx-auto my-auto"> | ||
{status === LOADING && <PageLoading srMessage={`${intl.formatMessage(messages.unsubscribeLoading)}`} />} | ||
{status !== LOADING && ( | ||
<> | ||
<Icon src={pageContent.icon} className={`size-56px mx-auto ${pageContent.iconClass}`} /> | ||
<h3 className="font-weight-bold text-primary-500 text-center my-3" data-testid="heading-text"> | ||
{intl.formatMessage(pageContent.headingText)} | ||
</h3> | ||
<div className="font-weight-normal text-gray-700 text-center"> | ||
{intl.formatMessage(pageContent.bodyText)} | ||
</div> | ||
<small className="d-block font-weight-normal text-gray text-center mt-3"> | ||
<FormattedMessage | ||
id="learning.notification.preferences.unsubscribe.preferenceCenterUrl" | ||
description="Shown as a suggestion or recommendation for learner when their unsubscribing request has failed" | ||
defaultMessage="Go to the {preferenceCenterUrl} to set your preferences" | ||
values={{ | ||
preferenceCenterUrl: ( | ||
<Hyperlink | ||
destination={`${getConfig().ACCOUNT_SETTINGS_URL}/notifications`} | ||
> | ||
{intl.formatMessage(messages.preferenceCenterUrl)} | ||
</Hyperlink> | ||
), | ||
}} | ||
/> | ||
</small> | ||
</> | ||
)} | ||
</div> | ||
</div> | ||
</Container> | ||
</div> | ||
); | ||
}; | ||
|
||
export default PreferencesUnsubscribe; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React from 'react'; | ||
|
||
import MockAdapter from 'axios-mock-adapter'; | ||
import { MemoryRouter, Route, Routes } from 'react-router-dom'; | ||
|
||
import { sendTrackEvent } from '@edx/frontend-platform/analytics'; | ||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; | ||
import { AppProvider } from '@edx/frontend-platform/react'; | ||
|
||
import { ROUTES } from '../constants'; | ||
import { | ||
initializeTestStore, initializeMockApp, render, screen, waitFor, | ||
} from '../setupTest'; | ||
import { getUnsubscribeUrl } from './data/api'; | ||
import PreferencesUnsubscribe from './index'; | ||
import initializeStore from '../store'; | ||
import { UserMessagesProvider } from '../generic/user-messages'; | ||
|
||
initializeMockApp(); | ||
jest.mock('@edx/frontend-platform/analytics'); | ||
|
||
describe('Notification Preferences One Click Unsubscribe', () => { | ||
let axiosMock; | ||
let component; | ||
let store; | ||
const userToken = '1234'; | ||
const updatePatch = 'abc123'; | ||
const url = getUnsubscribeUrl(userToken, updatePatch); | ||
|
||
beforeAll(async () => { | ||
await initializeTestStore(); | ||
axiosMock = new MockAdapter(getAuthenticatedHttpClient()); | ||
}); | ||
|
||
beforeEach(() => { | ||
sendTrackEvent.mockClear(); | ||
axiosMock.reset(); | ||
store = initializeStore(); | ||
component = ( | ||
<AppProvider store={store} wrapWithRouter={false}> | ||
<UserMessagesProvider> | ||
<MemoryRouter initialEntries={[`${`/preferences-unsubscribe/${userToken}/${updatePatch}/`}`]}> | ||
<Routes> | ||
<Route path={ROUTES.PREFERENCES_UNSUBSCRIBE} element={<PreferencesUnsubscribe />} /> | ||
</Routes> | ||
</MemoryRouter> | ||
</UserMessagesProvider> | ||
</AppProvider> | ||
); | ||
}); | ||
|
||
it('tests UI when unsubscribe is successful', async () => { | ||
axiosMock.onGet(url).reply(200, { result: 'success' }); | ||
render(component); | ||
|
||
await waitFor(() => expect(axiosMock.history.get).toHaveLength(1)); | ||
expect(sendTrackEvent).toHaveBeenCalledTimes(1); | ||
|
||
expect(screen.getByTestId('heading-text')).toHaveTextContent('Unsubscribe successful'); | ||
}); | ||
|
||
it('tests UI when unsubscribe failed', async () => { | ||
axiosMock.onGet(url).reply(400, { result: 'failed' }); | ||
render(component); | ||
|
||
await waitFor(() => expect(axiosMock.history.get).toHaveLength(1)); | ||
|
||
expect(sendTrackEvent).toHaveBeenCalledTimes(1); | ||
expect(screen.getByTestId('heading-text')).toHaveTextContent('Error unsubscribing from preference'); | ||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.notifications.preferences.unsubscribe', { | ||
userToken, | ||
updatePatch, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { defineMessages } from '@edx/frontend-platform/i18n'; | ||
|
||
const messages = defineMessages({ | ||
unsubscribeLoading: { | ||
id: 'learning.notification.preferences.unsubscribe.loading', | ||
defaultMessage: 'Loading', | ||
}, | ||
unsubscribeSuccessHeading: { | ||
id: 'learning.notification.preferences.unsubscribe.successHeading', | ||
defaultMessage: 'Unsubscribe successful', | ||
}, | ||
unsubscribeSuccessMessage: { | ||
id: 'learning.notification.preferences.unsubscribe.successMessage', | ||
defaultMessage: 'You have successfully unsubscribed from email digests for learning activity', | ||
}, | ||
unsubscribeFailedHeading: { | ||
id: 'learning.notification.preferences.unsubscribe.failedHeading', | ||
defaultMessage: 'Error unsubscribing from preference', | ||
}, | ||
unsubscribeFailedMessage: { | ||
id: 'learning.notification.preferences.unsubscribe.failedMessage', | ||
defaultMessage: 'Invalid Url or token expired', | ||
}, | ||
preferenceCenterUrl: { | ||
id: 'learning.notification.preferences.unsubscribe.preferenceCenterUrl', | ||
defaultMessage: 'preferences page', | ||
}, | ||
}); | ||
|
||
export default messages; |