From ce99f930f010e98c04e7ff9c1763c166eda7d79c Mon Sep 17 00:00:00 2001 From: Kira Miller Date: Tue, 24 Sep 2024 21:18:41 +0000 Subject: [PATCH 1/5] fix: cleanup surrounding status chip implementation --- .../FailedBadEmail.jsx | 4 +- .../FailedCancellation.jsx | 4 +- .../FailedRedemption.jsx | 4 +- .../FailedReminder.jsx | 4 +- .../assignments-status-chips/FailedSystem.jsx | 4 +- .../IncompleteAssignment.jsx | 4 +- .../NotifyingLearner.jsx | 4 +- .../WaitingForLearner.jsx | 4 +- .../data/hooks/index.js | 2 +- ...gnmentStatusChip.jsx => useStatusChip.jsx} | 2 +- .../LearnerCreditGroupMembersTable.jsx | 176 +++++++------- .../members-tab/MemberStatusTableCell.jsx | 216 ++---------------- .../members-tab/status-chips/Accepted.jsx | 36 +++ .../status-chips/BaseStatusChip.jsx | 97 ++++++++ .../status-chips/FailedBadEmail.jsx | 60 +++++ .../members-tab/status-chips/FailedSystem.jsx | 48 ++++ .../members-tab/status-chips/Pending.jsx | 60 +++++ .../members-tab/status-chips/Removed.jsx | 60 +++++ src/eventTracking.js | 12 + 19 files changed, 504 insertions(+), 297 deletions(-) rename src/components/learner-credit-management/data/hooks/{useAssignmentStatusChip.jsx => useStatusChip.jsx} (90%) create mode 100644 src/components/learner-credit-management/members-tab/status-chips/Accepted.jsx create mode 100644 src/components/learner-credit-management/members-tab/status-chips/BaseStatusChip.jsx create mode 100644 src/components/learner-credit-management/members-tab/status-chips/FailedBadEmail.jsx create mode 100644 src/components/learner-credit-management/members-tab/status-chips/FailedSystem.jsx create mode 100644 src/components/learner-credit-management/members-tab/status-chips/Pending.jsx create mode 100644 src/components/learner-credit-management/members-tab/status-chips/Removed.jsx diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx index fb19624bcc..213f7ed135 100644 --- a/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/FailedBadEmail.jsx @@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const FailedBadEmail = ({ learnerEmail, trackEvent }) => { const [target, setTarget] = useState(null); @@ -20,7 +20,7 @@ const FailedBadEmail = ({ learnerEmail, trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_EMAIL_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx index 1f08de7383..b770ae82bc 100644 --- a/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/FailedCancellation.jsx @@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const FailedCancellation = ({ trackEvent }) => { const [target, setTarget] = useState(null); @@ -20,7 +20,7 @@ const FailedCancellation = ({ trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_CANCELLATION_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx index aee3d169a0..14ac6e5475 100644 --- a/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/FailedRedemption.jsx @@ -7,7 +7,7 @@ import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const FailedRedemption = ({ trackEvent }) => { const [target, setTarget] = useState(null); @@ -22,7 +22,7 @@ const FailedRedemption = ({ trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REDEMPTION_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx index 5860ce1a60..a527ebccfc 100644 --- a/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/FailedReminder.jsx @@ -5,7 +5,7 @@ import { Error } from '@openedx/paragon/icons'; import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const FailedReminder = ({ trackEvent }) => { const [target, setTarget] = useState(null); @@ -19,7 +19,7 @@ const FailedReminder = ({ trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_REMINDER_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx b/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx index 5fc3f79033..fbb878ddf1 100644 --- a/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/FailedSystem.jsx @@ -7,7 +7,7 @@ import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const FailedSystem = ({ trackEvent }) => { const [target, setTarget] = useState(null); @@ -21,7 +21,7 @@ const FailedSystem = ({ trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_FAILED_SYSTEM_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/IncompleteAssignment.jsx b/src/components/learner-credit-management/assignments-status-chips/IncompleteAssignment.jsx index ab43b22a9d..5bb5a00be9 100644 --- a/src/components/learner-credit-management/assignments-status-chips/IncompleteAssignment.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/IncompleteAssignment.jsx @@ -5,7 +5,7 @@ import { Error } from '@openedx/paragon/icons'; import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; import EVENT_NAMES from '../../../eventTracking'; import { configuration } from '../../../config'; @@ -21,7 +21,7 @@ const IncompleteAssignment = ({ trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_INCOMPLETE_ASSIGNMENT, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_INCOMPLETE_ASSIGNMENT_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx b/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx index 2c82bce6f6..446a2ff483 100644 --- a/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/NotifyingLearner.jsx @@ -4,7 +4,7 @@ import { Chip } from '@openedx/paragon'; import { Send } from '@openedx/paragon/icons'; import BaseModalPopup from './BaseModalPopup'; import EVENT_NAMES from '../../../eventTracking'; -import { useAssignmentStatusChip } from '../data'; +import { useStatusChip } from '../data'; const NotifyingLearner = ({ learnerEmail, trackEvent }) => { const [target, setTarget] = useState(null); @@ -14,7 +14,7 @@ const NotifyingLearner = ({ learnerEmail, trackEvent }) => { openChipModal, closeChipModal, isChipModalOpen, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_NOTIFY_LEARNER, trackEvent, }); diff --git a/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx b/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx index 335d1c5b1f..8f03354279 100644 --- a/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx +++ b/src/components/learner-credit-management/assignments-status-chips/WaitingForLearner.jsx @@ -5,7 +5,7 @@ import { Timelapse } from '@openedx/paragon/icons'; import { getConfig } from '@edx/frontend-platform/config'; import BaseModalPopup from './BaseModalPopup'; -import { ASSIGNMENT_ENROLLMENT_DEADLINE, useAssignmentStatusChip } from '../data'; +import { ASSIGNMENT_ENROLLMENT_DEADLINE, useStatusChip } from '../data'; import EVENT_NAMES from '../../../eventTracking'; const WaitingForLearner = ({ learnerEmail, trackEvent }) => { @@ -20,7 +20,7 @@ const WaitingForLearner = ({ learnerEmail, trackEvent }) => { closeChipModal, isChipModalOpen, helpCenterTrackEvent, - } = useAssignmentStatusChip({ + } = useStatusChip({ chipInteractionEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER, chipHelpCenterEventName: BUDGET_DETAILS_ASSIGNED_DATATABLE_CHIP_WAITING_FOR_LEARNER_HELP_CENTER, trackEvent, diff --git a/src/components/learner-credit-management/data/hooks/index.js b/src/components/learner-credit-management/data/hooks/index.js index 2736e91111..2687a41a63 100644 --- a/src/components/learner-credit-management/data/hooks/index.js +++ b/src/components/learner-credit-management/data/hooks/index.js @@ -12,7 +12,7 @@ export { default as useSuccessfulAssignmentToastContextValue } from './useSucces export { default as useSuccessfulCancellationToastContextValue } from './useSuccessfulCancellationToastContextValue'; export { default as useSuccessfulReminderToastContextValue } from './useSuccessfulReminderToastContextValue'; export { default as useEnterpriseOffer } from './useEnterpriseOffer'; -export { default as useAssignmentStatusChip } from './useAssignmentStatusChip'; +export { default as useStatusChip } from './useStatusChip'; export { default as useEnterpriseGroupLearners } from './useEnterpriseGroupLearners'; export { default as useEnterpriseGroupMembersTableData } from './useEnterpriseGroupMembersTableData'; export { default as useEnterpriseCustomer } from './useEnterpriseCustomer'; diff --git a/src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx b/src/components/learner-credit-management/data/hooks/useStatusChip.jsx similarity index 90% rename from src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx rename to src/components/learner-credit-management/data/hooks/useStatusChip.jsx index 11281388a3..1b05b6d3c9 100644 --- a/src/components/learner-credit-management/data/hooks/useAssignmentStatusChip.jsx +++ b/src/components/learner-credit-management/data/hooks/useStatusChip.jsx @@ -13,7 +13,7 @@ import { useToggle } from '@openedx/paragon'; * isChipModalOpen: * * }} */ -export default function useAssignmentStatusChip({ chipInteractionEventName, chipHelpCenterEventName, trackEvent }) { +export default function useStatusChip({ chipInteractionEventName, chipHelpCenterEventName, trackEvent }) { const [isChipModalOpen, open, close] = useToggle(false); const openChipModal = () => { open(); diff --git a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx index 551a6f171b..2c05624447 100644 --- a/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx +++ b/src/components/learner-credit-management/members-tab/LearnerCreditGroupMembersTable.jsx @@ -83,93 +83,95 @@ const LearnerCreditGroupMembersTable = ({ }) => { const intl = useIntl(); return ( - ]} - columns={[ - { - Header: intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.columns.memberDetails', - defaultMessage: 'Member Details', - description: 'Column header for the Member Details column in the Members tab of the Budget Detail page', - }), - accessor: 'memberDetails', - Cell: MemberDetailsTableCell, - }, - { - Header: MemberStatusTableColumnHeader, - accessor: 'status', - Cell: MemberStatusTableCell, - Filter: removedGroupMembersCount > 0 ? ( - - ) :
, - filter: 'status', - }, - { - Header: intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.columns.recentAction', - defaultMessage: 'Recent action', - description: 'Column header for the Recent action column in the Members tab of the Budget Detail page', - }), - accessor: 'recentAction', - Cell: ({ row }) => row.original.recentAction, - disableFilters: true, - }, - { - Header: MemberEnrollmentsTableColumnHeader, - accessor: 'enrollmentCount', - Cell: ({ row }) => row.original.enrollmentCount, - disableFilters: true, - }, - ]} - initialTableOptions={{ - getRowId: row => row?.memberDetails.userEmail, - autoResetPage: true, - }} - initialState={{ - pageSize: MEMBERS_TABLE_PAGE_SIZE, - pageIndex: DEFAULT_PAGE, - sortBy: [ - { id: 'memberDetails', desc: true }, - ], - filters: [], - }} - bulkActions={[ - , - , - ]} - additionalColumns={[ - { - id: 'action', - Header: '', - // eslint-disable-next-line react/no-unstable-nested-components - Cell: (props) => ( - - ), - }, - ]} - fetchData={fetchTableData} - data={tableData.results} - itemCount={tableData.itemCount} - pageCount={tableData.pageCount} - EmptyTableComponent={CustomDataTableEmptyState} - /> + + ]} + columns={[ + { + Header: intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.columns.memberDetails', + defaultMessage: 'Member Details', + description: 'Column header for the Member Details column in the Members tab of the Budget Detail page', + }), + accessor: 'memberDetails', + Cell: MemberDetailsTableCell, + }, + { + Header: MemberStatusTableColumnHeader, + accessor: 'status', + Cell: MemberStatusTableCell, + Filter: removedGroupMembersCount > 0 ? ( + + ) :
, + filter: 'status', + }, + { + Header: intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.columns.recentAction', + defaultMessage: 'Recent action', + description: 'Column header for the Recent action column in the Members tab of the Budget Detail page', + }), + accessor: 'recentAction', + Cell: ({ row }) => row.original.recentAction, + disableFilters: true, + }, + { + Header: MemberEnrollmentsTableColumnHeader, + accessor: 'enrollmentCount', + Cell: ({ row }) => row.original.enrollmentCount, + disableFilters: true, + }, + ]} + initialTableOptions={{ + getRowId: row => row?.memberDetails.userEmail, + autoResetPage: true, + }} + initialState={{ + pageSize: MEMBERS_TABLE_PAGE_SIZE, + pageIndex: DEFAULT_PAGE, + sortBy: [ + { id: 'memberDetails', desc: true }, + ], + filters: [], + }} + bulkActions={[ + , + , + ]} + additionalColumns={[ + { + id: 'action', + Header: '', + // eslint-disable-next-line react/no-unstable-nested-components + Cell: (props) => ( + + ), + }, + ]} + fetchData={fetchTableData} + data={tableData.results} + itemCount={tableData.itemCount} + pageCount={tableData.pageCount} + EmptyTableComponent={CustomDataTableEmptyState} + /> + ); }; diff --git a/src/components/learner-credit-management/members-tab/MemberStatusTableCell.jsx b/src/components/learner-credit-management/members-tab/MemberStatusTableCell.jsx index 7fc1f7ef66..94c95e440c 100644 --- a/src/components/learner-credit-management/members-tab/MemberStatusTableCell.jsx +++ b/src/components/learner-credit-management/members-tab/MemberStatusTableCell.jsx @@ -1,207 +1,39 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Chip, Icon, Hyperlink, OverlayTrigger, Popover, -} from '@openedx/paragon'; -import { - CheckCircle, Error, RemoveCircle, Timelapse, -} from '@openedx/paragon/icons'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; -import { HELP_CENTER_GROUPS_INVITE_LINK } from '../../settings/data/constants'; -const MemberStatusTableCell = ({ - row, -}) => { - const intl = useIntl(); - let icon; - let text; - let popoverHeader; - let popoverBody; - let popoverExtra1; - let popoverExtra2; +import Accepted from './status-chips/Accepted'; +import FailedSystem from './status-chips/FailedSystem'; +import FailedBadEmail from './status-chips/FailedBadEmail'; +import Pending from './status-chips/Pending'; +import Removed from './status-chips/Removed'; + +const MemberStatusTableCell = ({ row }) => { if (row.original.status === 'pending') { - icon = Timelapse; - text = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pending', - defaultMessage: 'Waiting for member', - description: 'Status of the member invitation', - }); - popoverHeader = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverHeader', - defaultMessage: 'Waiting for {userEmail}', - description: 'Popover header for the pending status', - }, { userEmail: row.original.memberDetails.userEmail }); - popoverBody = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverBody', - defaultMessage: "This member must accept their invitation to browse this budget's catalog and enroll using their member permissions by logging in or creating an account within 90 days.", - description: 'Popover body for the pending status', - }); - popoverExtra1 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverExtra1', - defaultMessage: 'Need help?', - description: 'Extra text for the pending status', - }); - popoverExtra2 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverExtra2', - defaultMessage: 'Learn more about adding budget members in Learner Credit at ', - description: 'Extra text for the pending status', - }); - } else if (row.original.status === 'accepted') { - icon = CheckCircle; - text = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.accepted', - defaultMessage: 'Accepted', - description: 'Status of the member invitation', - }); - popoverHeader = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.acceptedPopoverHeader', - defaultMessage: 'Invitation accepted', - description: 'Popover header for the accepted status', - }); - popoverBody = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.acceptedPopoverBody', - defaultMessage: "This member has successfully accepted the member invitation and can now browse this budget's catalog and enroll using their member permissions.", - description: 'Popover body for the accepted status', - }); - } else if (row.original.status === 'internal_api_error') { - icon = Error; - text = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystem', - defaultMessage: 'Failed: System', - description: 'Status of the member invitation', - }); - popoverHeader = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverHeader', - defaultMessage: 'Failed: System', - description: 'Popover header for the system failed status', - }); - popoverBody = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverBody', - defaultMessage: 'Something went wrong behind the scenes.', - description: 'Popover body for the system failed status', - }); - popoverExtra1 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverExtra1', - defaultMessage: 'Need help?', - description: 'Extra text for the system failed status', - }); - popoverExtra2 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverExtra2', - defaultMessage: 'Get help at ', - description: 'Extra text for the system failed status', - }); - } else if (row.original.status === 'email_error') { - icon = Error; - text = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmail', - defaultMessage: 'Failed: Bad email', - description: 'Status of the member invitation', - }); - popoverHeader = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverHeader', - defaultMessage: 'Failed: Bad email', - description: 'Popover header for the failed email status', - }); - popoverBody = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverBody', - defaultMessage: 'This member invitation failed because a notification to {userEmail} could not be sent.', - description: 'Popover body for the failed email status', - }, { userEmail: row.original.memberDetails.userEmail }); - popoverExtra1 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverExtra1', - defaultMessage: 'Resolution steps', - description: 'Extra text for the failed email status', - }); - popoverExtra2 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverExtra2', - defaultMessage: 'Remove member from budget, ensure email is correct and re-invite. Get more troubleshooting help at ', - description: 'Extra text for the failed email status', - }); - } else { - icon = RemoveCircle; - text = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removed', - defaultMessage: 'Removed', - description: 'Status of the member invitation', - }); - popoverHeader = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverHeader', - defaultMessage: 'Member removed', - description: 'Popover header for the removed status', - }); - popoverBody = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverBody', - defaultMessage: "This member has been successfully removed and can not browse this budget's catalog and enroll using their member permissions.", - description: 'Popover body for the removed status', - }); - popoverExtra1 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverExtra1', - defaultMessage: 'Want to add them back?', - description: 'Extra text for the removed status', - }); - popoverExtra2 = intl.formatMessage({ - id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverExtra2', - defaultMessage: 'Follow the steps provided at ', - description: 'Extra text for the removed status', - }); + return ( + + ); + } if (row.original.status === 'accepted') { + return ( + + ); + } if (row.original.status === 'internal_api_error') { + return ( + + ); + } if (row.original.status === 'email_error') { + return ( + + ); } return ( - - - - {popoverHeader} - - - {popoverBody} - {popoverExtra1 !== null && ( -
-

{popoverExtra1}

-

{popoverExtra2} - ( - - {chunks} - - ), - }} - /> -

-
- )} -
- - )} - > - - {text} - -
+ ); }; MemberStatusTableCell.propTypes = { row: PropTypes.shape({ original: PropTypes.shape({ - memberDetails: PropTypes.shape({ - userEmail: PropTypes.string.isRequired, - userName: PropTypes.string, - }), - status: PropTypes.string, - recentAction: PropTypes.string.isRequired, - memberEnrollments: PropTypes.string, + status: PropTypes.string.isRequired, }).isRequired, }).isRequired, }; diff --git a/src/components/learner-credit-management/members-tab/status-chips/Accepted.jsx b/src/components/learner-credit-management/members-tab/status-chips/Accepted.jsx new file mode 100644 index 0000000000..f52a004856 --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/Accepted.jsx @@ -0,0 +1,36 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import { CheckCircle } from '@openedx/paragon/icons'; +import BaseStatusChip from './BaseStatusChip'; +import { LEARNER_CREDIT_MANAGEMENT_EVENTS as events } from '../../../../eventTracking'; + +const Accepted = () => { + const intl = useIntl(); + const icon = CheckCircle; + const text = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.accepted', + defaultMessage: 'Accepted', + description: 'Status of the member invitation', + }); + const popoverHeader = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.acceptedPopoverHeader', + defaultMessage: 'Invitation accepted', + description: 'Popover header for the accepted status', + }); + const popoverBody = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.acceptedPopoverBody', + defaultMessage: "This member has successfully accepted the member invitation and can now browse this budget's catalog and enroll using their member permissions.", + description: 'Popover body for the accepted status', + }); + return ( + + ); +}; + +export default Accepted; diff --git a/src/components/learner-credit-management/members-tab/status-chips/BaseStatusChip.jsx b/src/components/learner-credit-management/members-tab/status-chips/BaseStatusChip.jsx new file mode 100644 index 0000000000..22682026cc --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/BaseStatusChip.jsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { Chip, Hyperlink } from '@openedx/paragon'; +import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; +import { HELP_CENTER_GROUPS_INVITE_LINK } from '../../../settings/data/constants'; +import { useStatusChip } from '../../data'; +import BaseModalPopup from '../../assignments-status-chips/BaseModalPopup'; + +const BaseStatusChip = ({ + enterpriseId, icon, text, popoverHeader, popoverBody, + popoverExtra1, popoverExtra2, statusEventName, helpEventName, +}) => { + const sendTrackEvent = (eventName, eventMetadata = {}) => { + sendEnterpriseTrackEvent(enterpriseId, eventName, { + ...eventMetadata, + }); + }; + const [target, setTarget] = useState(null); + const { + openChipModal, + closeChipModal, + isChipModalOpen, + helpCenterTrackEvent, + } = useStatusChip({ + chipInteractionEventName: statusEventName, + chipHelpCenterEventName: helpEventName, + trackEvent: sendTrackEvent, + }); + + return ( + <> + + {text} + + + + {popoverHeader} + + +

{popoverBody}

+
+ {popoverExtra1 && ( + <> +

{popoverExtra1}

+
    + {popoverExtra2} + + Help Center: Inviting Budget Members + +
+ + )} +
+
+
+ + ); +}; + +const mapStateToProps = state => ({ + enterpriseId: state.portalConfiguration.enterpriseId, +}); + +BaseStatusChip.defaultProps = { + popoverExtra1: '', + popoverExtra2: '', +}; + +BaseStatusChip.propTypes = { + enterpriseId: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + popoverHeader: PropTypes.string.isRequired, + popoverBody: PropTypes.string.isRequired, + popoverExtra1: PropTypes.string, + popoverExtra2: PropTypes.string, + statusEventName: PropTypes.string.isRequired, + helpEventName: PropTypes.string.isRequired, +}; + +export default connect(mapStateToProps)(BaseStatusChip); diff --git a/src/components/learner-credit-management/members-tab/status-chips/FailedBadEmail.jsx b/src/components/learner-credit-management/members-tab/status-chips/FailedBadEmail.jsx new file mode 100644 index 0000000000..74e1f1acb4 --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/FailedBadEmail.jsx @@ -0,0 +1,60 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; + +import { Error } from '@openedx/paragon/icons'; +import BaseStatusChip from './BaseStatusChip'; +import { LEARNER_CREDIT_MANAGEMENT_EVENTS as events } from '../../../../eventTracking'; + +const FailedBadEmail = ({ row }) => { + const intl = useIntl(); + const icon = Error; + const text = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmail', + defaultMessage: 'Failed: Bad email', + description: 'Status of the member invitation', + }); + const popoverHeader = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverHeader', + defaultMessage: 'Failed: Bad email', + description: 'Popover header for the failed email status', + }); + const popoverBody = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverBody', + defaultMessage: 'This member invitation failed because a notification to {userEmail} could not be sent.', + description: 'Popover body for the failed email status', + }, { userEmail: row.original.memberDetails.userEmail }); + const popoverExtra1 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverExtra1', + defaultMessage: 'Resolution steps', + description: 'Extra text for the failed email status', + }); + const popoverExtra2 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedEmailPopoverExtra2', + defaultMessage: 'Remove member from budget, ensure email is correct and re-invite. Get more troubleshooting help at ', + description: 'Extra text for the failed email status', + }); + return ( + + ); +}; + +FailedBadEmail.propTypes = { + row: PropTypes.shape({ + original: PropTypes.shape({ + memberDetails: PropTypes.shape({ + userEmail: PropTypes.string.isRequired, + }), + }), + }).isRequired, +}; + +export default FailedBadEmail; diff --git a/src/components/learner-credit-management/members-tab/status-chips/FailedSystem.jsx b/src/components/learner-credit-management/members-tab/status-chips/FailedSystem.jsx new file mode 100644 index 0000000000..c8d22402b9 --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/FailedSystem.jsx @@ -0,0 +1,48 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Error } from '@openedx/paragon/icons'; +import BaseStatusChip from './BaseStatusChip'; +import { LEARNER_CREDIT_MANAGEMENT_EVENTS as events } from '../../../../eventTracking'; + +const FailedSystem = () => { + const intl = useIntl(); + const icon = Error; + const text = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystem', + defaultMessage: 'Failed: System', + description: 'Status of the member invitation', + }); + const popoverHeader = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverHeader', + defaultMessage: 'Failed: System', + description: 'Popover header for the system failed status', + }); + const popoverBody = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverBody', + defaultMessage: 'Something went wrong behind the scenes.', + description: 'Popover body for the system failed status', + }); + const popoverExtra1 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverExtra1', + defaultMessage: 'Need help?', + description: 'Extra text for the system failed status', + }); + const popoverExtra2 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.failedSystemPopoverExtra2', + defaultMessage: 'Get help at ', + description: 'Extra text for the system failed status', + }); + return ( + + ); +}; + +export default FailedSystem; diff --git a/src/components/learner-credit-management/members-tab/status-chips/Pending.jsx b/src/components/learner-credit-management/members-tab/status-chips/Pending.jsx new file mode 100644 index 0000000000..c8d7e6cf74 --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/Pending.jsx @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types'; + +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Timelapse } from '@openedx/paragon/icons'; +import BaseStatusChip from './BaseStatusChip'; +import { LEARNER_CREDIT_MANAGEMENT_EVENTS as events } from '../../../../eventTracking'; + +const Pending = ({ row }) => { + const intl = useIntl(); + const icon = Timelapse; + const text = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pending', + defaultMessage: 'Waiting for member', + description: 'Status of the member invitation', + }); + const popoverHeader = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverHeader', + defaultMessage: 'Waiting for {userEmail}', + description: 'Popover header for the pending status', + }, { userEmail: row.original.memberDetails.userEmail }); + const popoverBody = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverBody', + defaultMessage: "This member must accept their invitation to browse this budget's catalog and enroll using their member permissions by logging in or creating an account within 90 days.", + description: 'Popover body for the pending status', + }); + const popoverExtra1 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverExtra1', + defaultMessage: 'Need help?', + description: 'Extra text for the pending status', + }); + const popoverExtra2 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.pendingPopoverExtra2', + defaultMessage: 'Learn more about adding budget members in Learner Credit at ', + description: 'Extra text for the pending status', + }); + return ( + + ); +}; + +Pending.propTypes = { + row: PropTypes.shape({ + original: PropTypes.shape({ + memberDetails: PropTypes.shape({ + userEmail: PropTypes.string.isRequired, + }), + }), + }).isRequired, +}; + +export default Pending; diff --git a/src/components/learner-credit-management/members-tab/status-chips/Removed.jsx b/src/components/learner-credit-management/members-tab/status-chips/Removed.jsx new file mode 100644 index 0000000000..625db66897 --- /dev/null +++ b/src/components/learner-credit-management/members-tab/status-chips/Removed.jsx @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types'; + +import { useIntl } from '@edx/frontend-platform/i18n'; +import { RemoveCircle } from '@openedx/paragon/icons'; +import BaseStatusChip from './BaseStatusChip'; +import { LEARNER_CREDIT_MANAGEMENT_EVENTS as events } from '../../../../eventTracking'; + +const Removed = () => { + const intl = useIntl(); + const icon = RemoveCircle; + const text = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removed', + defaultMessage: 'Removed', + description: 'Status of the member invitation', + }); + const popoverHeader = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverHeader', + defaultMessage: 'Member removed', + description: 'Popover header for the removed status', + }); + const popoverBody = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverBody', + defaultMessage: "This member has been successfully removed and can not browse this budget's catalog and enroll using their member permissions.", + description: 'Popover body for the removed status', + }); + const popoverExtra1 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverExtra1', + defaultMessage: 'Want to add them back?', + description: 'Extra text for the removed status', + }); + const popoverExtra2 = intl.formatMessage({ + id: 'learnerCreditManagement.budgetDetail.membersTab.membersTable.removedPopoverExtra2', + defaultMessage: 'Follow the steps provided at ', + description: 'Extra text for the removed status', + }); + return ( + + ); +}; + +Removed.propTypes = { + row: PropTypes.shape({ + original: PropTypes.shape({ + memberDetails: PropTypes.shape({ + userEmail: PropTypes.string.isRequired, + }), + }), + }).isRequired, +}; + +export default Removed; diff --git a/src/eventTracking.js b/src/eventTracking.js index f7dfd9872f..7930c12541 100644 --- a/src/eventTracking.js +++ b/src/eventTracking.js @@ -178,6 +178,18 @@ export const LEARNER_CREDIT_MANAGEMENT_EVENTS = { // Budget Expiry BUDGET_EXPIRY_ALERT_CONTACT_SUPPORT: `${LEARNER_CREDIT_BUDGET_EXPIRY_PREFIX}.alert.contact_support.clicked`, BUDGET_EXPIRY_MODAL_CONTACT_SUPPORT: `${LEARNER_CREDIT_BUDGET_EXPIRY_PREFIX}.modal.contact_support.clicked`, + // Members tab + MEMBERS_DATATABLE_STATUS_CHIP_ACCEPTED: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_accepted.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_FAILED_SYSTEM: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_failed_system.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_FAILED_BAD_EMAIL: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_failed_bad_email.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_PENDING: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_pending.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_REMOVED: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_removed.clicked`, + + MEMBERS_DATATABLE_STATUS_CHIP_ACCEPTED_HELP_CENTER: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_accepted_help_center.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_FAILED_SYSTEM_HELP_CENTER: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_failed_system_help_center.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_FAILED_BAD_EMAIL_HELP_CENTER: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_failed_bad_email_help_center.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_PENDING_HELP_CENTER: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_pending_help_center.clicked`, + MEMBERS_DATATABLE_STATUS_CHIP_REMOVED_HELP_CENTER: `${LEARNER_CREDIT_MANAGEMENT_PREFIX}.members_tab.status_chip_removed_help_center.clicked`, }; const EVENT_NAMES = { From 3656d337e3a4cc54f02a232227a3e6738d540d3d Mon Sep 17 00:00:00 2001 From: Kira Miller Date: Fri, 27 Sep 2024 15:33:12 +0000 Subject: [PATCH 2/5] feat: Add people management page + zero state --- .../EnterpriseApp/EnterpriseAppContent.jsx | 3 + .../EnterpriseApp/EnterpriseAppRoutes.jsx | 10 +++ .../EnterpriseApp/data/constants.js | 5 +- src/components/EnterpriseApp/index.jsx | 4 + .../images/ZeroStateImage.svg | 69 ++++++++++++++++ src/components/PeopleManagement/index.jsx | 80 +++++++++++++++++++ .../tests/PeopleManagementPage.test.jsx | 45 +++++++++++ src/components/Sidebar/index.jsx | 11 ++- src/containers/EnterpriseApp/index.jsx | 1 + src/containers/Sidebar/index.jsx | 1 + 10 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 src/components/PeopleManagement/images/ZeroStateImage.svg create mode 100644 src/components/PeopleManagement/index.jsx create mode 100644 src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx diff --git a/src/components/EnterpriseApp/EnterpriseAppContent.jsx b/src/components/EnterpriseApp/EnterpriseAppContent.jsx index 8e57dd83dd..8c9fd069c4 100644 --- a/src/components/EnterpriseApp/EnterpriseAppContent.jsx +++ b/src/components/EnterpriseApp/EnterpriseAppContent.jsx @@ -13,6 +13,7 @@ const EnterpriseAppContent = ({ enableReportingPage, enableSubscriptionManagementPage, enableAnalyticsPage, + enterpriseGroupsV2, }) => { const { FEATURE_CONTENT_HIGHLIGHTS } = getConfig(); const enterpriseAppContext = useContext(EnterpriseAppContext); @@ -32,6 +33,7 @@ const EnterpriseAppContent = ({ enableSubscriptionManagementPage={enableSubscriptionManagementPage} enableAnalyticsPage={enableAnalyticsPage} enableContentHighlightsPage={isContentHighlightsEnabled} + enterpriseGroupsV2={enterpriseGroupsV2} /> ); }; @@ -44,6 +46,7 @@ EnterpriseAppContent.propTypes = { enableReportingPage: PropTypes.bool.isRequired, enableSubscriptionManagementPage: PropTypes.bool.isRequired, enableAnalyticsPage: PropTypes.bool.isRequired, + enterpriseGroupsV2: PropTypes.bool.isRequired, }; export default EnterpriseAppContent; diff --git a/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx b/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx index 12d466c89d..989ab419ef 100644 --- a/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx +++ b/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx @@ -17,6 +17,7 @@ import BulkEnrollmentResultsDownloadPage from '../BulkEnrollmentResultsDownloadP import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext'; import ContentHighlights from '../ContentHighlights'; import LearnerCreditManagementRoutes from '../learner-credit-management'; +import PeopleManagementPage from '../PeopleManagement'; const EnterpriseAppRoutes = ({ email, @@ -27,6 +28,7 @@ const EnterpriseAppRoutes = ({ enableSubscriptionManagementPage, enableAnalyticsPage, enableContentHighlightsPage, + enterpriseGroupsV2, }) => { const { canManageLearnerCredit } = useContext(EnterpriseSubsidiesContext); const { enterpriseAppPage } = useParams(); @@ -115,6 +117,13 @@ const EnterpriseAppRoutes = ({ /> )} + {enterpriseGroupsV2 && enterpriseAppPage === ROUTE_NAMES.peopleManagement && ( + } + /> + )} + {enableContentHighlightsPage && enterpriseAppPage === ROUTE_NAMES.contentHighlights && ( @@ -186,6 +188,7 @@ EnterpriseApp.defaultProps = { enableAnalyticsScreen: false, enableReportingConfigurationsScreen: false, enablePortalLearnerCreditManagementScreen: false, + enterpriseGroupsV2: false, loading: true, }; @@ -212,6 +215,7 @@ EnterpriseApp.propTypes = { enableAnalyticsScreen: PropTypes.bool, enableReportingConfigurationsScreen: PropTypes.bool, enablePortalLearnerCreditManagementScreen: PropTypes.bool, + enterpriseGroupsV2: PropTypes.bool, error: PropTypes.instanceOf(Error), loading: PropTypes.bool, }; diff --git a/src/components/PeopleManagement/images/ZeroStateImage.svg b/src/components/PeopleManagement/images/ZeroStateImage.svg new file mode 100644 index 0000000000..312c38bfa2 --- /dev/null +++ b/src/components/PeopleManagement/images/ZeroStateImage.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/PeopleManagement/index.jsx b/src/components/PeopleManagement/index.jsx new file mode 100644 index 0000000000..e1113e8bee --- /dev/null +++ b/src/components/PeopleManagement/index.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; +import { + ActionRow, Button, Card, Container, useToggle, +} from '@openedx/paragon'; +import { Add } from '@openedx/paragon/icons'; + +import cardImage from './images/ZeroStateImage.svg'; +import Hero from '../Hero'; + +const PeopleManagementPage = () => { + const intl = useIntl(); + const PAGE_TITLE = intl.formatMessage({ + id: 'admin.portal.people.management.page', + defaultMessage: 'People Management', + description: 'Title for the people management page.', + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [isModalOpen, openModal, closeModal] = useToggle(false); + + return ( + <> + + + + + +

+ +

+ +
+ + +
+ + + +

+ +

+

+ +

+
+
+
+ + ); +}; + +export default PeopleManagementPage; diff --git a/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx b/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx new file mode 100644 index 0000000000..3dc9da5279 --- /dev/null +++ b/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx @@ -0,0 +1,45 @@ +import { + render, screen, +} from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import thunk from 'redux-thunk'; +import configureMockStore from 'redux-mock-store'; +import { Provider } from 'react-redux'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; + +import PeopleManagementPage from '..'; + +const mockStore = configureMockStore([thunk]); +const getMockStore = store => mockStore(store); +const enterpriseSlug = 'test-enterprise'; +const enterpriseUUID = '1234'; +const initialStoreState = { + portalConfiguration: { + enterpriseId: enterpriseUUID, + enterpriseSlug, + enterpriseGroupsV2: true, + }, +}; + +const PeopleManagementPageWrapper = ({ + initialState = initialStoreState, +}) => { + const store = getMockStore(initialState); + return ( + + + + + + ); +}; + +describe('', () => { + it('renders the PeopleManagementPage zero state', () => { + render(); + expect(document.querySelector('h3').textContent).toEqual( + 'Your Learner Credit groups', + ); + expect(screen.getByText('You don\'t have any groups yet.')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Sidebar/index.jsx b/src/components/Sidebar/index.jsx index cb6b46c4b1..110f1ffb34 100644 --- a/src/components/Sidebar/index.jsx +++ b/src/components/Sidebar/index.jsx @@ -6,7 +6,8 @@ import classNames from 'classnames'; import { Icon } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { - BookOpen, CreditCard, Description, InsertChartOutlined, MoneyOutline, Settings, Support, Tag, TrendingUp, + BookOpen, CreditCard, Description, InsertChartOutlined, MoneyOutline, + Person, Settings, Support, Tag, TrendingUp, } from '@openedx/paragon/icons'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { getConfig } from '@edx/frontend-platform/config'; @@ -35,6 +36,7 @@ const Sidebar = ({ onWidthChange, isMobile, enterpriseGroupsV1, + enterpriseGroupsV2, onMount, }) => { const sidebarRef = useRef(); @@ -149,6 +151,12 @@ const Sidebar = ({ icon: , hidden: !canManageLearnerCredit, }, + { + title: 'People Management', + to: `${baseUrl}/admin/${ROUTE_NAMES.peopleManagement}`, + icon: , + hidden: !enterpriseGroupsV2, + }, { title: intl.formatMessage({ id: 'sidebar.menu.item.highlights.title', @@ -258,6 +266,7 @@ Sidebar.propTypes = { onMount: PropTypes.func.isRequired, isMobile: PropTypes.bool, enterpriseGroupsV1: PropTypes.bool, + enterpriseGroupsV2: PropTypes.bool, }; export default Sidebar; diff --git a/src/containers/EnterpriseApp/index.jsx b/src/containers/EnterpriseApp/index.jsx index cc5d3ffed6..afc4ff9ceb 100644 --- a/src/containers/EnterpriseApp/index.jsx +++ b/src/containers/EnterpriseApp/index.jsx @@ -19,6 +19,7 @@ const mapStateToProps = (state) => { enableLmsConfigurationsScreen: state.portalConfiguration.enableLmsConfigurationsScreen, enableReportingConfigurationsScreen: state.portalConfiguration.enableReportingConfigScreen, enablePortalLearnerCreditManagementScreen: state.portalConfiguration.enablePortalLearnerCreditManagementScreen, + enterpriseGroupsV2: state.portalConfiguration.enterpriseGroupsV2, enterpriseId: state.portalConfiguration.enterpriseId, enterpriseName: state.portalConfiguration.enterpriseName, enterpriseFeatures: state.portalConfiguration.enterpriseFeatures, diff --git a/src/containers/Sidebar/index.jsx b/src/containers/Sidebar/index.jsx index c0cfa096f6..ff023c010e 100644 --- a/src/containers/Sidebar/index.jsx +++ b/src/containers/Sidebar/index.jsx @@ -18,6 +18,7 @@ const mapStateToProps = state => ({ enableLmsConfigurationsScreen: state.portalConfiguration.enableLmsConfigurationsScreen, enableAnalyticsScreen: state.portalConfiguration.enableAnalyticsScreen, enterpriseGroupsV1: state.portalConfiguration.enterpriseFeatures?.enterpriseGroupsV1, + enterpriseGroupsV2: state.portalConfiguration.enterpriseFeatures?.enterpriseGroupsV2, }); const mapDispatchToProps = dispatch => ({ From 8009b4be97167b788a26d9437fade2f47680a9d7 Mon Sep 17 00:00:00 2001 From: Kira Miller Date: Fri, 27 Sep 2024 20:13:14 +0000 Subject: [PATCH 3/5] fix: adding info popover message --- src/components/PeopleManagement/index.jsx | 63 ++++++++++++++----- .../tests/PeopleManagementPage.test.jsx | 11 +++- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/components/PeopleManagement/index.jsx b/src/components/PeopleManagement/index.jsx index e1113e8bee..98d9fb23b9 100644 --- a/src/components/PeopleManagement/index.jsx +++ b/src/components/PeopleManagement/index.jsx @@ -1,13 +1,15 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Helmet } from 'react-helmet'; import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import { - ActionRow, Button, Card, Container, useToggle, + ActionRow, Button, Card, Icon, IconButtonWithTooltip, useToggle, } from '@openedx/paragon'; -import { Add } from '@openedx/paragon/icons'; +import { Add, InfoOutline } from '@openedx/paragon/icons'; import cardImage from './images/ZeroStateImage.svg'; import Hero from '../Hero'; +import { SUBSIDY_TYPES } from '../../data/constants/subsidyTypes'; +import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext'; const PeopleManagementPage = () => { const intl = useIntl(); @@ -17,23 +19,55 @@ const PeopleManagementPage = () => { description: 'Title for the people management page.', }); + const { + enterpriseSubsidyTypes, + } = useContext(EnterpriseSubsidiesContext); + + const hasLearnerCredit = enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.budget); + const hasOtherSubsidyTypes = enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.license) + || enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.coupon); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [isModalOpen, openModal, closeModal] = useToggle(false); + const tooltipContent = ( + + ); + return ( <> - + {hasLearnerCredit && ( +
-

- +

+ +

+ {hasLearnerCredit && hasOtherSubsidyTypes && ( + {}} + variant="primary" + className="ml-1" /> -

+ )} +
{

-

+

- +
+ )} ); }; diff --git a/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx b/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx index 3dc9da5279..f8a5d33a8b 100644 --- a/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx +++ b/src/components/PeopleManagement/tests/PeopleManagementPage.test.jsx @@ -7,6 +7,7 @@ import configureMockStore from 'redux-mock-store'; import { Provider } from 'react-redux'; import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { EnterpriseSubsidiesContext } from '../../EnterpriseSubsidiesContext'; import PeopleManagementPage from '..'; const mockStore = configureMockStore([thunk]); @@ -21,14 +22,22 @@ const initialStoreState = { }, }; +const defaultEnterpriseSubsidiesContextValue = { + enterpriseSubsidyTypes: ['budget', 'license'], + isLoading: false, +}; + const PeopleManagementPageWrapper = ({ initialState = initialStoreState, + enterpriseSubsidiesContextValue = defaultEnterpriseSubsidiesContextValue, }) => { const store = getMockStore(initialState); return ( - + + + ); From ea124464eec967ac581ad44048a101f7d933810c Mon Sep 17 00:00:00 2001 From: Kira Miller Date: Tue, 1 Oct 2024 22:49:15 +0000 Subject: [PATCH 4/5] fix: PR requests --- src/components/EnterpriseApp/index.jsx | 5 ++--- src/components/PeopleManagement/index.jsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/EnterpriseApp/index.jsx b/src/components/EnterpriseApp/index.jsx index 70c9de0511..5de30bdb61 100644 --- a/src/components/EnterpriseApp/index.jsx +++ b/src/components/EnterpriseApp/index.jsx @@ -82,7 +82,6 @@ class EnterpriseApp extends React.Component { enableAnalyticsScreen, enableReportingConfigurationsScreen, enablePortalLearnerCreditManagementScreen, - enterpriseGroupsV2, enterpriseId, enterpriseName, enterpriseFeatures, @@ -159,7 +158,7 @@ class EnterpriseApp extends React.Component { enableReportingPage={features.REPORTING_CONFIGURATIONS && enableReportingConfigurationsScreen} enableSubscriptionManagementPage={enableSubscriptionManagementScreen} enableAnalyticsPage={features.ANALYTICS && enableAnalyticsScreen} - enterpriseGroupsV2={enterpriseGroupsV2} + enterpriseGroupsV2={enterpriseFeatures.enterpriseGroupsV2} > @@ -188,7 +187,6 @@ EnterpriseApp.defaultProps = { enableAnalyticsScreen: false, enableReportingConfigurationsScreen: false, enablePortalLearnerCreditManagementScreen: false, - enterpriseGroupsV2: false, loading: true, }; @@ -198,6 +196,7 @@ EnterpriseApp.propTypes = { enterpriseName: PropTypes.string, enterpriseFeatures: PropTypes.shape({ topDownAssignmentRealTimeLcm: PropTypes.bool, + enterpriseGroupsV2: PropTypes.bool, }), enterpriseBranding: PropTypes.shape({ primary_color: PropTypes.string, diff --git a/src/components/PeopleManagement/index.jsx b/src/components/PeopleManagement/index.jsx index 98d9fb23b9..8014c27c03 100644 --- a/src/components/PeopleManagement/index.jsx +++ b/src/components/PeopleManagement/index.jsx @@ -34,7 +34,7 @@ const PeopleManagementPage = () => { ); From c4c5696eb57c07a33418cda0d39a70abe18d918b Mon Sep 17 00:00:00 2001 From: katrinan029 Date: Thu, 3 Oct 2024 15:14:55 +0000 Subject: [PATCH 5/5] feat: adds changes --- .../EnterpriseApp/EnterpriseAppRoutes.jsx | 2 +- .../PeopleManagement/CreateGroupModal.jsx | 111 ++++++++++ .../CreateGroupModalWrapper.jsx | 195 ++++++++++++++++++ src/components/PeopleManagement/constants.js | 1 + src/components/PeopleManagement/index.jsx | 125 +++++------ .../invite-modal/FileUpload.jsx | 5 +- .../invite-modal/InviteModalSummary.jsx | 3 +- .../InviteModalSummaryEmptyState.jsx | 22 +- src/data/services/LmsApiService.js | 10 + 9 files changed, 402 insertions(+), 72 deletions(-) create mode 100644 src/components/PeopleManagement/CreateGroupModal.jsx create mode 100644 src/components/PeopleManagement/CreateGroupModalWrapper.jsx create mode 100644 src/components/PeopleManagement/constants.js diff --git a/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx b/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx index 989ab419ef..de31ee76f9 100644 --- a/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx +++ b/src/components/EnterpriseApp/EnterpriseAppRoutes.jsx @@ -32,7 +32,7 @@ const EnterpriseAppRoutes = ({ }) => { const { canManageLearnerCredit } = useContext(EnterpriseSubsidiesContext); const { enterpriseAppPage } = useParams(); - + console.log(enterpriseGroupsV2) return ( {enterpriseAppPage === ROUTE_NAMES.learners && ( diff --git a/src/components/PeopleManagement/CreateGroupModal.jsx b/src/components/PeopleManagement/CreateGroupModal.jsx new file mode 100644 index 0000000000..54a180c6f4 --- /dev/null +++ b/src/components/PeopleManagement/CreateGroupModal.jsx @@ -0,0 +1,111 @@ +import React, { + useCallback, useEffect, useMemo, useState, +} from 'react'; +import PropTypes from 'prop-types'; +import debounce from 'lodash.debounce'; +import { + Col, Container, Form, Row, +} from '@openedx/paragon'; + +import InviteModalSummary from '../learner-credit-management/invite-modal/InviteModalSummary'; +import { EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY, INPUT_TYPE, isInviteEmailAddressesInputValueValid } from '../learner-credit-management/cards/data'; +import FileUpload from '../learner-credit-management/invite-modal/FileUpload'; +import { MAX_LENGTH_GROUP_NAME } from './constants'; + +const CreateGroupModal = ({ onEmailAddressesChange }) => { + const [learnerEmails, setLearnerEmails] = useState([]); + const [emailAddressesInputValue, setEmailAddressesInputValue] = useState(''); + const [memberInviteMetadata, setMemberInviteMetadata] = useState({ + isValidInput: null, + lowerCasedEmails: [], + duplicateEmails: null, + }); + const [groupNameLength, setGroupNameLength] = useState(0); + const [groupName, setGroupName] = useState(''); + + + const handleGroupNameChange = (e) => { + const { value } = e.target; + if (value.length > MAX_LENGTH_GROUP_NAME) { + return; + } + setGroupNameLength(value.length); + setGroupName(value); + }; + + const handleEmailAddressesChanged = useCallback((value) => { + if (!value) { + setLearnerEmails([]); + onEmailAddressesChange([]); + return; + } + const emails = value.split('\n').map((email) => email.trim()).filter((email) => email.length > 0); + console.log(emails) + setLearnerEmails(emails); + }, [onEmailAddressesChange]); + + const debouncedHandleEmailAddressesChanged = useMemo( + () => debounce(handleEmailAddressesChanged, EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY), + [handleEmailAddressesChanged], + ); + + useEffect(() => { + debouncedHandleEmailAddressesChanged(emailAddressesInputValue); + }, [emailAddressesInputValue, debouncedHandleEmailAddressesChanged]); + + useEffect(() => { + const inviteMetadata = isInviteEmailAddressesInputValueValid({ + learnerEmails, + }); + setMemberInviteMetadata(inviteMetadata); + if (inviteMetadata.canInvite) { + onEmailAddressesChange(learnerEmails, { canInvite: true }); + } else { + onEmailAddressesChange([]); + } + console.log(inviteMetadata) + }, [onEmailAddressesChange, learnerEmails]); + + return ( + +

Create a custom group of members

+ + +

Name your group

+ + + {groupNameLength} / {MAX_LENGTH_GROUP_NAME} + + + +
+ + +

Select group members

+

Upload a CSV or select members from the table below.

+ + + +

Details

+ +
+ +
+
+ ); +}; + +CreateGroupModal.propTypes = { + onEmailAddressesChange: PropTypes.func.isRequired, +}; + +export default CreateGroupModal; diff --git a/src/components/PeopleManagement/CreateGroupModalWrapper.jsx b/src/components/PeopleManagement/CreateGroupModalWrapper.jsx new file mode 100644 index 0000000000..ed4eef49ba --- /dev/null +++ b/src/components/PeopleManagement/CreateGroupModalWrapper.jsx @@ -0,0 +1,195 @@ +import React, { + useContext, useCallback, useEffect, useMemo, useState, +} from 'react'; +import debounce from 'lodash.debounce'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + ActionRow, Button, useToggle, FullscreenModal, StatefulButton, Col, Container, Form, Row, +} from '@openedx/paragon'; +import LmsApiService from '../../data/services/LmsApiService'; +import CreateGroupModal from './CreateGroupModal'; + +import InviteModalSummary from '../learner-credit-management/invite-modal/InviteModalSummary'; +import { EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY, INPUT_TYPE, isInviteEmailAddressesInputValueValid } from '../learner-credit-management/cards/data'; +import FileUpload from '../learner-credit-management/invite-modal/FileUpload'; +import { MAX_LENGTH_GROUP_NAME } from './constants'; + +const CreateGroupModalWrapper = ({ isModalOpen, openModal, closeModal, enterpriseUUID }) => { + const intl = useIntl(); + const [canInviteMembers, setCanInviteMembers] = useState(false); + const [isSystemErrorModalOpen, openSystemErrorModal, closeSystemErrorModal] = useToggle(false); + const [createButtonState, setCreateButtonState] = useState('default'); + + const [learnerEmails, setLearnerEmails] = useState([]); + const [emailAddressesInputValue, setEmailAddressesInputValue] = useState(''); + const [memberInviteMetadata, setMemberInviteMetadata] = useState({ + // isValidInput: null, + // lowerCasedEmails: [], + // duplicateEmails: null, + }); + const [groupNameLength, setGroupNameLength] = useState(0); + const [groupName, setGroupName] = useState(''); + + const handleCreateGroup = async () => { + setCreateButtonState('pending'); + const options = { + enterpriseCustomer: enterpriseUUID, + budgetType: 'flex', + name: '' + }; + + try { + if (true > 0) { + const groupUuid = ''; + const response = await LmsApiService.createEnterpriseGroup(groupUuid, options); + console.log(response) + const totalLearnersInvited = ''; + setCreateButtonState('complete'); + handleCloseInviteModal(); + } else { + setCreateButtonState('error'); + openSystemErrorModal(); + } + } catch (err) { + setCreateButtonState('error'); + openSystemErrorModal(); + } + }; + + const onEmailAddressesChange = useCallback(( + value, + { canInvite = false } = {}, + ) => { + setLearnerEmails(value); + setCanInviteMembers(canInvite); + }, []); + + + const handleGroupNameChange = (e) => { + const { value } = e.target; + if (value.length > MAX_LENGTH_GROUP_NAME) { + return; + } + setGroupNameLength(value.length); + setGroupName(value); + }; + + const handleEmailAddressesChanged = useCallback((value) => { + if (!value) { + setLearnerEmails([]); + onEmailAddressesChange([]); + return; + } + const emails = value.split('\n').map((email) => email.trim()).filter((email) => email.length > 0); + console.log(emails) + setLearnerEmails(emails); + }, [onEmailAddressesChange]); + + const debouncedHandleEmailAddressesChanged = useMemo( + () => debounce(handleEmailAddressesChanged, EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY), + [handleEmailAddressesChanged], + ); + + useEffect(() => { + debouncedHandleEmailAddressesChanged(emailAddressesInputValue); + }, [emailAddressesInputValue, debouncedHandleEmailAddressesChanged]); + + // useEffect(() => { + // const inviteMetadata = isInviteEmailAddressesInputValueValid({ + // learnerEmails, + // }); + // setMemberInviteMetadata(inviteMetadata); + // if (inviteMetadata.canInvite) { + // onEmailAddressesChange(learnerEmails, { canInvite: true }); + // } else { + // onEmailAddressesChange([]); + // } + // console.log(inviteMetadata) + // }, [onEmailAddressesChange, learnerEmails]); + + + return ( + + + + + + )} + > + {/* */} + +

Create a custom group of members

+ + +

Name your group

+ + + {groupNameLength} / {MAX_LENGTH_GROUP_NAME} + + + +
+ + +

Select group members

+

Upload a CSV or select members from the table below.

+ + + +

Details

+ +
+ +
+
+
+ ); +}; + +const mapStateToProps = state => ({ + enterpriseUUID: state.portalConfiguration.enterpriseId, +}); + +CreateGroupModalWrapper.propTypes = { + enterpriseUUID: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + openModal: PropTypes.func.isRequired, + closeModal: PropTypes.func.isRequired, +}; + + +export default connect(mapStateToProps)(CreateGroupModalWrapper); diff --git a/src/components/PeopleManagement/constants.js b/src/components/PeopleManagement/constants.js new file mode 100644 index 0000000000..fb370408c2 --- /dev/null +++ b/src/components/PeopleManagement/constants.js @@ -0,0 +1 @@ +export const MAX_LENGTH_GROUP_NAME = 60 \ No newline at end of file diff --git a/src/components/PeopleManagement/index.jsx b/src/components/PeopleManagement/index.jsx index 8014c27c03..65b03fb08d 100644 --- a/src/components/PeopleManagement/index.jsx +++ b/src/components/PeopleManagement/index.jsx @@ -2,7 +2,7 @@ import React, { useContext } from 'react'; import { Helmet } from 'react-helmet'; import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import { - ActionRow, Button, Card, Icon, IconButtonWithTooltip, useToggle, + ActionRow, Button, Card, Icon, IconButtonWithTooltip, useToggle } from '@openedx/paragon'; import { Add, InfoOutline } from '@openedx/paragon/icons'; @@ -10,6 +10,7 @@ import cardImage from './images/ZeroStateImage.svg'; import Hero from '../Hero'; import { SUBSIDY_TYPES } from '../../data/constants/subsidyTypes'; import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext'; +import CreateGroupModalWrapper from './CreateGroupModalWrapper'; const PeopleManagementPage = () => { const intl = useIntl(); @@ -27,7 +28,6 @@ const PeopleManagementPage = () => { const hasOtherSubsidyTypes = enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.license) || enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.coupon); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [isModalOpen, openModal, closeModal] = useToggle(false); const tooltipContent = ( @@ -43,70 +43,71 @@ const PeopleManagementPage = () => { {hasLearnerCredit && ( -
- - - -

- -

- {hasLearnerCredit && hasOtherSubsidyTypes && ( - {}} - variant="primary" - className="ml-1" - /> - )} -
- -
- - -
- - - -

+
+ + + +

+ +

+ {hasLearnerCredit && hasOtherSubsidyTypes && ( + { }} + variant="primary" + className="ml-1" + /> + )} +
-

-

+ + +

+ + + + + + +

+ +

+

+ +

+
+
+
)} ); diff --git a/src/components/learner-credit-management/invite-modal/FileUpload.jsx b/src/components/learner-credit-management/invite-modal/FileUpload.jsx index 46b81b49bc..dcdfebe2fc 100644 --- a/src/components/learner-credit-management/invite-modal/FileUpload.jsx +++ b/src/components/learner-credit-management/invite-modal/FileUpload.jsx @@ -8,7 +8,7 @@ import { InsertDriveFile } from '@openedx/paragon/icons'; import { formatBytes } from '../../MultipleFileInputField/utils'; import InviteModalInputFeedback from './InviteModalInputFeedback'; -const FileUpload = ({ memberInviteMetadata, setEmailAddressesInputValue }) => { +const FileUpload = ({ memberInviteMetadata, setEmailAddressesInputValue, isGroupsInvite }) => { const [uploadedFile, setUploadedFile] = useState(undefined); const UploadedFile = ( <> @@ -33,6 +33,7 @@ const FileUpload = ({ memberInviteMetadata, setEmailAddressesInputValue }) => { return ( { multipleDragged: 'Cannot upload more than one file.', }} /> - + ); }; diff --git a/src/components/learner-credit-management/invite-modal/InviteModalSummary.jsx b/src/components/learner-credit-management/invite-modal/InviteModalSummary.jsx index 4817e9b1f5..c07ffd8a9c 100644 --- a/src/components/learner-credit-management/invite-modal/InviteModalSummary.jsx +++ b/src/components/learner-credit-management/invite-modal/InviteModalSummary.jsx @@ -11,6 +11,7 @@ import InviteModalSummaryDuplicate from './InviteModalSummaryDuplicate'; const InviteModalSummary = ({ memberInviteMetadata, + isGroupsInvite, }) => { const { isValidInput, @@ -48,7 +49,7 @@ const InviteModalSummary = ({ if (isEmpty(cardSections)) { cardSections = cardSections.concat( - renderCard(), + renderCard(), ); } diff --git a/src/components/learner-credit-management/invite-modal/InviteModalSummaryEmptyState.jsx b/src/components/learner-credit-management/invite-modal/InviteModalSummaryEmptyState.jsx index f5af5723ac..6195062543 100644 --- a/src/components/learner-credit-management/invite-modal/InviteModalSummaryEmptyState.jsx +++ b/src/components/learner-credit-management/invite-modal/InviteModalSummaryEmptyState.jsx @@ -1,10 +1,20 @@ import React from 'react'; -const InviteModalSummaryEmptyState = () => ( - <> -
You haven't entered any members yet.
- Add member emails to get started. - -); +const InviteModalSummaryEmptyState = ({ isGroupsInvite }) => { + if (isGroupsInvite) { + return ( + <> +
You haven't uploaded any members yet.
+ Upload a CSV file or select members to get started. + + ) + } + return ( + <> +
You haven't entered any members yet.
+ Add member emails to get started. + + ) +}; export default InviteModalSummaryEmptyState; diff --git a/src/data/services/LmsApiService.js b/src/data/services/LmsApiService.js index c0bd742b04..6833892d91 100644 --- a/src/data/services/LmsApiService.js +++ b/src/data/services/LmsApiService.js @@ -45,6 +45,16 @@ class LmsApiService { static enterpriseGroupListUrl = `${LmsApiService.baseUrl}/enterprise/api/v1/enterprise_group/`; + static createEnterpriseGroup(options) { + const postParams = { + budget_type: options.budgetType, + name: options.name, + enterprise_customer: options.enterpriseCustomer, + }; + const createEnterpriseGroupUrl = `${LmsApiService.enterpriseGroupUrl}`; + return LmsApiService.apiClient().post(createEnterpriseGroupUrl, postParams); + } + static fetchEnterpriseSsoOrchestrationRecord(configurationUuid) { const enterpriseSsoOrchestrationFetchUrl = `${LmsApiService.enterpriseSsoOrchestrationUrl}${configurationUuid}`; return LmsApiService.apiClient().get(enterpriseSsoOrchestrationFetchUrl);