Skip to content

Commit 299f30f

Browse files
feat(Browse&Request): update decline modal UX and add decline reason … (#1728)
* feat(Browse&Request): update decline modal UX and add decline reason field * test(Browse&Request): added an unit test to verify decline reason text area input in decline modal
1 parent d933fba commit 299f30f

File tree

4 files changed

+42
-62
lines changed

4 files changed

+42
-62
lines changed

src/components/learner-credit-management/requests-tab/DeclineBnrSubsidyRequestModal.jsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ const DeclineBnrSubsidyRequestModal = ({
1616
onSuccess,
1717
onClose,
1818
}) => {
19-
const [shouldNotifyLearner, setShouldNotifyLearner] = useState(true);
20-
const [shouldUnlinkLearnerFromEnterprise, setShouldUnlinkLearnerFromEnterprise] = useState(false);
19+
const [declineReason, setDeclineReason] = useState('');
2120
const [isLoading, setIsLoading] = useState(false);
2221
const [error, setError] = useState();
2322

@@ -40,8 +39,8 @@ const DeclineBnrSubsidyRequestModal = ({
4039
await declineRequestFn({
4140
enterpriseId,
4241
subsidyRequestUUID: uuid,
43-
sendNotification: shouldNotifyLearner,
44-
unlinkUsersFromEnterprise: shouldUnlinkLearnerFromEnterprise,
42+
sendNotification: true, // always true as it would be by default
43+
declineReason, // include the reason field
4544
});
4645
onSuccess();
4746
} catch (err) {
@@ -51,8 +50,7 @@ const DeclineBnrSubsidyRequestModal = ({
5150
}
5251
}, [
5352
uuid,
54-
shouldNotifyLearner,
55-
shouldUnlinkLearnerFromEnterprise,
53+
declineReason,
5654
declineRequestFn,
5755
onSuccess,
5856
enterpriseId,
@@ -87,23 +85,22 @@ const DeclineBnrSubsidyRequestModal = ({
8785
Declining an enrollment request cannot be undone. If you change your mind, the learner will have to
8886
submit a new enrollment request.
8987
</p>
90-
<Form.Checkbox
91-
className="py-3"
92-
data-testid="decline-bnr-subsidy-request-modal-notify-learner-checkbox"
93-
checked={shouldNotifyLearner}
94-
onChange={(e) => setShouldNotifyLearner(e.target.checked)}
95-
>
96-
Send the learner an email notification
97-
</Form.Checkbox>
98-
<Form.Checkbox
99-
className="mt-1"
100-
data-testid="decline-subsidy-request-modal-unlink-learner-checkbox"
101-
checked={shouldUnlinkLearnerFromEnterprise}
102-
onChange={(e) => setShouldUnlinkLearnerFromEnterprise(e.target.checked)}
103-
description="Your learner won't know they have been disassociated."
104-
>
105-
Disassociate the learner with your organization
106-
</Form.Checkbox>
88+
{/* Reason input with character count + 250 limit */}
89+
<Form.Group className="mt-3">
90+
<Form.Control
91+
as="textarea"
92+
rows={2}
93+
maxLength={250} // Max Character limit
94+
placeholder="Reason for declining"
95+
data-testid="decline-subsidy-request-reason-input"
96+
value={declineReason}
97+
onChange={(e) => setDeclineReason(e.target.value)}
98+
/>
99+
<div className="text-right text-muted mt-1" style={{ fontSize: '0.85rem' }}>
100+
{declineReason.length}/250 characters
101+
</div>
102+
</Form.Group>
103+
107104
</ModalDialog.Body>
108105
<ModalDialog.Footer>
109106
<ActionRow>

src/components/learner-credit-management/tests/BudgetDetailRequestsTabContent.test.jsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,21 @@ describe('BudgetDetailRequestsTabContent', () => {
343343
expect(screen.getByText(/Declining an enrollment request cannot be undone/)).toBeInTheDocument();
344344
expect(screen.getByTestId('decline-subsidy-request-modal-decline-btn')).toBeInTheDocument();
345345
expect(screen.getByTestId('decline-subsidy-request-modal-close-btn')).toBeInTheDocument();
346-
expect(screen.getByTestId('decline-bnr-subsidy-request-modal-notify-learner-checkbox')).toBeInTheDocument();
347-
expect(screen.getByTestId('decline-subsidy-request-modal-unlink-learner-checkbox')).toBeInTheDocument();
348346
});
349347
});
348+
it('should allow the user to enter a decline reason in the modal', async () => {
349+
const user = userEvent.setup();
350+
render(<BudgetDetailRequestsTabContentWrapper />);
351+
await waitFor(() => {
352+
expect(screen.getByRole('table')).toBeInTheDocument();
353+
});
354+
const declineButton = screen.getByRole('button', { name: /decline/i });
355+
await user.click(declineButton);
356+
const reasonInput = await screen.findByTestId('decline-subsidy-request-reason-input');
357+
await user.type(reasonInput, 'This course is not approved.');
358+
expect(reasonInput).toHaveValue('This course is not approved.');
359+
expect(screen.getByText(/28\/250/i)).toBeInTheDocument();
360+
});
350361

351362
it('should call decline API with default options and refresh requests on successful decline', async () => {
352363
const user = userEvent.setup();
@@ -381,14 +392,14 @@ describe('BudgetDetailRequestsTabContent', () => {
381392
enterpriseId: 'test-enterprise-id',
382393
subsidyRequestUUID: 'request-1',
383394
sendNotification: true,
384-
unlinkUsersFromEnterprise: false,
395+
declineReason: '',
385396
});
386397
expect(mockRefreshRequests).toHaveBeenCalledTimes(1);
387398
expect(screen.queryByText('Are you sure?')).not.toBeInTheDocument();
388399
});
389400
});
390401

391-
it('should call decline API with custom options when checkboxes are modified', async () => {
402+
it('should call decline API with default options', async () => {
392403
const user = userEvent.setup();
393404
const mockDeclineRequest = jest.fn().mockResolvedValue({});
394405

@@ -403,25 +414,15 @@ describe('BudgetDetailRequestsTabContent', () => {
403414
const declineButton = screen.getByRole('button', { name: /decline/i });
404415
await user.click(declineButton);
405416

406-
await waitFor(() => {
407-
expect(screen.getByTestId('decline-bnr-subsidy-request-modal-notify-learner-checkbox')).toBeInTheDocument();
408-
});
409-
410-
const notifyCheckbox = screen.getByTestId('decline-bnr-subsidy-request-modal-notify-learner-checkbox');
411-
const unlinkCheckbox = screen.getByTestId('decline-subsidy-request-modal-unlink-learner-checkbox');
412-
413-
await user.click(notifyCheckbox);
414-
await user.click(unlinkCheckbox);
415-
416417
const confirmButton = screen.getByTestId('decline-subsidy-request-modal-decline-btn');
417418
await user.click(confirmButton);
418419

419420
await waitFor(() => {
420421
expect(mockDeclineRequest).toHaveBeenCalledWith({
421422
enterpriseId: 'test-enterprise-id',
422423
subsidyRequestUUID: 'request-1',
423-
sendNotification: false,
424-
unlinkUsersFromEnterprise: true,
424+
sendNotification: true, // always true now as it would be sent by default
425+
declineReason: '', // default empty
425426
});
426427
});
427428
});

src/data/services/EnterpriseAccessApiService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,20 +336,20 @@ class EnterpriseAccessApiService {
336336
* @param params.enterpriseId - The UUID of the enterprise customer
337337
* @param params.subsidyRequestUUID - The UUID of the subsidy request to decline
338338
* @param params.sendNotification - Whether to send a notification about the decline
339-
* @param params.unlinkUsersFromEnterprise - Whether to disassociate users from the organization
339+
* @param params.declineReason - The reason for declining
340340
* @returns A promise that resolves to the API response for the decline operation
341341
*/
342342
static declineBnrSubsidyRequest({
343343
enterpriseId,
344344
subsidyRequestUUID,
345345
sendNotification,
346-
unlinkUsersFromEnterprise,
346+
declineReason,
347347
}) {
348348
const options = {
349349
subsidy_request_uuid: subsidyRequestUUID,
350350
enterprise_customer_uuid: enterpriseId,
351351
send_notification: sendNotification,
352-
disassociate_from_org: unlinkUsersFromEnterprise,
352+
decline_reason: declineReason,
353353
};
354354

355355
const url = `${EnterpriseAccessApiService.baseUrl}/learner-credit-requests/decline/`;

src/data/services/tests/EnterpriseAccessApiService.test.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -322,32 +322,14 @@ describe('EnterpriseAccessApiService', () => {
322322
enterpriseId: mockEnterpriseUUID,
323323
subsidyRequestUUID: mockBnrSubsidyRequestUUID,
324324
sendNotification: true,
325-
unlinkUsersFromEnterprise: false,
325+
declineReason: '',
326326
});
327327

328328
expect(axios.post).toBeCalledWith(`${enterpriseAccessBaseUrl}/api/v1/learner-credit-requests/decline/`, {
329329
subsidy_request_uuid: mockBnrSubsidyRequestUUID,
330330
enterprise_customer_uuid: mockEnterpriseUUID,
331331
send_notification: true,
332-
disassociate_from_org: false,
333-
});
334-
});
335-
336-
test('declineBnrSubsidyRequest calls enterprise-access with unlinkUsersFromEnterprise set to true', () => {
337-
const mockBnrSubsidyRequestUUID = 'test-bnr-subsidy-request-uuid';
338-
339-
EnterpriseAccessApiService.declineBnrSubsidyRequest({
340-
enterpriseId: mockEnterpriseUUID,
341-
subsidyRequestUUID: mockBnrSubsidyRequestUUID,
342-
sendNotification: false,
343-
unlinkUsersFromEnterprise: true,
344-
});
345-
346-
expect(axios.post).toBeCalledWith(`${enterpriseAccessBaseUrl}/api/v1/learner-credit-requests/decline/`, {
347-
subsidy_request_uuid: mockBnrSubsidyRequestUUID,
348-
enterprise_customer_uuid: mockEnterpriseUUID,
349-
send_notification: false,
350-
disassociate_from_org: true,
332+
decline_reason: '',
351333
});
352334
});
353335

0 commit comments

Comments
 (0)