richie-education 3.1.3-dev15 → 3.1.3-dev17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/api/joanie.ts +8 -8
- package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +12 -11
- package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
- package/js/components/CourseGlimpse/utils.ts +28 -22
- package/js/components/CourseGlimpseList/utils.ts +2 -2
- package/js/components/PurchaseButton/index.tsx +3 -3
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +3 -3
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
- package/js/components/SaleTunnel/index.spec.tsx +5 -5
- package/js/components/SaleTunnel/index.tsx +2 -2
- package/js/components/TeacherDashboardCourseList/index.spec.tsx +3 -3
- package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
- package/js/hooks/useContractArchive/index.ts +3 -3
- package/js/hooks/useCourseProductUnion/index.spec.tsx +16 -16
- package/js/hooks/useCourseProductUnion/index.ts +7 -7
- package/js/hooks/useCourseProducts.ts +4 -4
- package/js/hooks/useDefaultOrganizationId/index.tsx +4 -4
- package/js/hooks/useOffering/index.ts +32 -0
- package/js/hooks/useTeacherCoursesSearch/index.tsx +4 -4
- package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
- package/js/pages/DashboardCourses/index.spec.tsx +17 -14
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +11 -8
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +6 -3
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -10
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +5 -5
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -8
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -21
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +19 -13
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
- package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +6 -3
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +7 -4
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -5
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +55 -55
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
- package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
- package/js/pages/TeacherDashboardTraining/index.spec.tsx +25 -25
- package/js/pages/TeacherDashboardTraining/index.tsx +16 -12
- package/js/types/Joanie.ts +21 -19
- package/js/utils/test/factories/joanie.ts +3 -3
- package/js/utils/test/mockCourseProductWithOrder.ts +4 -4
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +3 -3
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +4 -4
- package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +1 -1
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +23 -23
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -4
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +20 -17
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +22 -16
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -3
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +14 -10
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +87 -63
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +2 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +24 -20
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
- package/package.json +1 -1
- package/js/hooks/useOffer/index.ts +0 -32
|
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
4
4
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
5
5
|
import {
|
|
6
6
|
CourseListItemFactory,
|
|
7
|
-
|
|
7
|
+
OfferingFactory,
|
|
8
8
|
OrganizationFactory,
|
|
9
9
|
} from 'utils/test/factories/joanie';
|
|
10
10
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
@@ -59,8 +59,8 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
|
|
|
59
59
|
mockPaginatedResponse(CourseListItemFactory().many(15), 15, false),
|
|
60
60
|
);
|
|
61
61
|
fetchMock.get(
|
|
62
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
63
|
-
mockPaginatedResponse(
|
|
62
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
|
|
63
|
+
mockPaginatedResponse(OfferingFactory().many(15), 15, false),
|
|
64
64
|
);
|
|
65
65
|
fetchMock.get(
|
|
66
66
|
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?signature_state=half_signed&page=1`,
|
|
@@ -76,7 +76,7 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
|
|
|
76
76
|
await expectNoSpinner('Loading courses...');
|
|
77
77
|
|
|
78
78
|
nbApiCalls += 1; // course api call
|
|
79
|
-
nbApiCalls += 1; //
|
|
79
|
+
nbApiCalls += 1; // offerings api call
|
|
80
80
|
nbApiCalls += 1; // contracts api call
|
|
81
81
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
82
82
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
@@ -84,7 +84,7 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
|
|
|
84
84
|
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/courses/?has_listed_course_runs=true&page=1&page_size=${perPage}`,
|
|
85
85
|
);
|
|
86
86
|
expect(calledUrls).toContain(
|
|
87
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
87
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
|
|
88
88
|
);
|
|
89
89
|
await expectNoSpinner('Loading organization...');
|
|
90
90
|
|
|
@@ -117,8 +117,8 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
|
|
|
117
117
|
mockPaginatedResponse(CourseListItemFactory().many(15), 15, false),
|
|
118
118
|
);
|
|
119
119
|
fetchMock.get(
|
|
120
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
121
|
-
mockPaginatedResponse(
|
|
120
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
|
|
121
|
+
mockPaginatedResponse(OfferingFactory().many(15), 15, false),
|
|
122
122
|
);
|
|
123
123
|
|
|
124
124
|
render(<TeacherDashboardOrganizationCourseLoader />, {
|
|
@@ -136,22 +136,22 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
|
|
|
136
136
|
mockPaginatedResponse(CourseListItemFactory().many(5), 5, false),
|
|
137
137
|
);
|
|
138
138
|
fetchMock.get(
|
|
139
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
140
|
-
mockPaginatedResponse(
|
|
139
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
|
|
140
|
+
mockPaginatedResponse(OfferingFactory().many(5), 5, false),
|
|
141
141
|
);
|
|
142
142
|
const user = userEvent.setup();
|
|
143
143
|
await user.type(screen.getByRole('textbox', { name: /Search/ }), 'text query');
|
|
144
144
|
await user.click(screen.getByRole('button', { name: /Search/ }));
|
|
145
145
|
|
|
146
146
|
nbApiCalls = 1; // course api call
|
|
147
|
-
nbApiCalls += 1; //
|
|
147
|
+
nbApiCalls += 1; // offerings api call
|
|
148
148
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
149
149
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
150
150
|
expect(calledUrls).toContain(
|
|
151
151
|
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/courses/?query=text+query&has_listed_course_runs=true&page=1&page_size=${perPage}`,
|
|
152
152
|
);
|
|
153
153
|
expect(calledUrls).toContain(
|
|
154
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
154
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
|
|
155
155
|
);
|
|
156
156
|
|
|
157
157
|
await waitFor(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FormattedMessage, defineMessages } from 'react-intl';
|
|
2
2
|
import { useParams } from 'react-router';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { useOffering } from 'hooks/useOffering';
|
|
5
5
|
import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
|
|
6
6
|
import { TeacherDashboardCourseSidebar } from 'widgets/Dashboard/components/TeacherDashboardCourseSidebar';
|
|
7
7
|
import { Spinner } from 'components/Spinner';
|
|
@@ -22,17 +22,17 @@ const messages = defineMessages({
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
export const TeacherDashboardTrainingLoader = () => {
|
|
25
|
-
const {
|
|
26
|
-
|
|
25
|
+
const { offeringId, organizationId } = useParams<{
|
|
26
|
+
offeringId: string;
|
|
27
27
|
organizationId?: string;
|
|
28
28
|
}>();
|
|
29
29
|
|
|
30
30
|
const {
|
|
31
|
-
item:
|
|
31
|
+
item: offering,
|
|
32
32
|
states: { fetching },
|
|
33
|
-
} =
|
|
33
|
+
} = useOffering(offeringId, { organization_id: organizationId });
|
|
34
34
|
useBreadcrumbsPlaceholders({
|
|
35
|
-
courseTitle:
|
|
35
|
+
courseTitle: offering?.product.title ?? '',
|
|
36
36
|
});
|
|
37
37
|
return (
|
|
38
38
|
<DashboardLayout sidebar={<TeacherDashboardCourseSidebar />}>
|
|
@@ -48,7 +48,7 @@ export const TeacherDashboardTrainingLoader = () => {
|
|
|
48
48
|
</span>
|
|
49
49
|
</Spinner>
|
|
50
50
|
) : (
|
|
51
|
-
<TeacherDashboardTraining
|
|
51
|
+
<TeacherDashboardTraining offering={offering} />
|
|
52
52
|
)}
|
|
53
53
|
</DashboardLayout>
|
|
54
54
|
);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
UserFactory,
|
|
12
12
|
} from 'utils/test/factories/richie';
|
|
13
13
|
import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
|
|
14
|
-
import {
|
|
14
|
+
import { OfferingFactory, OrganizationFactory } from 'utils/test/factories/joanie';
|
|
15
15
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
16
16
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
17
17
|
import { DashboardBreadcrumbsProvider } from 'widgets/Dashboard/contexts/DashboardBreadcrumbsContext';
|
|
@@ -46,9 +46,9 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('should render TeacherDashboardTrainingLoader page', async () => {
|
|
49
|
-
const
|
|
50
|
-
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/?
|
|
51
|
-
fetchMock.get(`https://joanie.endpoint/api/v1.0/
|
|
49
|
+
const offering = OfferingFactory().one();
|
|
50
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/?offering_id=${offering.id}`, []);
|
|
51
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/`, offering);
|
|
52
52
|
|
|
53
53
|
const user = UserFactory().one();
|
|
54
54
|
render(
|
|
@@ -61,12 +61,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
61
61
|
router={createMemoryRouter(
|
|
62
62
|
[
|
|
63
63
|
{
|
|
64
|
-
path: ':
|
|
64
|
+
path: ':offeringId',
|
|
65
65
|
element: <TeacherDashboardTrainingLoader />,
|
|
66
66
|
},
|
|
67
67
|
],
|
|
68
68
|
{
|
|
69
|
-
initialEntries: [`/${
|
|
69
|
+
initialEntries: [`/${offering.id}`],
|
|
70
70
|
},
|
|
71
71
|
)}
|
|
72
72
|
/>
|
|
@@ -82,10 +82,10 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
82
82
|
await expectNoSpinner('Loading course...');
|
|
83
83
|
|
|
84
84
|
nbApiCalls += 1; // organizations api call
|
|
85
|
-
nbApiCalls += 1; //
|
|
85
|
+
nbApiCalls += 1; // offerings api call
|
|
86
86
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
87
87
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
88
|
-
expect(calledUrls).toContain(`https://joanie.endpoint/api/v1.0/
|
|
88
|
+
expect(calledUrls).toContain(`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/`);
|
|
89
89
|
|
|
90
90
|
// main titles
|
|
91
91
|
expect(
|
|
@@ -94,28 +94,28 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
94
94
|
}),
|
|
95
95
|
).toBeInTheDocument();
|
|
96
96
|
|
|
97
|
-
expect(
|
|
98
|
-
|
|
99
|
-
);
|
|
97
|
+
expect(
|
|
98
|
+
screen.getAllByRole('heading', { name: capitalize(offering.product.title) }),
|
|
99
|
+
).toHaveLength(2);
|
|
100
100
|
|
|
101
|
-
const nbCourseRun =
|
|
101
|
+
const nbCourseRun = offering.product.target_courses.reduce(
|
|
102
102
|
(acc, course) => acc + course.course_runs.length,
|
|
103
103
|
0,
|
|
104
104
|
);
|
|
105
105
|
expect(screen.getAllByRole('link', { name: 'Go to course area' })).toHaveLength(nbCourseRun);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
it('should fetch
|
|
108
|
+
it('should fetch offering with organization id if there is one in the path', async () => {
|
|
109
109
|
const organization = OrganizationFactory().one();
|
|
110
|
-
const
|
|
110
|
+
const offering = OfferingFactory({
|
|
111
111
|
organizations: [organization],
|
|
112
112
|
}).one();
|
|
113
113
|
fetchMock.get(
|
|
114
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
115
|
-
|
|
114
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/${offering.id}/`,
|
|
115
|
+
offering,
|
|
116
116
|
);
|
|
117
117
|
fetchMock.get(
|
|
118
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?
|
|
118
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?offering_id=${offering.id}&signature_state=half_signed&page=1&page_size=25`,
|
|
119
119
|
[],
|
|
120
120
|
);
|
|
121
121
|
|
|
@@ -130,12 +130,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
130
130
|
router={createMemoryRouter(
|
|
131
131
|
[
|
|
132
132
|
{
|
|
133
|
-
path: '/:organizationId/:
|
|
133
|
+
path: '/:organizationId/:offeringId',
|
|
134
134
|
element: <TeacherDashboardTrainingLoader />,
|
|
135
135
|
},
|
|
136
136
|
],
|
|
137
137
|
{
|
|
138
|
-
initialEntries: [`/${organization.id}/${
|
|
138
|
+
initialEntries: [`/${organization.id}/${offering.id}`],
|
|
139
139
|
},
|
|
140
140
|
)}
|
|
141
141
|
/>
|
|
@@ -151,11 +151,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
151
151
|
await expectNoSpinner('Loading course...');
|
|
152
152
|
|
|
153
153
|
nbApiCalls += 1; // contracts api call
|
|
154
|
-
nbApiCalls += 1; //
|
|
154
|
+
nbApiCalls += 1; // offerings api call
|
|
155
155
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
156
156
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
157
157
|
expect(calledUrls).toContain(
|
|
158
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
158
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/${offering.id}/`,
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
// main titles
|
|
@@ -165,11 +165,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
165
165
|
}),
|
|
166
166
|
).toBeInTheDocument();
|
|
167
167
|
|
|
168
|
-
expect(
|
|
169
|
-
|
|
170
|
-
);
|
|
168
|
+
expect(
|
|
169
|
+
screen.getAllByRole('heading', { name: capitalize(offering.product.title) }),
|
|
170
|
+
).toHaveLength(2);
|
|
171
171
|
|
|
172
|
-
const nbCourseRun =
|
|
172
|
+
const nbCourseRun = offering.product.target_courses.reduce(
|
|
173
173
|
(acc, course) => acc + course.course_runs.length,
|
|
174
174
|
0,
|
|
175
175
|
);
|
|
@@ -6,33 +6,33 @@ import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
|
|
|
6
6
|
import { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';
|
|
7
7
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
8
8
|
import Banner, { BannerType } from 'components/Banner';
|
|
9
|
-
import {
|
|
9
|
+
import { Offering } from 'types/Joanie';
|
|
10
10
|
|
|
11
11
|
const messages = defineMessages({
|
|
12
|
-
|
|
12
|
+
errorNoOffering: {
|
|
13
13
|
defaultMessage: "This product doesn't exist",
|
|
14
|
-
description: 'Message displayed when requested
|
|
15
|
-
id: 'components.TeacherDashboardTraining.
|
|
14
|
+
description: 'Message displayed when requested offering is not found',
|
|
15
|
+
id: 'components.TeacherDashboardTraining.errorNoOffering',
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
interface TeacherDashboardTrainingProps {
|
|
20
|
-
|
|
20
|
+
offering: Offering;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const TeacherDashboardTraining = ({
|
|
23
|
+
export const TeacherDashboardTraining = ({ offering }: TeacherDashboardTrainingProps) => {
|
|
24
24
|
const intl = useIntl();
|
|
25
|
-
return
|
|
25
|
+
return offering ? (
|
|
26
26
|
<div className="teacher-course-page">
|
|
27
27
|
<DashboardCard
|
|
28
28
|
className="icon-arrow-right-rounded"
|
|
29
29
|
header={
|
|
30
30
|
<div>
|
|
31
31
|
<div className="dashboard__title_container--small">
|
|
32
|
-
<h2 className="dashboard__title--large">{capitalize(
|
|
32
|
+
<h2 className="dashboard__title--large">{capitalize(offering.product.title)}</h2>
|
|
33
33
|
</div>
|
|
34
|
-
{
|
|
35
|
-
<div className="dashboard__quote">{
|
|
34
|
+
{offering.product.description && (
|
|
35
|
+
<div className="dashboard__quote">{offering.product.description}</div>
|
|
36
36
|
)}
|
|
37
37
|
</div>
|
|
38
38
|
}
|
|
@@ -40,7 +40,7 @@ export const TeacherDashboardTraining = ({ offer }: TeacherDashboardTrainingProp
|
|
|
40
40
|
fullWidth
|
|
41
41
|
/>
|
|
42
42
|
<DashboardLayout.NestedSection>
|
|
43
|
-
{
|
|
43
|
+
{offering.product.target_courses.map((course) => (
|
|
44
44
|
<DashboardLayout.Section key={`course_target_${course.code}`}>
|
|
45
45
|
<DashboardCard
|
|
46
46
|
className="icon-arrow-right-rounded"
|
|
@@ -62,7 +62,11 @@ export const TeacherDashboardTraining = ({ offer }: TeacherDashboardTrainingProp
|
|
|
62
62
|
</DashboardLayout.NestedSection>
|
|
63
63
|
</div>
|
|
64
64
|
) : (
|
|
65
|
-
<Banner
|
|
65
|
+
<Banner
|
|
66
|
+
message={intl.formatMessage(messages.errorNoOffering)}
|
|
67
|
+
type={BannerType.ERROR}
|
|
68
|
+
rounded
|
|
69
|
+
/>
|
|
66
70
|
);
|
|
67
71
|
};
|
|
68
72
|
|
package/js/types/Joanie.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface Organization {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface OrganizationResourceQuery extends ResourcesQuery {
|
|
41
|
-
|
|
41
|
+
offering_id?: Offering['id'];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface ContractDefinition {
|
|
@@ -174,7 +174,7 @@ export interface DefinitionResourcesProduct {
|
|
|
174
174
|
contract_definition_id: Nullable<ContractDefinition['id']>;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
export interface
|
|
177
|
+
export interface OfferingLight {
|
|
178
178
|
id: string;
|
|
179
179
|
course: CourseLight;
|
|
180
180
|
organizations: Organization[];
|
|
@@ -182,7 +182,7 @@ export interface OfferLight {
|
|
|
182
182
|
created_on: string;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
export interface
|
|
185
|
+
export interface OfferingRule {
|
|
186
186
|
discounted_price: Nullable<number>;
|
|
187
187
|
discount_rate: Nullable<number>;
|
|
188
188
|
discount_amount: Nullable<number>;
|
|
@@ -194,11 +194,13 @@ export interface OfferRule {
|
|
|
194
194
|
has_seats_left: boolean;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
export interface
|
|
197
|
+
export interface Offering extends OfferingLight {
|
|
198
198
|
is_withdrawable: boolean;
|
|
199
|
-
rules:
|
|
199
|
+
rules: OfferingRule;
|
|
200
200
|
}
|
|
201
|
-
export function
|
|
201
|
+
export function isOffering(
|
|
202
|
+
entity: CourseListItem | OfferingLight | RichieCourse,
|
|
203
|
+
): entity is OfferingLight {
|
|
202
204
|
return 'course' in entity && 'product' in entity;
|
|
203
205
|
}
|
|
204
206
|
|
|
@@ -243,7 +245,7 @@ export interface Enrollment {
|
|
|
243
245
|
was_created_by_order: boolean;
|
|
244
246
|
created_on: string;
|
|
245
247
|
orders: OrderEnrollment[];
|
|
246
|
-
|
|
248
|
+
offerings: Offering[];
|
|
247
249
|
certificate_id: Nullable<string>;
|
|
248
250
|
}
|
|
249
251
|
export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
|
|
@@ -257,7 +259,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
|
|
|
257
259
|
'was_created_by_order' in obj &&
|
|
258
260
|
'created_on' in obj &&
|
|
259
261
|
'orders' in obj &&
|
|
260
|
-
'
|
|
262
|
+
'offerings' in obj &&
|
|
261
263
|
'certificate_id' in obj
|
|
262
264
|
);
|
|
263
265
|
};
|
|
@@ -407,7 +409,7 @@ export interface NestedCourseOrder {
|
|
|
407
409
|
|
|
408
410
|
export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
|
|
409
411
|
course_id?: CourseListItem['id'];
|
|
410
|
-
|
|
412
|
+
offering_id?: Offering['id'];
|
|
411
413
|
organization_id?: Organization['id'];
|
|
412
414
|
product_id?: Product['id'];
|
|
413
415
|
}
|
|
@@ -542,8 +544,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
|
|
|
542
544
|
id?: Product['id'];
|
|
543
545
|
course_id?: CourseListItem['id'];
|
|
544
546
|
}
|
|
545
|
-
export interface
|
|
546
|
-
id?:
|
|
547
|
+
export interface OfferingQueryFilters extends PaginatedResourceQuery {
|
|
548
|
+
id?: Offering['id'];
|
|
547
549
|
organization_id?: Organization['id'];
|
|
548
550
|
product_type?: ProductType;
|
|
549
551
|
query?: string;
|
|
@@ -556,7 +558,7 @@ export enum ContractState {
|
|
|
556
558
|
}
|
|
557
559
|
export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
558
560
|
organization_id?: Organization['id'];
|
|
559
|
-
|
|
561
|
+
offering_id?: Offering['id'];
|
|
560
562
|
contract_ids?: Contract['id'][];
|
|
561
563
|
signature_state?: ContractState;
|
|
562
564
|
}
|
|
@@ -564,7 +566,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
|
564
566
|
export interface OrganizationContractSignatureLinksFilters {
|
|
565
567
|
contracts_ids?: string[];
|
|
566
568
|
organization_id: Organization['id'];
|
|
567
|
-
|
|
569
|
+
offering_ids?: Offering['id'][];
|
|
568
570
|
}
|
|
569
571
|
|
|
570
572
|
export interface ContractInvitationLinkResponse {
|
|
@@ -658,10 +660,10 @@ interface APIUser {
|
|
|
658
660
|
check: (id: string) => Promise<Response>;
|
|
659
661
|
create: ({
|
|
660
662
|
organization_id,
|
|
661
|
-
|
|
663
|
+
offering_id,
|
|
662
664
|
}: {
|
|
663
665
|
organization_id?: Organization['id'];
|
|
664
|
-
|
|
666
|
+
offering_id?: Offering['id'];
|
|
665
667
|
}) => Promise<{ url: string }>;
|
|
666
668
|
get: (id: string) => Promise<File>;
|
|
667
669
|
};
|
|
@@ -677,7 +679,7 @@ export interface API {
|
|
|
677
679
|
? Promise<Nullable<CourseListItem>>
|
|
678
680
|
: Promise<PaginatedResponse<CourseListItem>>;
|
|
679
681
|
products: {
|
|
680
|
-
get(filters?: CourseProductQueryFilters): Promise<Nullable<
|
|
682
|
+
get(filters?: CourseProductQueryFilters): Promise<Nullable<Offering>>;
|
|
681
683
|
paymentSchedule: {
|
|
682
684
|
get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
|
|
683
685
|
};
|
|
@@ -710,12 +712,12 @@ export interface API {
|
|
|
710
712
|
filters?: CourseRunFilters,
|
|
711
713
|
): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
|
|
712
714
|
};
|
|
713
|
-
|
|
715
|
+
offerings: {
|
|
714
716
|
get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
|
|
715
717
|
filters?: Filters,
|
|
716
718
|
): Filters extends { id: string }
|
|
717
|
-
? Promise<Nullable<
|
|
718
|
-
: Promise<PaginatedResponse<
|
|
719
|
+
? Promise<Nullable<Offering>>
|
|
720
|
+
: Promise<PaginatedResponse<OfferingLight>>;
|
|
719
721
|
};
|
|
720
722
|
contractDefinitions: {
|
|
721
723
|
previewTemplate(id: string): Promise<File>;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
CourseLight,
|
|
13
13
|
CourseListItem,
|
|
14
14
|
CourseProduct,
|
|
15
|
-
|
|
15
|
+
Offering,
|
|
16
16
|
CourseRun,
|
|
17
17
|
CredentialOrder,
|
|
18
18
|
CredentialProduct,
|
|
@@ -89,7 +89,7 @@ export const EnrollmentFactory = factory((): Enrollment => {
|
|
|
89
89
|
id: faker.string.uuid(),
|
|
90
90
|
course_run: CourseRunWithCourseFactory().one(),
|
|
91
91
|
is_active: true,
|
|
92
|
-
|
|
92
|
+
offerings: OfferingFactory().many(1),
|
|
93
93
|
state: EnrollmentState.SET,
|
|
94
94
|
was_created_by_order: false,
|
|
95
95
|
created_on: faker.date.past({ years: 1 }).toISOString(),
|
|
@@ -309,7 +309,7 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
|
|
|
309
309
|
};
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
-
export const
|
|
312
|
+
export const OfferingFactory = factory((): Offering => {
|
|
313
313
|
return {
|
|
314
314
|
id: faker.string.uuid(),
|
|
315
315
|
created_on: faker.date.past().toISOString(),
|
|
@@ -3,14 +3,14 @@ import { CredentialOrder } from 'types/Joanie';
|
|
|
3
3
|
import {
|
|
4
4
|
ContractDefinitionFactory,
|
|
5
5
|
CourseFactory,
|
|
6
|
-
|
|
6
|
+
OfferingFactory,
|
|
7
7
|
ProductFactory,
|
|
8
8
|
} from 'utils/test/factories/joanie';
|
|
9
9
|
|
|
10
10
|
export const mockCourseProductWithOrder = (order: CredentialOrder) => {
|
|
11
11
|
const courseCode = order.course.code;
|
|
12
12
|
const productId = order.product_id;
|
|
13
|
-
const
|
|
13
|
+
const offering = OfferingFactory({
|
|
14
14
|
product: ProductFactory({
|
|
15
15
|
id: order.product_id,
|
|
16
16
|
contract_definition: order.contract ? ContractDefinitionFactory().one() : undefined,
|
|
@@ -22,7 +22,7 @@ export const mockCourseProductWithOrder = (order: CredentialOrder) => {
|
|
|
22
22
|
|
|
23
23
|
fetchMock.get(
|
|
24
24
|
`https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${productId}/`,
|
|
25
|
-
|
|
25
|
+
offering,
|
|
26
26
|
);
|
|
27
|
-
return
|
|
27
|
+
return offering;
|
|
28
28
|
};
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx
CHANGED
|
@@ -33,7 +33,7 @@ export const DashboardItemEnrollment = ({ enrollment }: DashboardItemCourseRunPr
|
|
|
33
33
|
</div>
|
|
34
34
|
</div>,
|
|
35
35
|
];
|
|
36
|
-
enrollment.
|
|
36
|
+
enrollment.offerings.forEach(({ product, is_withdrawable }) => {
|
|
37
37
|
if (isCertificateProduct(product)) {
|
|
38
38
|
partialFooterList.push(
|
|
39
39
|
<ProductCertificateFooter
|
|
@@ -249,7 +249,7 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
249
249
|
course,
|
|
250
250
|
}).one(),
|
|
251
251
|
}).one();
|
|
252
|
-
enrollment.
|
|
252
|
+
enrollment.offerings[0].product = CertificateProductFactory().one();
|
|
253
253
|
|
|
254
254
|
fetchMock.get(
|
|
255
255
|
`https://joanie.endpoint/api/v1.0/enrollments/?was_created_by_order=false&is_active=true&page=1&page_size=${PER_PAGE.useOrdersEnrollments}`,
|
|
@@ -67,15 +67,15 @@ export const DashboardItemOrder = ({
|
|
|
67
67
|
}: DashboardItemOrderProps) => {
|
|
68
68
|
const { course } = order;
|
|
69
69
|
const intl = useIntl();
|
|
70
|
-
const { item:
|
|
70
|
+
const { item: offering } = useCourseProduct({
|
|
71
71
|
product_id: order.product_id,
|
|
72
72
|
course_id: course.code,
|
|
73
73
|
});
|
|
74
|
-
const { product } =
|
|
74
|
+
const { product } = offering || {};
|
|
75
75
|
const needsSignature = OrderHelper.orderNeedsSignature(order);
|
|
76
76
|
const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
|
|
77
77
|
const isActive = OrderHelper.isActive(order);
|
|
78
|
-
const isProductPurchasable = ProductHelper.isPurchasable(
|
|
78
|
+
const isProductPurchasable = ProductHelper.isPurchasable(offering?.product);
|
|
79
79
|
const isNotResumable = !isActive && !isProductPurchasable;
|
|
80
80
|
const canEnroll = OrderHelper.allowEnrollment(order);
|
|
81
81
|
|
|
@@ -57,7 +57,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
57
57
|
contract: ContractFactory({ student_signed_on: null }).one(),
|
|
58
58
|
}).one();
|
|
59
59
|
|
|
60
|
-
// learner dashboard course page do one call to
|
|
60
|
+
// learner dashboard course page do one call to offering per order
|
|
61
61
|
const { product } = mockCourseProductWithOrder(order);
|
|
62
62
|
|
|
63
63
|
// overwrite useOmniscientOrders call
|
|
@@ -94,7 +94,7 @@ const Installment = ({ order }: Props) => {
|
|
|
94
94
|
|
|
95
95
|
const PaymentMethodManager = ({ order }: Props) => {
|
|
96
96
|
const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
|
|
97
|
-
const { item:
|
|
97
|
+
const { item: offering, states } = useCourseProduct({
|
|
98
98
|
course_id: order.course.code,
|
|
99
99
|
product_id: order.product_id,
|
|
100
100
|
});
|
|
@@ -118,9 +118,9 @@ const PaymentMethodManager = ({ order }: Props) => {
|
|
|
118
118
|
)}
|
|
119
119
|
<SaleTunnel
|
|
120
120
|
{...modal}
|
|
121
|
-
product={
|
|
122
|
-
course={
|
|
123
|
-
isWithdrawable={
|
|
121
|
+
product={offering.product as CredentialProduct}
|
|
122
|
+
course={offering.course}
|
|
123
|
+
isWithdrawable={offering.is_withdrawable}
|
|
124
124
|
/>
|
|
125
125
|
</>
|
|
126
126
|
);
|