Skip to content

Commit 801a302

Browse files
committed
feat: improve un-inrollment experience
enhance the unenrollment experience to provide better user control, clarity, and accessibility.
1 parent 9224306 commit 801a302

File tree

13 files changed

+62
-82
lines changed

13 files changed

+62
-82
lines changed

src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3+
import { reduxHooks } from 'hooks';
34

45
import { useIntl } from '@edx/frontend-platform/i18n';
56
import {
@@ -10,13 +11,17 @@ import {
1011
import messages from './messages';
1112

1213
export const ConfirmPane = ({
14+
cardId,
1315
handleClose,
1416
handleConfirm,
1517
}) => {
1618
const { formatMessage } = useIntl();
19+
const { courseName } = reduxHooks.useCardCourseData(cardId);
20+
const courseTitle = <span className="font-italic">{courseName}</span>;
1721
return (
1822
<>
1923
<h4>{formatMessage(messages.confirmHeader)}</h4>
24+
<p className="py-2">{formatMessage(messages.confirmText, { courseTitle })}</p>
2025
<ActionRow>
2126
<Button variant="tertiary" onClick={handleClose}>
2227
{formatMessage(messages.confirmCancel)}
@@ -31,6 +36,7 @@ export const ConfirmPane = ({
3136
ConfirmPane.propTypes = {
3237
handleClose: PropTypes.func.isRequired,
3338
handleConfirm: PropTypes.func.isRequired,
39+
cardId: PropTypes.string.isRequired,
3440
};
3541

3642
export default ConfirmPane;

src/containers/UnenrollConfirmModal/components/ConfirmPane.test.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ConfirmPane } from './ConfirmPane';
66
import messages from './messages';
77

88
const props = {
9+
cardId: 'cardId',
910
handleClose: jest.fn().mockName('props.handleClose'),
1011
handleConfirm: jest.fn().mockName('props.handleConfirm'),
1112
};

src/containers/UnenrollConfirmModal/components/FinishedPane.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3+
import { reduxHooks } from 'hooks';
34

45
import { useIntl } from '@edx/frontend-platform/i18n';
56
import {
@@ -10,16 +11,18 @@ import {
1011
import messages from './messages';
1112

1213
export const FinishedPane = ({
13-
gaveReason,
14+
cardId,
1415
handleClose,
1516
}) => {
1617
const { formatMessage } = useIntl();
18+
const { courseName } = reduxHooks.useCardCourseData(cardId);
19+
const courseTitle = <span className="font-italic">{courseName}</span>;
20+
1721
return (
1822
<>
1923
<h4>{formatMessage(messages.finishHeading)}</h4>
2024
<p>
21-
{gaveReason && formatMessage(messages.finishThanksText)}
22-
{formatMessage(messages.finishText)}
25+
{formatMessage(messages.finishText, { courseTitle })}
2326
</p>
2427
<ActionRow>
2528
<Button onClick={handleClose}>{formatMessage(messages.finishReturn)}</Button>
@@ -29,7 +32,7 @@ export const FinishedPane = ({
2932
};
3033
FinishedPane.propTypes = {
3134
handleClose: PropTypes.func.isRequired,
32-
gaveReason: PropTypes.bool.isRequired,
35+
cardId: PropTypes.string.isRequired,
3336
};
3437

3538
export default FinishedPane;

src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { FinishedPane } from './FinishedPane';
66
import messages from './messages';
77

88
const props = {
9-
gaveReason: true,
9+
cardId: 'cardId',
1010
handleClose: jest.fn().mockName('props.handleClose'),
1111
};
1212

@@ -25,22 +25,8 @@ describe('UnenrollConfirmModal FinishedPane', () => {
2525
expect(returnButton).toBeInTheDocument();
2626
});
2727
it('Gave reason, display thanks message', () => {
28-
const thanksMsg = screen.getByText((text) => text.includes('Thank you'));
29-
expect(thanksMsg).toBeInTheDocument();
30-
expect(thanksMsg.innerHTML).toContain(formatMessage(messages.finishThanksText));
31-
});
32-
});
33-
describe('Did not give reason', () => {
34-
it('Does not display thanks message', () => {
35-
const customProps = {
36-
gaveReason: false,
37-
handleClose: jest.fn().mockName('props.handleClose'),
38-
};
39-
render(<IntlProvider locale="en"><FinishedPane {...customProps} /></IntlProvider>);
40-
const thanksMsg = screen.queryByText((text) => text.includes('Thank you'));
41-
expect(thanksMsg).toBeNull();
42-
const finishMsg = screen.getByText(formatMessage(messages.finishText));
43-
expect(finishMsg).toBeInTheDocument();
28+
const finishSuccessMessage = screen.getByText((text) => text.includes('Unenrollment Successful'));
29+
expect(finishSuccessMessage).toBeInTheDocument();
4430
});
4531
});
4632
});

src/containers/UnenrollConfirmModal/components/ReasonPane.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import messages from './messages';
1313

1414
export const ReasonPane = ({
1515
reason,
16+
handleClose,
1617
}) => {
1718
const { formatMessage } = useIntl();
1819
const option = (key) => (
@@ -27,6 +28,7 @@ export const ReasonPane = ({
2728
name="unenrollReason"
2829
onChange={reason.selectOption}
2930
value={reason.selected}
31+
defaultValue={constants.reasonKeys.preferNotToSay}
3032
>
3133
{constants.order.map(option)}
3234
<Form.Radio value={constants.reasonKeys.custom}>
@@ -35,12 +37,13 @@ export const ReasonPane = ({
3537
placeholder={formatMessage(constants.messages.customPlaceholder)}
3638
/>
3739
</Form.Radio>
40+
{option(constants.reasonKeys.preferNotToSay)}
3841
</Form.RadioSet>
3942
<ActionRow>
40-
<Button variant="tertiary" onClick={reason.handleSkip}>
41-
{formatMessage(messages.reasonSkip)}
43+
<Button variant="tertiary" onClick={handleClose}>
44+
{formatMessage(messages.confirmCancel)}
4245
</Button>
43-
<Button disabled={!reason.hasReason} onClick={reason.handleSubmit}>
46+
<Button onClick={reason.handleSubmit}>
4447
{formatMessage(messages.reasonSubmit)}
4548
</Button>
4649
</ActionRow>
@@ -50,7 +53,6 @@ export const ReasonPane = ({
5053
ReasonPane.propTypes = {
5154
reason: PropTypes.shape({
5255
value: PropTypes.string,
53-
handleSkip: PropTypes.func,
5456
hasReason: PropTypes.bool,
5557
selectOption: PropTypes.func,
5658
customOption: PropTypes.shape({
@@ -60,6 +62,7 @@ ReasonPane.propTypes = {
6062
selected: PropTypes.string,
6163
handleSubmit: PropTypes.func.isRequired,
6264
}).isRequired,
65+
handleClose: PropTypes.func.isRequired,
6366
};
6467

6568
export default ReasonPane;

src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ describe('UnenrollConfirmModal ReasonPane', () => {
2828
render(<IntlProvider locale="en"><ReasonPane {...props} /></IntlProvider>);
2929
const radioButtons = screen.getAllByRole('radio');
3030
expect(radioButtons).toBeDefined();
31-
expect(radioButtons.length).toBe(10);
31+
expect(radioButtons.length).toBe(11);
3232
});
33-
it('render skip button', () => {
33+
it('render cancel button', () => {
3434
render(<IntlProvider locale="en"><ReasonPane {...props} hasReason={false} /></IntlProvider>);
35-
const skipButton = screen.getByRole('button', { name: formatMessage(messages.reasonSkip) });
35+
const skipButton = screen.getByRole('button', { name: formatMessage(messages.confirmCancel) });
3636
expect(skipButton).toBeInTheDocument();
3737
});
3838
it('render submit button', () => {

src/containers/UnenrollConfirmModal/components/messages.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ const messages = defineMessages({
55
confirmHeader: {
66
id: 'learner-dash.unenrollConfirm.confirm.header',
77
description: 'Header for confirm unenroll modal',
8-
defaultMessage: 'Unenroll from course?',
8+
defaultMessage: 'Confirm Unenrollment',
9+
},
10+
confirmText: {
11+
id: 'learner-dash.unenrollConfirm.confirm.text',
12+
description: 'Text for confirm unenroll modal',
13+
defaultMessage: 'Are you sure you want to unenroll from the course {courseTitle} ?',
914
},
1015
confirmCancel: {
1116
id: 'learner-dash.unenrollConfirm.confirm.cancel',
1217
description: 'Cancel action for confirm unenroll modal',
13-
defaultMessage: 'Never mind',
18+
defaultMessage: 'Cancel',
1419
},
1520
confirmUnenroll: {
1621
id: 'learner-dash.unenrollConfirm.confirm.unenroll',
@@ -20,7 +25,7 @@ const messages = defineMessages({
2025
reasonHeading: {
2126
id: 'learner-dash.unenrollConfirm.confirm.reason.heading',
2227
description: 'Heading for unenroll reason modal',
23-
defaultMessage: `What's your main reason for unenrolling?`,
28+
defaultMessage: `Why are you unenrolling?`,
2429
},
2530
reasonSkip: {
2631
id: 'learner-dash.unenrollConfirm.confirm.reason.skip',
@@ -30,27 +35,22 @@ const messages = defineMessages({
3035
reasonSubmit: {
3136
id: 'learner-dash.unenrollConfirm.confirm.reason.submit',
3237
description: 'Submit action for unenroll reason modal',
33-
defaultMessage: 'Submit reason',
38+
defaultMessage: 'Unenroll',
3439
},
3540
finishHeading: {
3641
id: 'learner-dash.unenrollConfirm.confirm.finish.heading',
3742
description: 'Heading for unenroll finish modal',
38-
defaultMessage: 'You are unenrolled',
39-
},
40-
finishThanksText: {
41-
id: 'learner-dash.unenrollConfirm.confirm.finish.thanks-text',
42-
description: 'Thank you message on unenroll modal for providing a reason',
43-
defaultMessage: 'Thank you for sharing your reason for unenrolling. ',
43+
defaultMessage: 'Unenrollment Successful',
4444
},
4545
finishText: {
4646
id: 'learner-dash.unenrollConfirm.confirm.finish.text',
4747
description: 'Text for unenroll finish modal',
48-
defaultMessage: 'This course will be removed from your dashboard.',
48+
defaultMessage: 'You have been unenrolled from the course {courseTitle}',
4949
},
5050
finishReturn: {
5151
id: 'learner-dash.unenrollConfirm.confirm.finish.return',
5252
description: 'Return action for unenroll finish modal',
53-
defaultMessage: 'Return to dashboard',
53+
defaultMessage: 'Ok',
5454
},
5555
});
5656

src/containers/UnenrollConfirmModal/constants.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ export const reasonKeys = StrictDict({
1313
quality: 'quality',
1414
easy: 'easy',
1515
custom: 'custom',
16+
preferNotToSay: 'prefer-not-to-say',
1617
});
1718

1819
export const order = [
1920
reasonKeys.prereqs,
2021
reasonKeys.difficulty,
22+
reasonKeys.easy,
2123
reasonKeys.goals,
2224
reasonKeys.broken,
2325
reasonKeys.time,
2426
reasonKeys.browse,
2527
reasonKeys.support,
2628
reasonKeys.quality,
27-
reasonKeys.easy,
2829
];
2930

3031
const messages = defineMessages({
@@ -78,6 +79,11 @@ const messages = defineMessages({
7879
description: 'Unenroll custom reason option placeholder text',
7980
defaultMessage: 'Other',
8081
},
82+
[reasonKeys.preferNotToSay]: {
83+
id: 'learner-dash.unenrollConfirm.reasons.prefer-not-to-say',
84+
description: 'Unenroll reason option - prefer not to say',
85+
defaultMessage: 'I prefer not to say',
86+
},
8187
});
8288

8389
export default {

src/containers/UnenrollConfirmModal/hooks/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const useUnenrollData = ({ closeModal, cardId }) => {
2424

2525
let modalState;
2626
if (isConfirmed) {
27-
modalState = (reason.isSubmitted || reason.isSkipped)
27+
modalState = (reason.isSubmitted)
2828
? modalStates.finished : modalStates.reason;
2929
} else {
3030
modalState = modalStates.confirm;

src/containers/UnenrollConfirmModal/hooks/reasons.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { StrictDict } from 'utils';
99
import track from 'tracking';
1010

1111
import * as module from './reasons';
12+
import constants from '../constants';
1213

1314
export const state = StrictDict({
1415
customOption: (val) => React.useState(val), // eslint-disable-line
15-
isSkipped: (val) => React.useState(val), // eslint-disable-line
1616
selectedReason: (val) => React.useState(val), // eslint-disable-line
1717
isSubmitted: (val) => React.useState(val), //eslint-disable-line
1818
});
@@ -21,12 +21,12 @@ export const useUnenrollReasons = ({
2121
cardId,
2222
}) => {
2323
// The selected option element from the menu
24-
const [selectedReason, setSelectedReason] = module.state.selectedReason(null);
24+
const [selectedReason, setSelectedReason] = module.state.selectedReason(
25+
constants.reasonKeys.preferNotToSay,
26+
);
2527
// Custom option element entry value
2628
const [customOption, setCustomOption] = module.state.customOption('');
2729

28-
// Did the user choose to skip selecting a reason?
29-
const [isSkipped, setIsSkipped] = module.state.isSkipped(false);
3030
// Did the user submit an unenrollment reason
3131
const [isSubmitted, setIsSubmitted] = module.state.isSubmitted(false);
3232

@@ -47,15 +47,9 @@ export const useUnenrollReasons = ({
4747
const handleClear = () => {
4848
setSelectedReason(null);
4949
setCustomOption('');
50-
setIsSkipped(false);
5150
setIsSubmitted(false);
5251
};
5352

54-
const handleSkip = () => {
55-
setIsSkipped(true);
56-
unenrollFromCourse();
57-
};
58-
5953
const handleSubmit = (e) => {
6054
handleTrackReasons(e);
6155
setIsSubmitted(true);
@@ -68,10 +62,8 @@ export const useUnenrollReasons = ({
6862
return {
6963
customOption: { value: customOption, onChange: handleCustomOptionChange },
7064
handleClear,
71-
handleSkip,
7265
handleSubmit,
7366
hasReason,
74-
isSkipped,
7567
isSubmitted,
7668
selectOption: handleSelectOption,
7769
submittedReason,

0 commit comments

Comments
 (0)