diff --git a/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx b/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx index d8ada45302..9a5774a0e0 100644 --- a/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx +++ b/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx @@ -52,7 +52,6 @@ const BudgetDetailActivityTabContents = ({ enterpriseUUID, enterpriseFeatures }) && ( )} {hasSpentTransactions && } diff --git a/src/components/learner-credit-management/BudgetDetailRedemptions.jsx b/src/components/learner-credit-management/BudgetDetailRedemptions.jsx index a69e8e45e0..4a7c8a50ec 100644 --- a/src/components/learner-credit-management/BudgetDetailRedemptions.jsx +++ b/src/components/learner-credit-management/BudgetDetailRedemptions.jsx @@ -47,7 +47,7 @@ const BudgetDetailRedemptions = ({ enterpriseFeatures, enterpriseUUID }) => {

{(enterpriseOfferId || (subsidyAccessPolicyId && !enterpriseFeatures.topDownAssignmentRealTimeLcm)) ? ( diff --git a/src/components/learner-credit-management/BudgetStatusSubtitle.jsx b/src/components/learner-credit-management/BudgetStatusSubtitle.jsx index 75b0b7b238..f68c54fb84 100644 --- a/src/components/learner-credit-management/BudgetStatusSubtitle.jsx +++ b/src/components/learner-credit-management/BudgetStatusSubtitle.jsx @@ -15,6 +15,7 @@ const BudgetStatusSubtitle = ({ }) => { const { data: enterpriseGroup } = useEnterpriseGroup(policy); const { data: enterpriseCustomer } = useEnterpriseCustomer(enterpriseUUID); + // universal group = all members of the organization are automatically in a group const universalGroup = enterpriseGroup?.appliesToAllContexts; const intl = useIntl(); const budgetType = { @@ -40,24 +41,32 @@ const BudgetStatusSubtitle = ({ defaultMessage: 'Assignment', description: 'Enrollment type for budgets that are assignable', }), + }, + browseAndEnroll: { + enrollmentType: + intl.formatMessage({ + id: 'lcm.budget.detail.page.overview.enroll.browse.and.enroll', + defaultMessage: 'Browse & Enroll', + description: 'Enrollment type for budgets that are browsable and enrollable', + }), popoverText: intl.formatMessage({ - id: 'lcm.budget.detail.page.overview.enroll.assignable.popover', + id: 'lcm.budget.detail.page.overview.enroll.browse.and.enroll.popover', defaultMessage: 'Available to members added to this budget', - description: 'Popover text for budgets that are assignable', + description: 'Popover text for budgets that are browsable and enrollable', }), icon: , }, - browseAndEnroll: { + orgBrowseAndEnroll: { enrollmentType: intl.formatMessage({ - id: 'lcm.budget.detail.page.overview.enroll.browse.and.enroll', + id: 'lcm.budget.detail.page.overview.enroll.org.browse.and.enroll', defaultMessage: 'Browse & Enroll', description: 'Enrollment type for budgets that are browsable and enrollable', }), popoverText: intl.formatMessage({ - id: 'lcm.budget.detail.page.overview.enroll.browse.and.enroll.popover', + id: 'lcm.budget.detail.page.overview.enroll.org.browse.and.enroll.popover', defaultMessage: 'Available to all people in your organization', description: 'Popover text for budgets that are browsable and enrollable', }), @@ -68,6 +77,8 @@ const BudgetStatusSubtitle = ({ if (isLmsBudget(enterpriseCustomer?.activeIntegrations.length, universalGroup)) { budgetTypeToRender = budgetType.lms; + } else if (universalGroup) { + budgetTypeToRender = budgetType.orgBrowseAndEnroll; } else if (isAssignable) { budgetTypeToRender = budgetType.assignable; } else { diff --git a/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx b/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx index 94795cdbd9..5991139706 100644 --- a/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx +++ b/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx @@ -5,8 +5,12 @@ import { Button, Card, Col, Row, } from '@openedx/paragon'; import { Link } from 'react-router-dom'; - -import { useIsLargeOrGreater } from '../data'; +import { + useBudgetId, + useEnterpriseGroupLearners, + useIsLargeOrGreater, + useSubsidyAccessPolicy, +} from '../data'; import nameYourMembers from '../assets/reading.svg'; import memberBrowse from '../assets/phoneScroll.svg'; import enrollAndSpend from '../assets/wallet.svg'; @@ -23,7 +27,12 @@ const EnrollAndSpendIllustration = (props) => ( ); -const NoBnEBudgetActivity = ({ openInviteModal, isEnterpriseGroupsEnabled }) => { +const NoBnEBudgetActivity = ({ openInviteModal }) => { + const { subsidyAccessPolicyId } = useBudgetId(); + const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId); + const { data } = useEnterpriseGroupLearners(subsidyAccessPolicy?.groupAssociations[0]); + const groupMembersCount = data?.count || 0; + const isLargeOrGreater = useIsLargeOrGreater(); return ( @@ -89,7 +98,7 @@ const NoBnEBudgetActivity = ({ openInviteModal, isEnterpriseGroupsEnabled }) => as={Link} onClick={openInviteModal} > - {isEnterpriseGroupsEnabled ? 'Invite more members' : 'Get started'} + {groupMembersCount > 0 ? 'Invite more members' : 'Get started'} @@ -100,11 +109,6 @@ const NoBnEBudgetActivity = ({ openInviteModal, isEnterpriseGroupsEnabled }) => NoBnEBudgetActivity.propTypes = { openInviteModal: PropTypes.func.isRequired, - isEnterpriseGroupsEnabled: PropTypes.bool, -}; - -NoBnEBudgetActivity.defaultProps = { - isEnterpriseGroupsEnabled: false, }; export default NoBnEBudgetActivity; diff --git a/src/components/learner-credit-management/invite-modal/InviteModalBudgetCard.jsx b/src/components/learner-credit-management/invite-modal/InviteModalBudgetCard.jsx index e1e444f9e1..b19c27c8f7 100644 --- a/src/components/learner-credit-management/invite-modal/InviteModalBudgetCard.jsx +++ b/src/components/learner-credit-management/invite-modal/InviteModalBudgetCard.jsx @@ -25,7 +25,7 @@ const InviteModalBudgetCard = ({ const intl = useIntl(); const { subsidyAccessPolicyId, enterpriseOfferId } = useBudgetId(); const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId); - const { data } = useEnterpriseGroupLearners(subsidyAccessPolicy.groupAssociations[0]); + const { data } = useEnterpriseGroupLearners(subsidyAccessPolicy?.groupAssociations[0]); const memberSubtitle = data?.count ? `${makePlural(data?.count, 'current member')}` : ''; const budgetType = (enterpriseOfferId !== null) ? BUDGET_TYPES.ecommerce : BUDGET_TYPES.policy; diff --git a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx index 913ca7ab58..46137ee5f3 100644 --- a/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx +++ b/src/components/learner-credit-management/members-tab/tests/MembersTab.test.jsx @@ -17,9 +17,9 @@ import { useBudgetDetailActivityOverview, useEnterpriseGroupLearners, useEnterpriseGroupMembersTableData, - useSubsidySummaryAnalyticsApi, useEnterpriseOffer, useEnterpriseRemovedGroupMembers, + useSubsidySummaryAnalyticsApi, } from '../../data'; import { EnterpriseSubsidiesContext } from '../../../EnterpriseSubsidiesContext'; import { diff --git a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx index 7baee2407c..a2be5ab47c 100644 --- a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx +++ b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx @@ -24,6 +24,7 @@ import { useEnterpriseCustomer, useEnterpriseGroup, useEnterpriseGroupLearners, + useEnterpriseRemovedGroupMembers, useEnterpriseOffer, useIsLargeOrGreater, useSubsidyAccessPolicy, @@ -66,6 +67,7 @@ jest.mock('../data', () => ({ useEnterpriseGroup: jest.fn(), useEnterpriseGroupLearners: jest.fn(), useEnterpriseGroupMembersTableData: jest.fn(), + useEnterpriseRemovedGroupMembers: jest.fn(), useEnterpriseOffer: jest.fn(), useIsLargeOrGreater: jest.fn().mockReturnValue(true), useSubsidyAccessPolicy: jest.fn(), @@ -453,6 +455,10 @@ describe('', () => { budgetRedemptions: mockEmptyBudgetRedemptions, fetchBudgetRedemptions: jest.fn(), }); + useEnterpriseRemovedGroupMembers.mockReturnValue({ + isRemovedMembersLoading: false, + removedGroupMembersCount: 0, + }); renderWithRouter(); if (isLoading) { @@ -648,12 +654,63 @@ describe('', () => { budgetRedemptions: mockEmptyBudgetRedemptions, fetchBudgetRedemptions: jest.fn(), }); + useEnterpriseRemovedGroupMembers.mockReturnValue({ + isRemovedMembersLoading: false, + removedGroupMembersCount: 0, + }); renderWithRouter(); // Overview empty state (no content assignments, no spent transactions) expect(screen.getByText('No budget activity yet? Invite members to browse the catalog and enroll!')).toBeInTheDocument(); const illustrationTestIds = ['name-your-members-illustration', 'members-browse-illustration', 'enroll-and-spend-illustration']; illustrationTestIds.forEach(testId => expect(screen.getByTestId(testId)).toBeInTheDocument()); + expect(screen.getByText('Get started', { selector: 'a' })).toBeInTheDocument(); + }); + + it('still render bne zero state if there are members but no spend', async () => { + useParams.mockReturnValue({ + enterpriseSlug: 'test-enterprise-slug', + enterpriseAppPage: 'test-enterprise-page', + budgetId: 'a52e6548-649f-4576-b73f-c5c2bee25e9c', + activeTabKey: 'activity', + }); + useSubsidyAccessPolicy.mockReturnValue({ + isInitialLoading: false, + data: mockPerLearnerSpendLimitSubsidyAccessPolicy, + }); + useEnterpriseGroupLearners.mockReturnValue({ + data: { + count: 1, + currentPage: 1, + next: null, + numPages: 1, + results: { + enterpriseGroupMembershipUuid: 'cde2e374-032f-4c08-8c0d-bf3205fa7c7e', + learnerId: 4382, + memberDetails: { userEmail: 'foobar@test.com', userName: 'ayy lmao' }, + }, + }, + }); + useEnterpriseRemovedGroupMembers.mockReturnValue({ + isRemovedMembersLoading: false, + removedGroupMembersCount: 0, + }); + useBudgetDetailActivityOverview.mockReturnValue({ + isLoading: false, + data: mockEmptyStateBudgetDetailActivityOverview, + }); + useBudgetRedemptions.mockReturnValue({ + isLoading: false, + budgetRedemptions: mockEmptyBudgetRedemptions, + fetchBudgetRedemptions: jest.fn(), + }); + renderWithRouter(); + + // Overview empty state (no content assignments, no spent transactions) + screen.debug(undefined, 1000000); + + expect(screen.queryByText('No budget activity yet? Invite members to browse the catalog and enroll!')).toBeInTheDocument(); + expect(screen.getByText('Invite more members', { selector: 'a' })).toBeInTheDocument(); }); @@ -1548,6 +1605,10 @@ describe('', () => { budgetRedemptions: mockEmptyBudgetRedemptions, fetchBudgetRedemptions: jest.fn(), }); + useEnterpriseRemovedGroupMembers.mockReturnValue({ + isRemovedMembersLoading: false, + removedGroupMembersCount: 0, + }); renderWithRouter(); // Catalog tab does NOT exist @@ -2228,6 +2289,10 @@ describe('', () => { }, }, }); + useEnterpriseRemovedGroupMembers.mockReturnValue({ + isRemovedMembersLoading: false, + removedGroupMembersCount: 0, + }); renderWithRouter(