Skip to content

Commit 1dc7460

Browse files
committed
feat: move catalogEditorProvider to the router to be able to use it in coursePage
1 parent a1d9f68 commit 1dc7460

File tree

6 files changed

+60
-41
lines changed

6 files changed

+60
-41
lines changed

src/Router.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Route, Router as WouterRouter, Switch } from 'wouter';
22

33
import { lazy, Suspense } from 'react';
44
import { paths } from '@src/constants';
5+
import { CatalogEditionModalProvider } from './catalogs/useCatalogEditionModal';
56

67
const CorporatePartnerPage = lazy(() => import('@src/partner/CorporatePartnerPage'));
78
const PartnerCatalogsPage = lazy(() => import('@src/catalogs/PartnerCatalogsPage'));
@@ -12,8 +13,10 @@ const Router = () => (
1213
<WouterRouter base={paths.base}>
1314
<Switch>
1415
<Route path={paths.partners.path} component={CorporatePartnerPage} />
15-
<Route path={paths.catalogs.path} component={PartnerCatalogsPage} />
16-
<Route path={paths.courses.path} component={CoursesPage} />
16+
<CatalogEditionModalProvider>
17+
<Route path={paths.catalogs.path} component={PartnerCatalogsPage} />
18+
<Route path={paths.courses.path} component={CoursesPage} />
19+
</CatalogEditionModalProvider>
1720
<Route path={paths.courseDetail.path}>
1821
<h1>Course Details</h1>
1922
</Route>

src/app/AppLayout.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Header from '@edx/frontend-component-header';
77
import { FooterSlot } from '@edx/frontend-component-footer';
88

99
import { useNavigate } from '@src/hooks';
10-
import { CatalogEditionModalProvider } from '@src/catalogs/useCatalogEditionModal';
1110
import messages from './messages';
1211

1312
type AppLayoutProps = {
@@ -39,11 +38,8 @@ const AppLayout = ({
3938
</Button>
4039
)}
4140
{title && <h1 className="my-4">{title}</h1>}
42-
<CatalogEditionModalProvider>
43-
{children}
44-
</CatalogEditionModalProvider>
41+
{children}
4542
</Container>
46-
4743
{withFooter && <FooterSlot />}
4844
</>
4945
);

src/app/HeaderDescription.test.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
2-
render, screen, fireEvent, waitFor,
2+
screen, fireEvent, waitFor,
33
} from '@testing-library/react';
4+
import { renderWrapper } from '@src/setupTest';
45
import HeaderDescription from './HeaderDescription';
56

67
jest.mock('@openedx/paragon', () => ({
@@ -32,13 +33,13 @@ describe('HeaderDescription', () => {
3233

3334
describe('Copy to clipboard', () => {
3435
it('renders copy button if copyableDescription is true', () => {
35-
render(<HeaderDescription context={{ ...mockContext, copyableDescription: true }} info={mockInfo} />);
36+
renderWrapper(<HeaderDescription context={{ ...mockContext, copyableDescription: true }} info={mockInfo} />);
3637
const copyButton = screen.getByRole('button', { name: 'Copy description' });
3738
expect(copyButton).toBeInTheDocument();
3839
});
3940

4041
it('copies description to clipboard when copy button is clicked', async () => {
41-
render(<HeaderDescription context={{ ...mockContext, copyableDescription: true }} info={mockInfo} />);
42+
renderWrapper(<HeaderDescription context={{ ...mockContext, copyableDescription: true }} info={mockInfo} />);
4243
const copyButton = screen.getByRole('button', { name: 'Copy description' });
4344
fireEvent.click(copyButton);
4445
await waitFor(() => {
@@ -49,17 +50,17 @@ describe('HeaderDescription', () => {
4950

5051
describe('Basic Rendering', () => {
5152
it('renders the context title', () => {
52-
render(<HeaderDescription context={mockContext} info={mockInfo} />);
53+
renderWrapper(<HeaderDescription context={mockContext} info={mockInfo} />);
5354
expect(screen.getByText(mockContext.title)).toBeInTheDocument();
5455
});
5556

5657
it('renders the context description', () => {
57-
render(<HeaderDescription context={mockContext} info={mockInfo} />);
58+
renderWrapper(<HeaderDescription context={mockContext} info={mockInfo} />);
5859
expect(screen.getByText(mockContext.description)).toBeInTheDocument();
5960
});
6061

6162
it('renders all info items', () => {
62-
render(<HeaderDescription context={mockContext} info={mockInfo} />);
63+
renderWrapper(<HeaderDescription context={mockContext} info={mockInfo} />);
6364

6465
expect(screen.getByText('Catalogs')).toBeInTheDocument();
6566
expect(screen.getByText('5')).toBeInTheDocument();
@@ -70,14 +71,14 @@ describe('HeaderDescription', () => {
7071
});
7172

7273
it('handles empty info array', () => {
73-
render(<HeaderDescription context={mockContext} info={[]} />);
74+
renderWrapper(<HeaderDescription context={mockContext} info={[]} />);
7475
expect(screen.getByText(mockContext.title)).toBeInTheDocument();
7576
});
7677
});
7778

7879
describe('Image Handling', () => {
7980
it('renders image when imageUrl is provided', async () => {
80-
render(<HeaderDescription context={mockContext} info={mockInfo} />);
81+
renderWrapper(<HeaderDescription context={mockContext} info={mockInfo} />);
8182

8283
const imageContainer = await screen.findByTestId('image-with-skeleton');
8384
expect(imageContainer).toBeInTheDocument();
@@ -87,7 +88,7 @@ describe('HeaderDescription', () => {
8788

8889
it('does not render image when imageUrl is null', () => {
8990
const contextWithoutImage = { ...mockContext, imageUrl: null };
90-
render(<HeaderDescription context={contextWithoutImage} info={mockInfo} />);
91+
renderWrapper(<HeaderDescription context={contextWithoutImage} info={mockInfo} />);
9192
expect(screen.queryByTestId('image-with-skeleton')).not.toBeInTheDocument();
9293
});
9394
});
@@ -99,12 +100,12 @@ describe('HeaderDescription', () => {
99100
imageUrl: mockContext.imageUrl,
100101
};
101102

102-
render(<HeaderDescription context={contextWithoutDescription} info={mockInfo} />);
103+
renderWrapper(<HeaderDescription context={contextWithoutDescription} info={mockInfo} />);
103104
expect(screen.getByText(contextWithoutDescription.title)).toBeInTheDocument();
104105
});
105106

106107
it('renders children when provided', () => {
107-
render(
108+
renderWrapper(
108109
<HeaderDescription context={mockContext} info={mockInfo}>
109110
<div data-testid="custom-children">Custom content</div>
110111
</HeaderDescription>,

src/app/HeaderDescription.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { FC, ReactNode, useState } from 'react';
2+
import { useIntl } from '@edx/frontend-platform/i18n';
23
import {
34
breakpoints, Stack, useMediaQuery, IconButtonWithTooltip,
45
} from '@openedx/paragon';
56
import { ContentCopy } from '@openedx/paragon/icons';
67

78
import ImageWithSkeleton from './ImageWithSkeleton';
9+
import messages from './messages';
810

911
interface HeaderDescriptionProps {
1012
context: {
@@ -21,19 +23,20 @@ interface HeaderDescriptionProps {
2123
}
2224

2325
const HeaderDescription: FC<HeaderDescriptionProps> = ({ context, info, children }) => {
26+
const intl = useIntl();
2427
const isSmall = useMediaQuery({ maxWidth: breakpoints.small.maxWidth });
2528
const isMedium = useMediaQuery({ maxWidth: breakpoints.medium.maxWidth });
26-
const [copied, setCopied] = useState('Copy');
29+
const [copied, setCopied] = useState(intl.formatMessage(messages.copyAction));
2730

2831
const handleCopy = async () => {
2932
if (context.copyableDescription && context.description) {
3033
try {
3134
await navigator.clipboard.writeText(context.description);
32-
setCopied('Copied');
33-
setTimeout(() => setCopied('Copy'), 500);
35+
setCopied(intl.formatMessage(messages.copySuccess));
36+
setTimeout(() => setCopied(intl.formatMessage(messages.copyAction)), 500);
3437
} catch (e) {
35-
setCopied('It was not possible to copy, try again');
36-
setTimeout(() => setCopied('Copy'), 1500);
38+
setCopied(intl.formatMessage(messages.copyError));
39+
setTimeout(() => setCopied(intl.formatMessage(messages.copyAction)), 2500);
3740
}
3841
}
3942
};

src/app/messages.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ const messages = defineMessages({
4141
defaultMessage: 'Cancel',
4242
description: 'Text for the cancel button in the app layout',
4343
},
44+
copyAction: {
45+
id: 'app.copy.button',
46+
defaultMessage: 'Copy',
47+
description: 'Text copy text action',
48+
},
49+
copySuccess: {
50+
id: 'app.copy.success',
51+
defaultMessage: 'Cancel',
52+
description: 'Text for copy text sucess',
53+
},
54+
copyError: {
55+
id: 'app.copy.error',
56+
defaultMessage: 'It was not possible to copy, try again',
57+
description: 'Text for copy text error',
58+
},
4459
});
4560

4661
export default messages;

src/courses/CoursesPage.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,37 @@ import { usePartnerDetails } from '@src/partner/hooks';
66
import { IconButton } from '@openedx/paragon';
77
import { LmsEditSquare } from '@openedx/paragon/icons';
88
import { paths } from '@src/constants';
9+
import { useCatalogEditionModal } from '@src/catalogs/useCatalogEditionModal';
910
import CoursesList from './components/CoursesList';
1011
import AppLayout from '../app/AppLayout';
1112

1213
const CoursesPage = () => {
1314
const intl = useIntl();
1415
const { partnerId, catalogId } = useParams<{ partnerId: string, catalogId: string }>();
15-
const { catalogDetails } = useCatalogDetails({ partnerId, catalogId });
16+
const { catalogDetails } = useCatalogDetails({ partnerId, selectedCatalog: catalogId });
1617
const { partnerDetails } = usePartnerDetails({ partnerId });
18+
const { handleChangeSelectedCatalog } = useCatalogEditionModal();
1719

1820
return (
1921
<AppLayout withBackButton backPath={paths.catalogs.buildPath(partnerId)}>
2022
{catalogDetails
2123
&& (
22-
<HeaderDescription
23-
context={{
24-
title: catalogDetails?.name,
25-
imageUrl: partnerDetails?.logo || null,
26-
description: catalogDetails?.catalogAlternativeLink,
27-
copyableDescription: true,
28-
}}
29-
info={[
30-
{ title: intl.formatMessage({ id: 'courses.page.totalCourses', defaultMessage: 'Courses' }), value: catalogDetails?.courses },
31-
{ title: intl.formatMessage({ id: 'courses.page.enrolledEmployees', defaultMessage: 'Enrollments' }), value: catalogDetails?.enrollments },
32-
{ title: intl.formatMessage({ id: 'courses.page.totalCourses', defaultMessage: 'Certified Students' }), value: catalogDetails?.certified },
33-
{ title: intl.formatMessage({ id: 'courses.page.enrolledEmployees', defaultMessage: 'Completion Rate' }), value: catalogDetails?.completionRate },
34-
]}
35-
>
36-
37-
<IconButton src={LmsEditSquare} alt="edit catalog" />
38-
</HeaderDescription>
24+
<HeaderDescription
25+
context={{
26+
title: catalogDetails?.name,
27+
imageUrl: partnerDetails?.logo || null,
28+
description: catalogDetails?.catalogAlternativeLink,
29+
copyableDescription: true,
30+
}}
31+
info={[
32+
{ title: intl.formatMessage({ id: 'courses.page.totalCourses', defaultMessage: 'Courses' }), value: catalogDetails?.courses },
33+
{ title: intl.formatMessage({ id: 'courses.page.enrolledEmployees', defaultMessage: 'Enrollments' }), value: catalogDetails?.enrollments },
34+
{ title: intl.formatMessage({ id: 'courses.page.totalCourses', defaultMessage: 'Certified Students' }), value: catalogDetails?.certified },
35+
{ title: intl.formatMessage({ id: 'courses.page.enrolledEmployees', defaultMessage: 'Completion Rate' }), value: catalogDetails?.completionRate },
36+
]}
37+
>
38+
<IconButton src={LmsEditSquare} alt="edit catalog" onClick={() => handleChangeSelectedCatalog(catalogId)} />
39+
</HeaderDescription>
3940
)}
4041
<CoursesList catalogId={catalogId} partnerId={partnerId} />
4142
</AppLayout>

0 commit comments

Comments
 (0)