From abc2c977e7793b7e78c158463fd5a91b198090b0 Mon Sep 17 00:00:00 2001 From: Kira Miller <31229189+kiram15@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:06:01 -0700 Subject: [PATCH] feat: create zero state for bnr (#1182) * feat: create zero state for bnr * fix: PR requests --- .../AssignMoreCoursesEmptyStateMinimal.jsx | 2 +- .../BudgetDetailActivityTabContents.jsx | 20 ++-- ...findTheRightCourse.svg => phoneScroll.svg} | 0 .../{nameYourLearners.svg => reading.svg} | 0 .../assets/{confirmSpend.svg => wallet.svg} | 0 .../NoAssignableBudgetActivity.jsx} | 10 +- .../empty-state/NoBnEBudgetActivity.jsx | 99 +++++++++++++++++++ .../tests/BudgetDetailPage.test.jsx | 35 ++++++- 8 files changed, 151 insertions(+), 15 deletions(-) rename src/components/learner-credit-management/assets/{findTheRightCourse.svg => phoneScroll.svg} (100%) rename src/components/learner-credit-management/assets/{nameYourLearners.svg => reading.svg} (100%) rename src/components/learner-credit-management/assets/{confirmSpend.svg => wallet.svg} (100%) rename src/components/learner-credit-management/{NoBudgetActivityEmptyState.jsx => empty-state/NoAssignableBudgetActivity.jsx} (92%) create mode 100644 src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx diff --git a/src/components/learner-credit-management/AssignMoreCoursesEmptyStateMinimal.jsx b/src/components/learner-credit-management/AssignMoreCoursesEmptyStateMinimal.jsx index d51595c5e9..25d1a02a6b 100644 --- a/src/components/learner-credit-management/AssignMoreCoursesEmptyStateMinimal.jsx +++ b/src/components/learner-credit-management/AssignMoreCoursesEmptyStateMinimal.jsx @@ -5,7 +5,7 @@ import { Button, Card } from '@edx/paragon'; import { formatDate, formatPrice, useBudgetId, usePathToCatalogTab, useSubsidyAccessPolicy, } from './data'; -import nameYourLearner from './assets/nameYourLearners.svg'; +import nameYourLearner from './assets/reading.svg'; const AssignMoreCoursesEmptyStateMinimal = () => { const { subsidyAccessPolicyId } = useBudgetId(); diff --git a/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx b/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx index 21e65d2de1..9720c607a9 100644 --- a/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx +++ b/src/components/learner-credit-management/BudgetDetailActivityTabContents.jsx @@ -6,7 +6,8 @@ import { Stack, Skeleton } from '@edx/paragon'; import BudgetDetailRedemptions from './BudgetDetailRedemptions'; import BudgetDetailAssignments from './BudgetDetailAssignments'; import { useBudgetDetailActivityOverview, useBudgetId, useSubsidyAccessPolicy } from './data'; -import NoBudgetActivityEmptyState from './NoBudgetActivityEmptyState'; +import NoAssignableBudgetActivity from './empty-state/NoAssignableBudgetActivity'; +import NoBnEBudgetActivity from './empty-state/NoBnEBudgetActivity'; const BudgetDetailActivityTabContents = ({ enterpriseUUID, enterpriseFeatures }) => { const isTopDownAssignmentEnabled = enterpriseFeatures.topDownAssignmentRealTimeLcm; @@ -32,19 +33,22 @@ const BudgetDetailActivityTabContents = ({ enterpriseUUID, enterpriseFeatures }) ); } + const hasSpentTransactions = budgetActivityOverview.spentTransactions?.count > 0; + const hasContentAssignments = budgetActivityOverview.contentAssignments?.count > 0; + if (!isTopDownAssignmentEnabled || !subsidyAccessPolicy?.isAssignable) { - return ; + return ( + <> + {!hasSpentTransactions && ()} + + + ); } - const hasContentAssignments = !!budgetActivityOverview.contentAssignments?.count > 0; - const hasSpentTransactions = !!budgetActivityOverview.spentTransactions?.count > 0; - // If there is no activity whatsoever (no assignments, no spent transactions), show the // full empty state. if (!hasContentAssignments && !hasSpentTransactions) { - return ( - - ); + return ; } // Otherwise, render the contents of the "Activity" tab. diff --git a/src/components/learner-credit-management/assets/findTheRightCourse.svg b/src/components/learner-credit-management/assets/phoneScroll.svg similarity index 100% rename from src/components/learner-credit-management/assets/findTheRightCourse.svg rename to src/components/learner-credit-management/assets/phoneScroll.svg diff --git a/src/components/learner-credit-management/assets/nameYourLearners.svg b/src/components/learner-credit-management/assets/reading.svg similarity index 100% rename from src/components/learner-credit-management/assets/nameYourLearners.svg rename to src/components/learner-credit-management/assets/reading.svg diff --git a/src/components/learner-credit-management/assets/confirmSpend.svg b/src/components/learner-credit-management/assets/wallet.svg similarity index 100% rename from src/components/learner-credit-management/assets/confirmSpend.svg rename to src/components/learner-credit-management/assets/wallet.svg diff --git a/src/components/learner-credit-management/NoBudgetActivityEmptyState.jsx b/src/components/learner-credit-management/empty-state/NoAssignableBudgetActivity.jsx similarity index 92% rename from src/components/learner-credit-management/NoBudgetActivityEmptyState.jsx rename to src/components/learner-credit-management/empty-state/NoAssignableBudgetActivity.jsx index 53559092e1..a59c245f88 100644 --- a/src/components/learner-credit-management/NoBudgetActivityEmptyState.jsx +++ b/src/components/learner-credit-management/empty-state/NoAssignableBudgetActivity.jsx @@ -8,11 +8,11 @@ import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; -import { useIsLargeOrGreater, usePathToCatalogTab } from './data'; -import nameYourLearners from './assets/nameYourLearners.svg'; -import findTheRightCourse from './assets/findTheRightCourse.svg'; -import confirmSpend from './assets/confirmSpend.svg'; -import EVENT_NAMES from '../../eventTracking'; +import { useIsLargeOrGreater, usePathToCatalogTab } from '../data'; +import findTheRightCourse from '../assets/phoneScroll.svg'; +import nameYourLearners from '../assets/reading.svg'; +import confirmSpend from '../assets/wallet.svg'; +import EVENT_NAMES from '../../../eventTracking'; const FindTheRightCourseIllustration = (props) => ( diff --git a/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx b/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx new file mode 100644 index 0000000000..ecfd3f3c06 --- /dev/null +++ b/src/components/learner-credit-management/empty-state/NoBnEBudgetActivity.jsx @@ -0,0 +1,99 @@ +import React from 'react'; +import classNames from 'classnames'; +import { + Button, Card, Row, Col, +} from '@edx/paragon'; +import { Link } from 'react-router-dom'; + +import { useIsLargeOrGreater } from '../data'; +import nameYourMembers from '../assets/reading.svg'; +import memberBrowse from '../assets/phoneScroll.svg'; +import enrollAndSpend from '../assets/wallet.svg'; + +const NameYourMembersIllustration = (props) => ( + +); + +const MemberBrowseIllustration = (props) => ( + +); + +const EnrollAndSpendIllustration = (props) => ( + +); + +const NoBnEBudgetActivity = () => { + const isLargeOrGreater = useIsLargeOrGreater(); + + return ( + + +

+ No budget activity yet? Invite members to browse the catalog and enroll! +

+ {isLargeOrGreater && ( + + + + + + + + + + + + )} +
+ + + + {!isLargeOrGreater && } +

+ 01 + Name your members +

+ + Upload or enter email addresses to invite people to browse and enroll + using this budget. + + + + {!isLargeOrGreater && } +

+ 02 + Members find the right course +

+ + Members can then browse the catalog associated with this budget and + find a course that aligns with their interests. + + + + {!isLargeOrGreater && } +

+ 03 + Members can enroll and spend +

+ + Members can enroll in courses, subject to any limits in this budget's + settings. The deducted costs from this budget will be visible right here + in your budget activity! + + +
+ + + + + +
+
+ ); +}; + +export default NoBnEBudgetActivity; diff --git a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx index f05394b572..07423a77d2 100644 --- a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx +++ b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx @@ -445,7 +445,7 @@ describe('', () => { it.each([ { isLargeViewport: true }, { isLargeViewport: false }, - ])('displays budget activity overview empty state', async ({ isLargeViewport }) => { + ])('displays assignable budget activity overview empty state', async ({ isLargeViewport }) => { useIsLargeOrGreater.mockReturnValue(isLargeViewport); useParams.mockReturnValue({ enterpriseSlug: 'test-enterprise-slug', @@ -472,6 +472,39 @@ describe('', () => { await waitFor(() => expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1)); }); + it.each([ + { isLargeViewport: true }, + { isLargeViewport: false }, + ])('displays bnr budget activity overview empty state', async ({ isLargeViewport }) => { + useIsLargeOrGreater.mockReturnValue(isLargeViewport); + useParams.mockReturnValue({ + enterpriseSlug: 'test-enterprise-slug', + enterpriseAppPage: 'test-enterprise-page', + budgetId: 'a52e6548-649f-4576-b73f-c5c2bee25e9c', + activeTabKey: 'activity', + }); + useSubsidyAccessPolicy.mockReturnValue({ + isInitialLoading: false, + data: mockPerLearnerSpendLimitSubsidyAccessPolicy, + }); + 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) + 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.each([ { budgetId: mockEnterpriseOfferId,