Skip to content

Commit

Permalink
Merge branch 'master' into hu/ent-6501
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 authored Nov 18, 2022
2 parents d585630 + a79f3cb commit af9ec2a
Show file tree
Hide file tree
Showing 39 changed files with 622 additions and 126 deletions.
2 changes: 1 addition & 1 deletion src/components/App/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NotFoundPage from '../NotFoundPage';
import { SystemWideWarningBanner } from '../system-wide-banner';

import store from '../../data/store';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';

const AppWrapper = () => {
const apiClient = getAuthenticatedHttpClient();
Expand Down
2 changes: 1 addition & 1 deletion src/components/CodeManagement/CouponCodeTabs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { SubsidyRequestsContext } from '../subsidy-requests';
import ManageCodesTab from './ManageCodesTab';
import ManageRequestsTab from './ManageRequestsTab';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import {
MANAGE_CODES_TAB,
MANAGE_REQUESTS_TAB,
Expand Down
2 changes: 1 addition & 1 deletion src/components/CodeManagement/ManageCodesTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import LoadingMessage from '../LoadingMessage';
import Coupon from '../Coupon';
import { updateUrl } from '../../utils';
import { fetchCouponOrders, clearCouponOrders } from '../../data/actions/coupons';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import NewFeatureAlertBrowseAndRequest from '../NewFeatureAlertBrowseAndRequest';
import { SubsidyRequestsContext } from '../subsidy-requests';
import { SUPPORTED_SUBSIDY_TYPES } from '../../data/constants/subsidyRequests';
Expand Down
4 changes: 2 additions & 2 deletions src/components/ContentHighlights/ContentHighlightRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Route } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import ContentHighlightSet from './ContentHighlightSet';
import ContentHighlightsDashboard from './ContentHighlightsDashboard';

Expand All @@ -16,7 +16,7 @@ const ContentHighlightRoutes = ({ enterpriseSlug }) => {
exact
/>
<Route
path={`${baseContentHighlightPath}/:highlightUUID/`}
path={`${baseContentHighlightPath}/:highlightSetUUID/`}
component={ContentHighlightSet}
exact
/>
Expand Down
8 changes: 4 additions & 4 deletions src/components/ContentHighlights/ContentHighlightSetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Card } from '@edx/paragon';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import { ContentHighlightsContext } from './ContentHighlightsContext';
import { toggleStepperModalAction } from './data/actions';

const ContentHighlightSetCard = ({
imageCapSrc,
title,
highlightUUID,
highlightSetUUID,
isPublished,
enterpriseSlug,
itemCount,
Expand All @@ -22,7 +22,7 @@ const ContentHighlightSetCard = ({
const handleHighlightSetClick = () => {
if (isPublished) {
// redirect to individual highlighted courses based on uuid
return history.push(`/${enterpriseSlug}/admin/${ROUTE_NAMES.contentHighlights}/${highlightUUID}`);
return history.push(`/${enterpriseSlug}/admin/${ROUTE_NAMES.contentHighlights}/${highlightSetUUID}`);
}
return dispatch(toggleStepperModalAction({ isOpen: true }));
};
Expand All @@ -43,7 +43,7 @@ const ContentHighlightSetCard = ({

ContentHighlightSetCard.propTypes = {
title: PropTypes.string.isRequired,
highlightUUID: PropTypes.string.isRequired,
highlightSetUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
isPublished: PropTypes.bool.isRequired,
itemCount: PropTypes.number.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const ContentHighlightSetCardContainer = () => (
<ContentHighlightSetCard
key={uuid}
title={title}
highlightUUID={uuid}
highlightSetUUID={uuid}
isPublished={isPublished}
/>
))}
{/* eslint-enable camelcase */}
</CardGrid>
);

export default ContentHighlightSetCardContainer;
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React, { useState } from 'react';
import { CardGrid } from '@edx/paragon';
import { useParams } from 'react-router-dom';
import { camelCaseObject } from '@edx/frontend-platform';
import ContentHighlightCardItem from './ContentHighlightCardItem';
import { TEST_COURSE_HIGHLIGHTS_DATA } from './data/constants';

const ContentHighlightsCardItemsContainer = () => {
const { highlightUUID } = useParams(); // eslint-disable-line
const [highlightCourses] = useState(
camelCaseObject(TEST_COURSE_HIGHLIGHTS_DATA)[0]?.highlightedContent,
);
Expand Down
46 changes: 38 additions & 8 deletions src/components/ContentHighlights/ContentHighlightsDashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import React, { useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Container } from '@edx/paragon';
import { useHistory } from 'react-router-dom';
import { Container, Toast } from '@edx/paragon';

import ZeroStateHighlights from './ZeroState';
import CurrentContentHighlights from './CurrentContentHighlights';
import ContentHighlightHelmet from './ContentHighlightHelmet';
import { EnterpriseAppContext } from '../EnterpriseApp/EnterpriseAppContextProvider';

const ContentHighlightsDashboardBase = ({ children }) => (
<Container fluid className="my-5">
<ContentHighlightHelmet title="Highlights" />
{children}
</Container>
);
const ContentHighlightsDashboardBase = ({ children }) => {
const history = useHistory();
const { location } = history;
const { state: locationState } = location;

const [hasDeletedHighlightSetToast, setHasDeletedHighlightSetToast] = useState(false);

// TODO: the below `useEffect` needs test coverage. deferred until there is a reducer
// for the `ContentHighlights` module, where `DeleteHighlightSet` can dispatch an action
// to trigger the `Toast`, rather than relying on history's location state.
/* istanbul ignore next */
useEffect(() => {
if (!locationState?.deletedHighlightSet) {
return;
}
setHasDeletedHighlightSetToast(true);
const newState = { ...locationState };
delete newState.deletedHighlightSet;
history.replace({ ...location, state: newState });
}, [history, location, locationState]);

return (
<Container fluid className="my-5">
<ContentHighlightHelmet title="Highlights" />
{children}
<Toast
onClose={() => setHasDeletedHighlightSetToast(false)}
show={hasDeletedHighlightSetToast}
>
Highlight collection deleted.
</Toast>
</Container>
);
};

ContentHighlightsDashboardBase.propTypes = {
children: PropTypes.node.isRequired,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import { Button, ActionRow } from '@edx/paragon';
import { ActionRow } from '@edx/paragon';
import { useParams } from 'react-router-dom';
import ContentHighlightHelmet from './ContentHighlightHelmet';
import DeleteHighlightSet from './DeleteHighlightSet';

const CurrentContentHighlightItemsHeader = () => {
const { highlightUUID } = useParams();
const { highlightSetUUID } = useParams();

const highlightTitle = highlightUUID;
const highlightTitle = highlightSetUUID;

const titleName = `${highlightTitle} - Highlights`;

Expand All @@ -18,7 +19,7 @@ const CurrentContentHighlightItemsHeader = () => {
{highlightTitle}
</h2>
<ActionRow.Spacer />
<Button variant="outline-primary">Delete Highlight</Button>
<DeleteHighlightSet />
</ActionRow>
</>
);
Expand Down
111 changes: 111 additions & 0 deletions src/components/ContentHighlights/DeleteHighlightSet.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Alert,
Button,
AlertModal,
useToggle,
ActionRow,
StatefulButton,
} from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { useParams, useHistory } from 'react-router-dom';
import { logError } from '@edx/frontend-platform/logging';
import { connect } from 'react-redux';

import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogApiService';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import { EnterpriseAppContext } from '../EnterpriseApp/EnterpriseAppContextProvider';
import { enterpriseCurationActions } from '../EnterpriseApp/data/enterpriseCurationReducer';

function DeleteHighlightSet({ enterpriseSlug }) {
const { highlightSetUUID } = useParams();
const [isOpen, open, close] = useToggle(false);
const [deletionState, setDeletionState] = useState('default');
const history = useHistory();
const { enterpriseCuration: { dispatch } } = useContext(EnterpriseAppContext);
const [isDeleted, setIsDeleted] = useState(false);
const [deletionError, setDeletionError] = useState(null);

const handleDeleteClick = () => {
const deleteHighlightSet = async () => {
setDeletionState('pending');
try {
await EnterpriseCatalogApiService.deleteHighlightSet(highlightSetUUID);
dispatch(enterpriseCurationActions.deleteHighlightSet(highlightSetUUID));
setIsDeleted(true);
} catch (error) {
logError(error);
setDeletionError(error);
} finally {
setDeletionState('default');
}
};
deleteHighlightSet();
};

useEffect(() => {
if (isDeleted) {
close();
history.push(`/${enterpriseSlug}/admin/${ROUTE_NAMES.contentHighlights}`, {
// TODO: expose the highlight set name here so it can be
// displayed in the Toast notification. once ContentHighlights has
// a reducer in its context value, we can use that to communicate between
// components instead of history's location state.
deletedHighlightSet: true,
});
}
}, [isDeleted, close, highlightSetUUID, enterpriseSlug, history]);

return (
<>
<Button variant="outline-primary" onClick={open}>Delete highlight</Button>
<AlertModal
title="Delete highlight?"
isOpen={isOpen}
onClose={close}
footerNode={(
<ActionRow>
<Button variant="tertiary" onClick={close}>Cancel</Button>
<StatefulButton
labels={{
default: 'Delete highlight',
pending: 'Deleting highlight...',
}}
variant="primary"
state={deletionState}
onClick={handleDeleteClick}
data-testid="delete-confirmation-button"
/>
</ActionRow>
)}
>
<Alert
show={!!deletionError}
onClose={() => setDeletionError(null)}
variant="danger"
dismissible
icon={Info}
>
<p>
An error occurred while deleting this highlight collection. Please try again.
</p>
</Alert>
<p>
Deleting this highlight collection will remove it from your
learners. This action is permanent and cannot be undone.
</p>
</AlertModal>
</>
);
}

DeleteHighlightSet.propTypes = {
enterpriseSlug: PropTypes.string.isRequired,
};

const mapStateToProps = (state) => ({
enterpriseSlug: state.portalConfiguration.enterpriseSlug,
});

export default connect(mapStateToProps)(DeleteHighlightSet);
2 changes: 1 addition & 1 deletion src/components/ContentHighlights/HighlightSetSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const HighlightSetSection = ({
<ContentHighlightSetCard
key={uuid}
title={title}
highlightUUID={uuid}
highlightSetUUID={uuid}
isPublished={isPublished}
itemCount={highlightedContentUuids.length}
imageCapSrc="https://source.unsplash.com/360x200/?cat,dog"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const ContentHighlightStepper = ({ isModalOpen }) => {
<>
<Stepper activeKey={currentStep}>
<FullscreenModal
title="New Highlight"
title="New highlight"
className="bg-light-200"
isOpen={isModalOpen}
onClose={() => {
Expand Down
22 changes: 8 additions & 14 deletions src/components/ContentHighlights/ZeroState/ZeroStateHighlights.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,19 @@ const ZeroStateHighlights = ({ cardClassName }) => {
const { dispatch, stepperModal: { isOpen } } = useContext(ContentHighlightsContext);
return (
<Row>
<Col xs={12} sm={8} lg={5}>
<Col xs={12} sm={10} md={9} lg={8} xl={5}>
<Card className={cardClassName}>
<ZeroStateCardImage imageContainerClassName="bg-light-400 p-4" cardImage={cardImage} />
<ZeroStateCardText textContainerClassName="text-center w-75 align-self-center pb-0">
<h2>You haven&apos;t created any &quot;highlights&quot; collections yet.</h2>
<p className="mb-4">&quot;Highlights&quot; feature allows you to create and recommend course collections to your learners,
<ZeroStateCardText textContainerClassName="text-center align-self-center">
<h2 className="h3 mb-3">You haven&apos;t created any highlights yet.</h2>
<p>
Create and recommend course collections to your learners,
enable them to quickly locate relevant content.
</p>
</ZeroStateCardText>
<ZeroStateCardFooter footerClassName="pb-0 mb-4.5">
<Button
onClick={() => {
dispatch(toggleStepperModalAction({ isOpen: true }));
}}
iconBefore={Add}
block
>
New Highlight
</Button>

<ZeroStateCardFooter>
<Button onClick={() => setIsModalOpen(true)} iconBefore={Add} block>New highlight</Button>
</ZeroStateCardFooter>
</Card>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ const mockStore = configureMockStore([thunk]);

const mockData = {
title: 'Test Title',
highlightUUID: 'test-uuid',
highlightSetUUID: 'test-uuid',
enterpriseSlug: 'test-enterprise-slug',
itemCount: 0,
imageCapSrc: 'http://fake.image',
isPublished: true,
};

const initialState = {
portalConfiguration: {
enterpriseSlug: 'test-enterprise-id',
enterpriseSlug: 'test-enterprise',
},
highlightUUID: 'test-uuid',
highlightSetUUID: 'test-uuid',
};

const ContentHighlightSetCardWrapper = (props) => {
Expand Down Expand Up @@ -58,7 +59,11 @@ describe('<ContentHighlightSetCard>', () => {
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
it('Displays the stepper modal on click of the draft status', () => {
renderWithRouter(<ContentHighlightSetCardWrapper {...mockData} />);
const props = {
...mockData,
isPublished: false,
};
renderWithRouter(<ContentHighlightSetCardWrapper {...props} />);
fireEvent.click(screen.getByText('Test Title'));
expect(screen.getByText(STEPPER_STEP_TEXT.createTitle)).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const mockStore = configureMockStore([thunk]);
const initialState = {
portalConfiguration:
{
enterpriseSlug: 'test-enterprise-id',
enterpriseSlug: 'test-enterprise',
},
};

Expand Down
Loading

0 comments on commit af9ec2a

Please sign in to comment.