richie-education 3.1.3-dev11 → 3.1.3-dev12
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 +11 -20
- package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
- package/js/components/CourseGlimpse/utils.ts +22 -35
- package/js/components/CourseGlimpseList/utils.ts +2 -2
- package/js/components/PurchaseButton/index.tsx +3 -3
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +3 -10
- 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 -18
- package/js/hooks/useCourseProductUnion/index.ts +7 -7
- package/js/hooks/useCourseProducts.ts +4 -8
- package/js/hooks/useDefaultOrganizationId/index.tsx +4 -7
- package/js/hooks/useOffer/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 +14 -17
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +8 -14
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +4 -12
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -13
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +20 -28
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -11
- 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 -28
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +13 -23
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -13
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
- package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +3 -6
- 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 +4 -7
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -10
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +61 -79
- 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 -33
- package/js/pages/TeacherDashboardTraining/index.tsx +12 -20
- package/js/types/Joanie.ts +17 -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 -28
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -8
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +17 -27
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +16 -25
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -7
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +9 -13
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +63 -87
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +2 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +20 -34
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
- package/package.json +1 -1
- package/js/hooks/useCourseProductRelation/index.ts +0 -44
|
@@ -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
|
+
OfferFactory,
|
|
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}/offers/?product_type=credential&page=1&page_size=${perPage}`,
|
|
63
|
+
mockPaginatedResponse(OfferFactory().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; // offers 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}/offers/?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}/offers/?product_type=credential&page=1&page_size=${perPage}`,
|
|
121
|
+
mockPaginatedResponse(OfferFactory().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}/offers/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
|
|
140
|
+
mockPaginatedResponse(OfferFactory().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; // offers 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}/offers/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
|
|
155
155
|
);
|
|
156
156
|
|
|
157
157
|
await waitFor(() => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { FormattedMessage, defineMessages } from 'react-intl';
|
|
2
2
|
import { useParams } from 'react-router';
|
|
3
3
|
|
|
4
|
+
import { useOffer } from 'hooks/useOffer';
|
|
4
5
|
import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
|
|
5
6
|
import { TeacherDashboardCourseSidebar } from 'widgets/Dashboard/components/TeacherDashboardCourseSidebar';
|
|
6
7
|
import { Spinner } from 'components/Spinner';
|
|
7
|
-
import { useCourseProductRelation } from 'hooks/useCourseProductRelation';
|
|
8
8
|
import { useBreadcrumbsPlaceholders } from 'hooks/useBreadcrumbsPlaceholders';
|
|
9
9
|
import { TeacherDashboardTraining } from '.';
|
|
10
10
|
|
|
@@ -22,17 +22,17 @@ const messages = defineMessages({
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
export const TeacherDashboardTrainingLoader = () => {
|
|
25
|
-
const {
|
|
26
|
-
|
|
25
|
+
const { offerId, organizationId } = useParams<{
|
|
26
|
+
offerId: string;
|
|
27
27
|
organizationId?: string;
|
|
28
28
|
}>();
|
|
29
29
|
|
|
30
30
|
const {
|
|
31
|
-
item:
|
|
31
|
+
item: offer,
|
|
32
32
|
states: { fetching },
|
|
33
|
-
} =
|
|
33
|
+
} = useOffer(offerId, { organization_id: organizationId });
|
|
34
34
|
useBreadcrumbsPlaceholders({
|
|
35
|
-
courseTitle:
|
|
35
|
+
courseTitle: offer?.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 offer={offer} />
|
|
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 { OfferFactory, 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,15 +46,9 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('should render TeacherDashboardTrainingLoader page', async () => {
|
|
49
|
-
const
|
|
50
|
-
fetchMock.get(
|
|
51
|
-
|
|
52
|
-
[],
|
|
53
|
-
);
|
|
54
|
-
fetchMock.get(
|
|
55
|
-
`https://joanie.endpoint/api/v1.0/course-product-relations/${courseProductRelation.id}/`,
|
|
56
|
-
courseProductRelation,
|
|
57
|
-
);
|
|
49
|
+
const offer = OfferFactory().one();
|
|
50
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/?offer_id=${offer.id}`, []);
|
|
51
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/offers/${offer.id}/`, offer);
|
|
58
52
|
|
|
59
53
|
const user = UserFactory().one();
|
|
60
54
|
render(
|
|
@@ -67,12 +61,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
67
61
|
router={createMemoryRouter(
|
|
68
62
|
[
|
|
69
63
|
{
|
|
70
|
-
path: ':
|
|
64
|
+
path: ':offerId',
|
|
71
65
|
element: <TeacherDashboardTrainingLoader />,
|
|
72
66
|
},
|
|
73
67
|
],
|
|
74
68
|
{
|
|
75
|
-
initialEntries: [`/${
|
|
69
|
+
initialEntries: [`/${offer.id}`],
|
|
76
70
|
},
|
|
77
71
|
)}
|
|
78
72
|
/>
|
|
@@ -88,12 +82,10 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
88
82
|
await expectNoSpinner('Loading course...');
|
|
89
83
|
|
|
90
84
|
nbApiCalls += 1; // organizations api call
|
|
91
|
-
nbApiCalls += 1; //
|
|
85
|
+
nbApiCalls += 1; // offers api call
|
|
92
86
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
93
87
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
94
|
-
expect(calledUrls).toContain(
|
|
95
|
-
`https://joanie.endpoint/api/v1.0/course-product-relations/${courseProductRelation.id}/`,
|
|
96
|
-
);
|
|
88
|
+
expect(calledUrls).toContain(`https://joanie.endpoint/api/v1.0/offers/${offer.id}/`);
|
|
97
89
|
|
|
98
90
|
// main titles
|
|
99
91
|
expect(
|
|
@@ -102,28 +94,28 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
102
94
|
}),
|
|
103
95
|
).toBeInTheDocument();
|
|
104
96
|
|
|
105
|
-
expect(
|
|
106
|
-
|
|
107
|
-
)
|
|
97
|
+
expect(screen.getAllByRole('heading', { name: capitalize(offer.product.title) })).toHaveLength(
|
|
98
|
+
2,
|
|
99
|
+
);
|
|
108
100
|
|
|
109
|
-
const nbCourseRun =
|
|
101
|
+
const nbCourseRun = offer.product.target_courses.reduce(
|
|
110
102
|
(acc, course) => acc + course.course_runs.length,
|
|
111
103
|
0,
|
|
112
104
|
);
|
|
113
105
|
expect(screen.getAllByRole('link', { name: 'Go to course area' })).toHaveLength(nbCourseRun);
|
|
114
106
|
});
|
|
115
107
|
|
|
116
|
-
it('should fetch
|
|
108
|
+
it('should fetch offer with organization id if there is one in the path', async () => {
|
|
117
109
|
const organization = OrganizationFactory().one();
|
|
118
|
-
const
|
|
110
|
+
const offer = OfferFactory({
|
|
119
111
|
organizations: [organization],
|
|
120
112
|
}).one();
|
|
121
113
|
fetchMock.get(
|
|
122
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
123
|
-
|
|
114
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offers/${offer.id}/`,
|
|
115
|
+
offer,
|
|
124
116
|
);
|
|
125
117
|
fetchMock.get(
|
|
126
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?
|
|
118
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?offer_id=${offer.id}&signature_state=half_signed&page=1&page_size=25`,
|
|
127
119
|
[],
|
|
128
120
|
);
|
|
129
121
|
|
|
@@ -138,12 +130,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
138
130
|
router={createMemoryRouter(
|
|
139
131
|
[
|
|
140
132
|
{
|
|
141
|
-
path: '/:organizationId/:
|
|
133
|
+
path: '/:organizationId/:offerId',
|
|
142
134
|
element: <TeacherDashboardTrainingLoader />,
|
|
143
135
|
},
|
|
144
136
|
],
|
|
145
137
|
{
|
|
146
|
-
initialEntries: [`/${organization.id}/${
|
|
138
|
+
initialEntries: [`/${organization.id}/${offer.id}`],
|
|
147
139
|
},
|
|
148
140
|
)}
|
|
149
141
|
/>
|
|
@@ -159,11 +151,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
159
151
|
await expectNoSpinner('Loading course...');
|
|
160
152
|
|
|
161
153
|
nbApiCalls += 1; // contracts api call
|
|
162
|
-
nbApiCalls += 1; //
|
|
154
|
+
nbApiCalls += 1; // offers api call
|
|
163
155
|
const calledUrls = fetchMock.calls().map((call) => call[0]);
|
|
164
156
|
expect(calledUrls).toHaveLength(nbApiCalls);
|
|
165
157
|
expect(calledUrls).toContain(
|
|
166
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
158
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offers/${offer.id}/`,
|
|
167
159
|
);
|
|
168
160
|
|
|
169
161
|
// main titles
|
|
@@ -173,11 +165,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
|
|
|
173
165
|
}),
|
|
174
166
|
).toBeInTheDocument();
|
|
175
167
|
|
|
176
|
-
expect(
|
|
177
|
-
|
|
178
|
-
)
|
|
168
|
+
expect(screen.getAllByRole('heading', { name: capitalize(offer.product.title) })).toHaveLength(
|
|
169
|
+
2,
|
|
170
|
+
);
|
|
179
171
|
|
|
180
|
-
const nbCourseRun =
|
|
172
|
+
const nbCourseRun = offer.product.target_courses.reduce(
|
|
181
173
|
(acc, course) => acc + course.course_runs.length,
|
|
182
174
|
0,
|
|
183
175
|
);
|
|
@@ -6,37 +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 { Offer } from 'types/Joanie';
|
|
10
10
|
|
|
11
11
|
const messages = defineMessages({
|
|
12
|
-
|
|
12
|
+
errorNoOffer: {
|
|
13
13
|
defaultMessage: "This product doesn't exist",
|
|
14
|
-
description: 'Message displayed when requested
|
|
15
|
-
id: 'components.TeacherDashboardTraining.
|
|
14
|
+
description: 'Message displayed when requested offer is not found',
|
|
15
|
+
id: 'components.TeacherDashboardTraining.errorNoOffer',
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
interface TeacherDashboardTrainingProps {
|
|
20
|
-
|
|
20
|
+
offer: Offer;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const TeacherDashboardTraining = ({
|
|
24
|
-
courseProductRelation,
|
|
25
|
-
}: TeacherDashboardTrainingProps) => {
|
|
23
|
+
export const TeacherDashboardTraining = ({ offer }: TeacherDashboardTrainingProps) => {
|
|
26
24
|
const intl = useIntl();
|
|
27
|
-
return
|
|
25
|
+
return offer ? (
|
|
28
26
|
<div className="teacher-course-page">
|
|
29
27
|
<DashboardCard
|
|
30
28
|
className="icon-arrow-right-rounded"
|
|
31
29
|
header={
|
|
32
30
|
<div>
|
|
33
31
|
<div className="dashboard__title_container--small">
|
|
34
|
-
<h2 className="dashboard__title--large">
|
|
35
|
-
{capitalize(courseProductRelation.product.title)}
|
|
36
|
-
</h2>
|
|
32
|
+
<h2 className="dashboard__title--large">{capitalize(offer.product.title)}</h2>
|
|
37
33
|
</div>
|
|
38
|
-
{
|
|
39
|
-
<div className="dashboard__quote">{
|
|
34
|
+
{offer.product.description && (
|
|
35
|
+
<div className="dashboard__quote">{offer.product.description}</div>
|
|
40
36
|
)}
|
|
41
37
|
</div>
|
|
42
38
|
}
|
|
@@ -44,7 +40,7 @@ export const TeacherDashboardTraining = ({
|
|
|
44
40
|
fullWidth
|
|
45
41
|
/>
|
|
46
42
|
<DashboardLayout.NestedSection>
|
|
47
|
-
{
|
|
43
|
+
{offer.product.target_courses.map((course) => (
|
|
48
44
|
<DashboardLayout.Section key={`course_target_${course.code}`}>
|
|
49
45
|
<DashboardCard
|
|
50
46
|
className="icon-arrow-right-rounded"
|
|
@@ -66,11 +62,7 @@ export const TeacherDashboardTraining = ({
|
|
|
66
62
|
</DashboardLayout.NestedSection>
|
|
67
63
|
</div>
|
|
68
64
|
) : (
|
|
69
|
-
<Banner
|
|
70
|
-
message={intl.formatMessage(messages.errorNoCourseProductRelation)}
|
|
71
|
-
type={BannerType.ERROR}
|
|
72
|
-
rounded
|
|
73
|
-
/>
|
|
65
|
+
<Banner message={intl.formatMessage(messages.errorNoOffer)} type={BannerType.ERROR} rounded />
|
|
74
66
|
);
|
|
75
67
|
};
|
|
76
68
|
|
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
|
+
offer_id?: Offer['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 OfferLight {
|
|
178
178
|
id: string;
|
|
179
179
|
course: CourseLight;
|
|
180
180
|
organizations: Organization[];
|
|
@@ -182,7 +182,7 @@ export interface CourseProductRelationLight {
|
|
|
182
182
|
created_on: string;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
export interface
|
|
185
|
+
export interface Offer extends OfferLight {
|
|
186
186
|
is_withdrawable: boolean;
|
|
187
187
|
discounted_price: Nullable<number>;
|
|
188
188
|
discount_rate: Nullable<number>;
|
|
@@ -193,9 +193,7 @@ export interface CourseProductRelation extends CourseProductRelationLight {
|
|
|
193
193
|
nb_seats_available: Nullable<number>;
|
|
194
194
|
seats: Nullable<number>;
|
|
195
195
|
}
|
|
196
|
-
export function
|
|
197
|
-
entity: CourseListItem | CourseProductRelationLight | RichieCourse,
|
|
198
|
-
): entity is CourseProductRelationLight {
|
|
196
|
+
export function isOffer(entity: CourseListItem | OfferLight | RichieCourse): entity is OfferLight {
|
|
199
197
|
return 'course' in entity && 'product' in entity;
|
|
200
198
|
}
|
|
201
199
|
|
|
@@ -240,7 +238,7 @@ export interface Enrollment {
|
|
|
240
238
|
was_created_by_order: boolean;
|
|
241
239
|
created_on: string;
|
|
242
240
|
orders: OrderEnrollment[];
|
|
243
|
-
|
|
241
|
+
offers: Offer[];
|
|
244
242
|
certificate_id: Nullable<string>;
|
|
245
243
|
}
|
|
246
244
|
export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
|
|
@@ -254,7 +252,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
|
|
|
254
252
|
'was_created_by_order' in obj &&
|
|
255
253
|
'created_on' in obj &&
|
|
256
254
|
'orders' in obj &&
|
|
257
|
-
'
|
|
255
|
+
'offers' in obj &&
|
|
258
256
|
'certificate_id' in obj
|
|
259
257
|
);
|
|
260
258
|
};
|
|
@@ -404,7 +402,7 @@ export interface NestedCourseOrder {
|
|
|
404
402
|
|
|
405
403
|
export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
|
|
406
404
|
course_id?: CourseListItem['id'];
|
|
407
|
-
|
|
405
|
+
offer_id?: Offer['id'];
|
|
408
406
|
organization_id?: Organization['id'];
|
|
409
407
|
product_id?: Product['id'];
|
|
410
408
|
}
|
|
@@ -539,8 +537,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
|
|
|
539
537
|
id?: Product['id'];
|
|
540
538
|
course_id?: CourseListItem['id'];
|
|
541
539
|
}
|
|
542
|
-
export interface
|
|
543
|
-
id?:
|
|
540
|
+
export interface OfferQueryFilters extends PaginatedResourceQuery {
|
|
541
|
+
id?: Offer['id'];
|
|
544
542
|
organization_id?: Organization['id'];
|
|
545
543
|
product_type?: ProductType;
|
|
546
544
|
query?: string;
|
|
@@ -553,7 +551,7 @@ export enum ContractState {
|
|
|
553
551
|
}
|
|
554
552
|
export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
555
553
|
organization_id?: Organization['id'];
|
|
556
|
-
|
|
554
|
+
offer_id?: Offer['id'];
|
|
557
555
|
contract_ids?: Contract['id'][];
|
|
558
556
|
signature_state?: ContractState;
|
|
559
557
|
}
|
|
@@ -561,7 +559,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
|
561
559
|
export interface OrganizationContractSignatureLinksFilters {
|
|
562
560
|
contracts_ids?: string[];
|
|
563
561
|
organization_id: Organization['id'];
|
|
564
|
-
|
|
562
|
+
offer_ids?: Offer['id'][];
|
|
565
563
|
}
|
|
566
564
|
|
|
567
565
|
export interface ContractInvitationLinkResponse {
|
|
@@ -655,10 +653,10 @@ interface APIUser {
|
|
|
655
653
|
check: (id: string) => Promise<Response>;
|
|
656
654
|
create: ({
|
|
657
655
|
organization_id,
|
|
658
|
-
|
|
656
|
+
offer_id,
|
|
659
657
|
}: {
|
|
660
658
|
organization_id?: Organization['id'];
|
|
661
|
-
|
|
659
|
+
offer_id?: Offer['id'];
|
|
662
660
|
}) => Promise<{ url: string }>;
|
|
663
661
|
get: (id: string) => Promise<File>;
|
|
664
662
|
};
|
|
@@ -674,7 +672,7 @@ export interface API {
|
|
|
674
672
|
? Promise<Nullable<CourseListItem>>
|
|
675
673
|
: Promise<PaginatedResponse<CourseListItem>>;
|
|
676
674
|
products: {
|
|
677
|
-
get(filters?: CourseProductQueryFilters): Promise<Nullable<
|
|
675
|
+
get(filters?: CourseProductQueryFilters): Promise<Nullable<Offer>>;
|
|
678
676
|
paymentSchedule: {
|
|
679
677
|
get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
|
|
680
678
|
};
|
|
@@ -707,12 +705,12 @@ export interface API {
|
|
|
707
705
|
filters?: CourseRunFilters,
|
|
708
706
|
): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
|
|
709
707
|
};
|
|
710
|
-
|
|
708
|
+
offers: {
|
|
711
709
|
get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
|
|
712
710
|
filters?: Filters,
|
|
713
711
|
): Filters extends { id: string }
|
|
714
|
-
? Promise<Nullable<
|
|
715
|
-
: Promise<PaginatedResponse<
|
|
712
|
+
? Promise<Nullable<Offer>>
|
|
713
|
+
: Promise<PaginatedResponse<OfferLight>>;
|
|
716
714
|
};
|
|
717
715
|
contractDefinitions: {
|
|
718
716
|
previewTemplate(id: string): Promise<File>;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
CourseLight,
|
|
13
13
|
CourseListItem,
|
|
14
14
|
CourseProduct,
|
|
15
|
-
|
|
15
|
+
Offer,
|
|
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
|
+
offers: OfferFactory().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 OfferFactory = factory((): Offer => {
|
|
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
|
+
OfferFactory,
|
|
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 offer = OfferFactory({
|
|
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
|
+
offer,
|
|
26
26
|
);
|
|
27
|
-
return
|
|
27
|
+
return offer;
|
|
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.offers.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.offers[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: offer } = useCourseProduct({
|
|
71
71
|
product_id: order.product_id,
|
|
72
72
|
course_id: course.code,
|
|
73
73
|
});
|
|
74
|
-
const { product } =
|
|
74
|
+
const { product } = offer || {};
|
|
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(offer?.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 offer 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: offer, 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={offer.product as CredentialProduct}
|
|
122
|
+
course={offer.course}
|
|
123
|
+
isWithdrawable={offer.is_withdrawable}
|
|
124
124
|
/>
|
|
125
125
|
</>
|
|
126
126
|
);
|