Skip to content

Commit

Permalink
Merge branch 'rpenido/fal-3753-library-home-page-bare-bones' into rpe…
Browse files Browse the repository at this point in the history
…nido/fal-3768-create-new-library-form
  • Loading branch information
rpenido committed Jul 10, 2024
2 parents 5f4ebbd + 624309d commit f16c1df
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 46 deletions.
6 changes: 5 additions & 1 deletion src/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { StudioHeader } from '@edx/frontend-component-header';
import { useToggle } from '@openedx/paragon';
import { generatePath, useHref } from 'react-router-dom';

import { SearchModal } from '../search-modal';
import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils';
Expand All @@ -27,6 +28,7 @@ const Header = ({
isLibrary = false,
}: HeaderProps) => {
const intl = useIntl();
const libraryHref = useHref('/library/:libraryId');

const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false);

Expand All @@ -49,7 +51,9 @@ const Header = ({
items: getToolsMenuItems({ studioBaseUrl, courseId: contextId, intl }),
},
] : [];
const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contextId}` : `/course-authoring/library/${contextId}`;
const outlineLink = !isLibrary
? `${studioBaseUrl}/course/${contextId}`
: generatePath(libraryHref, { libraryId: contextId });

return (
<>
Expand Down
39 changes: 16 additions & 23 deletions src/library-authoring/LibraryAuthoringPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { StudioFooter } from '@edx/frontend-component-footer';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand All @@ -19,11 +19,11 @@ import LibraryHome from './LibraryHome';
import { useContentLibrary } from './data/apiHook';
import messages from './messages';

const TAB_LIST = {
home: '',
components: 'components',
collections: 'collections',
};
enum TabList {
home = '',
components = 'components',
collections = 'collections',
}

const SubHeaderTitle = ({ title }: { title: string }) => {
const intl = useIntl();
Expand All @@ -44,21 +44,14 @@ const LibraryAuthoringPage = () => {
const intl = useIntl();
const location = useLocation();
const navigate = useNavigate();
const [tabKey, setTabKey] = useState(TAB_LIST.home);
const [searchKeywords, setSearchKeywords] = useState('');

const { libraryId } = useParams();

const { data: libraryData, isLoading } = useContentLibrary(libraryId);

useEffect(() => {
const currentPath = location.pathname.split('/').pop();
if (currentPath && Object.values(TAB_LIST).includes(currentPath)) {
setTabKey(currentPath);
} else {
setTabKey(TAB_LIST.home);
}
}, [location]);
const currentPath = location.pathname.split('/').pop();
const activeKey = (currentPath && currentPath in TabList) ? TabList[currentPath] : TabList.home;

if (isLoading) {
return <Loading />;
Expand All @@ -69,7 +62,7 @@ const LibraryAuthoringPage = () => {
}

const handleTabChange = (key: string) => {
setTabKey(key);
// setTabKey(key);
navigate(key);
};

Expand All @@ -96,25 +89,25 @@ const LibraryAuthoringPage = () => {
/>
<Tabs
variant="tabs"
activeKey={tabKey}
activeKey={activeKey}
onSelect={handleTabChange}
className="my-3"
>
<Tab eventKey={TAB_LIST.home} title={intl.formatMessage(messages.homeTab)} />
<Tab eventKey={TAB_LIST.components} title={intl.formatMessage(messages.componentsTab)} />
<Tab eventKey={TAB_LIST.collections} title={intl.formatMessage(messages.collectionsTab)} />
<Tab eventKey={TabList.home} title={intl.formatMessage(messages.homeTab)} />
<Tab eventKey={TabList.components} title={intl.formatMessage(messages.componentsTab)} />
<Tab eventKey={TabList.collections} title={intl.formatMessage(messages.collectionsTab)} />
</Tabs>
<Routes>
<Route
path={TAB_LIST.home}
path={TabList.home}
element={<LibraryHome libraryId={libraryId} filter={{ searchKeywords }} />}
/>
<Route
path={TAB_LIST.components}
path={TabList.components}
element={<LibraryComponents libraryId={libraryId} filter={{ searchKeywords }} />}
/>
<Route
path={TAB_LIST.collections}
path={TabList.collections}
element={<LibraryCollections />}
/>
<Route
Expand Down
11 changes: 6 additions & 5 deletions src/library-authoring/LibraryHome.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Card, Stack,
} from '@openedx/paragon';
Expand Down Expand Up @@ -29,6 +29,7 @@ type LibraryHomeProps = {
};

const LibraryHome = ({ libraryId, filter } : LibraryHomeProps) => {
const intl = useIntl();
const { searchKeywords } = filter;
const { componentCount, collectionCount } = useLibraryComponentCount(libraryId, searchKeywords);

Expand All @@ -38,13 +39,13 @@ const LibraryHome = ({ libraryId, filter } : LibraryHomeProps) => {

return (
<Stack gap={3}>
<Section title="Recently Modified">
<FormattedMessage {...messages.recentComponentsTempPlaceholder} />
<Section title={intl.formatMessage(messages.recentlyModifiedTitle)}>
{ intl.formatMessage(messages.recentComponentsTempPlaceholder) }
</Section>
<Section title={`Collections (${collectionCount})`}>
<Section title={intl.formatMessage(messages.collectionsTitle, { collectionCount })}>
<LibraryCollections />
</Section>
<Section title={`Components (${componentCount})`}>
<Section title={intl.formatMessage(messages.componentsTitle, { componentCount })}>
<LibraryComponents libraryId={libraryId} filter={filter} />
</Section>
</Stack>
Expand Down
15 changes: 15 additions & 0 deletions src/library-authoring/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ const messages = defineMessages({
defaultMessage: 'Recently modified components and collections will be displayed here.',
description: 'Temp placeholder for the recent components container. This will be replaced with the actual list.',
},
recentlyModifiedTitle: {
id: 'course-authoring.library-authoring.recently-modified-title',
defaultMessage: 'Recently Modified',
description: 'Title for the recently modified components and collections container',
},
collectionsTitle: {
id: 'course-authoring.library-authoring.collections-title',
defaultMessage: 'Collections ({collectionCount})',
description: 'Title for the collections container',
},
componentsTitle: {
id: 'course-authoring.library-authoring.components-title',
defaultMessage: 'Components ({componentCount})',
description: 'Title for the components container',
},
});

export default messages;
32 changes: 19 additions & 13 deletions src/studio-home/StudioHome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import { Add as AddIcon, Error } from '@openedx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { StudioFooter } from '@edx/frontend-component-footer';
import { getConfig, getPath } from '@edx/frontend-platform';
import { useLocation } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { useLocation, useNavigate } from 'react-router-dom';

import { constructLibraryAuthoringURL } from '../utils';
import Loading from '../generic/Loading';
Expand All @@ -30,6 +30,7 @@ import AlertMessage from '../generic/alert-message';

const StudioHome = ({ intl }) => {
const location = useLocation();
const navigate = useNavigate();

const isPaginationCoursesEnabled = getConfig().ENABLE_HOME_PAGE_COURSE_API_V2;
const {
Expand Down Expand Up @@ -87,31 +88,36 @@ const StudioHome = ({ intl }) => {
);
}

let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`;
if (isMixedOrV2LibrariesMode(libMode) && !v1LibraryTab) {
libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe
? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create')
// Redirection to the placeholder is done in the MFE rather than
// through the backend i.e. redirection from cms, because this this will probably change,
// hence why we use the MFE's origin
: `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/create`;
}
const newLibraryClick = () => {
if (isMixedOrV2LibrariesMode(libMode) && !v1LibraryTab) {
if (libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe) {
// Library authoring MFE
window.open(constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create'));
} else {
// Use course-authoring route
navigate('/library/create');
}
} else {
// Studio home library for legacy libraries
window.open(`${getConfig().STUDIO_BASE_URL}/home_library`);
}
};

headerButtons.push(
<Button
variant="outline-primary"
iconBefore={AddIcon}
size="sm"
disabled={showNewCourseContainer}
href={libraryHref}
onClick={newLibraryClick}
data-testid="new-library-button"
>
{intl.formatMessage(messages.addNewLibraryBtnText)}
</Button>,
);

return headerButtons;
}, [location]);
}, [location, userIsActive, isFailedLoadingPage]);

const headerButtons = userIsActive ? getHeaderButtons() : [];
if (isLoadingPage && !isFiltered) {
Expand Down
39 changes: 35 additions & 4 deletions src/studio-home/StudioHome.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));

const mockNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'), // use actual for all non-hook parts
useNavigate: () => mockNavigate,
}));

const queryClient = new QueryClient();

const RootWrapper = () => (
Expand Down Expand Up @@ -165,7 +172,7 @@ describe('<StudioHome />', () => {
});
});

it('href should include home_library when in "v1 only" lib mode', async () => {
it('should navigate to home_library when in "v1 only" lib mode', () => {
setConfig({
...getConfig(),
LIBRARY_MODE: 'v1 only',
Expand All @@ -178,9 +185,15 @@ describe('<StudioHome />', () => {

const { getByTestId } = render(<RootWrapper />);
const createNewLibraryButton = getByTestId('new-library-button');
expect(createNewLibraryButton.getAttribute('href')).toBe(`${studioBaseUrl}/home_library`);

const { open } = window;
window.open = jest.fn();
fireEvent.click(createNewLibraryButton);
expect(window.open).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`);
window.open = open;
});
it('href should include create', async () => {

it('should navigate to the library authoring mfe', () => {
useSelector.mockReturnValue({
...studioHomeMock,
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
Expand All @@ -191,9 +204,27 @@ describe('<StudioHome />', () => {

const { getByTestId } = render(<RootWrapper />);
const createNewLibraryButton = getByTestId('new-library-button');
expect(createNewLibraryButton.getAttribute('href')).toBe(

const { open } = window;
window.open = jest.fn();
fireEvent.click(createNewLibraryButton);
expect(window.open).toHaveBeenCalledWith(
`${constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create')}`,
);
window.open = open;
});

it('should navigate to the library authoring page in course authoring', () => {
useSelector.mockReturnValue({
...studioHomeMock,
LIBRARY_MODE: 'v2 only',
});
const { getByTestId } = render(<RootWrapper />);
const createNewLibraryButton = getByTestId('new-library-button');

fireEvent.click(createNewLibraryButton);

expect(mockNavigate).toHaveBeenCalledWith('/library/create');
});
});

Expand Down

0 comments on commit f16c1df

Please sign in to comment.