richie-education 3.1.3-dev11 → 3.1.3-dev15
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 +5 -3
- package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
- package/js/components/SaleTunnel/index.spec.tsx +76 -63
- 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 +25 -22
- package/js/utils/test/factories/joanie.ts +14 -11
- 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 +10 -18
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +81 -99
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +6 -4
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +20 -31
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
- package/package.json +1 -1
- package/js/hooks/useCourseProductRelation/index.ts +0 -44
|
@@ -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,20 +182,23 @@ export interface CourseProductRelationLight {
|
|
|
182
182
|
created_on: string;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
export interface
|
|
186
|
-
is_withdrawable: boolean;
|
|
185
|
+
export interface OfferRule {
|
|
187
186
|
discounted_price: Nullable<number>;
|
|
188
187
|
discount_rate: Nullable<number>;
|
|
189
188
|
discount_amount: Nullable<number>;
|
|
190
189
|
discount_start: Nullable<string>;
|
|
191
190
|
discount_end: Nullable<string>;
|
|
192
191
|
description: Nullable<string>;
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
nb_available_seats: Nullable<number>;
|
|
193
|
+
has_seat_limit: boolean;
|
|
194
|
+
has_seats_left: boolean;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface Offer extends OfferLight {
|
|
198
|
+
is_withdrawable: boolean;
|
|
199
|
+
rules: OfferRule;
|
|
195
200
|
}
|
|
196
|
-
export function
|
|
197
|
-
entity: CourseListItem | CourseProductRelationLight | RichieCourse,
|
|
198
|
-
): entity is CourseProductRelationLight {
|
|
201
|
+
export function isOffer(entity: CourseListItem | OfferLight | RichieCourse): entity is OfferLight {
|
|
199
202
|
return 'course' in entity && 'product' in entity;
|
|
200
203
|
}
|
|
201
204
|
|
|
@@ -240,7 +243,7 @@ export interface Enrollment {
|
|
|
240
243
|
was_created_by_order: boolean;
|
|
241
244
|
created_on: string;
|
|
242
245
|
orders: OrderEnrollment[];
|
|
243
|
-
|
|
246
|
+
offers: Offer[];
|
|
244
247
|
certificate_id: Nullable<string>;
|
|
245
248
|
}
|
|
246
249
|
export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
|
|
@@ -254,7 +257,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
|
|
|
254
257
|
'was_created_by_order' in obj &&
|
|
255
258
|
'created_on' in obj &&
|
|
256
259
|
'orders' in obj &&
|
|
257
|
-
'
|
|
260
|
+
'offers' in obj &&
|
|
258
261
|
'certificate_id' in obj
|
|
259
262
|
);
|
|
260
263
|
};
|
|
@@ -404,7 +407,7 @@ export interface NestedCourseOrder {
|
|
|
404
407
|
|
|
405
408
|
export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
|
|
406
409
|
course_id?: CourseListItem['id'];
|
|
407
|
-
|
|
410
|
+
offer_id?: Offer['id'];
|
|
408
411
|
organization_id?: Organization['id'];
|
|
409
412
|
product_id?: Product['id'];
|
|
410
413
|
}
|
|
@@ -539,8 +542,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
|
|
|
539
542
|
id?: Product['id'];
|
|
540
543
|
course_id?: CourseListItem['id'];
|
|
541
544
|
}
|
|
542
|
-
export interface
|
|
543
|
-
id?:
|
|
545
|
+
export interface OfferQueryFilters extends PaginatedResourceQuery {
|
|
546
|
+
id?: Offer['id'];
|
|
544
547
|
organization_id?: Organization['id'];
|
|
545
548
|
product_type?: ProductType;
|
|
546
549
|
query?: string;
|
|
@@ -553,7 +556,7 @@ export enum ContractState {
|
|
|
553
556
|
}
|
|
554
557
|
export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
555
558
|
organization_id?: Organization['id'];
|
|
556
|
-
|
|
559
|
+
offer_id?: Offer['id'];
|
|
557
560
|
contract_ids?: Contract['id'][];
|
|
558
561
|
signature_state?: ContractState;
|
|
559
562
|
}
|
|
@@ -561,7 +564,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
|
|
|
561
564
|
export interface OrganizationContractSignatureLinksFilters {
|
|
562
565
|
contracts_ids?: string[];
|
|
563
566
|
organization_id: Organization['id'];
|
|
564
|
-
|
|
567
|
+
offer_ids?: Offer['id'][];
|
|
565
568
|
}
|
|
566
569
|
|
|
567
570
|
export interface ContractInvitationLinkResponse {
|
|
@@ -655,10 +658,10 @@ interface APIUser {
|
|
|
655
658
|
check: (id: string) => Promise<Response>;
|
|
656
659
|
create: ({
|
|
657
660
|
organization_id,
|
|
658
|
-
|
|
661
|
+
offer_id,
|
|
659
662
|
}: {
|
|
660
663
|
organization_id?: Organization['id'];
|
|
661
|
-
|
|
664
|
+
offer_id?: Offer['id'];
|
|
662
665
|
}) => Promise<{ url: string }>;
|
|
663
666
|
get: (id: string) => Promise<File>;
|
|
664
667
|
};
|
|
@@ -674,7 +677,7 @@ export interface API {
|
|
|
674
677
|
? Promise<Nullable<CourseListItem>>
|
|
675
678
|
: Promise<PaginatedResponse<CourseListItem>>;
|
|
676
679
|
products: {
|
|
677
|
-
get(filters?: CourseProductQueryFilters): Promise<Nullable<
|
|
680
|
+
get(filters?: CourseProductQueryFilters): Promise<Nullable<Offer>>;
|
|
678
681
|
paymentSchedule: {
|
|
679
682
|
get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
|
|
680
683
|
};
|
|
@@ -707,12 +710,12 @@ export interface API {
|
|
|
707
710
|
filters?: CourseRunFilters,
|
|
708
711
|
): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
|
|
709
712
|
};
|
|
710
|
-
|
|
713
|
+
offers: {
|
|
711
714
|
get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
|
|
712
715
|
filters?: Filters,
|
|
713
716
|
): Filters extends { id: string }
|
|
714
|
-
? Promise<Nullable<
|
|
715
|
-
: Promise<PaginatedResponse<
|
|
717
|
+
? Promise<Nullable<Offer>>
|
|
718
|
+
: Promise<PaginatedResponse<OfferLight>>;
|
|
716
719
|
};
|
|
717
720
|
contractDefinitions: {
|
|
718
721
|
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(),
|
|
@@ -317,14 +317,17 @@ export const CourseProductRelationFactory = factory((): CourseProductRelation =>
|
|
|
317
317
|
product: ProductFactory().one(),
|
|
318
318
|
organizations: OrganizationFactory().many(1),
|
|
319
319
|
is_withdrawable: true,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
320
|
+
rules: {
|
|
321
|
+
discounted_price: null,
|
|
322
|
+
discount_rate: null,
|
|
323
|
+
discount_amount: null,
|
|
324
|
+
discount_start: null,
|
|
325
|
+
discount_end: null,
|
|
326
|
+
description: null,
|
|
327
|
+
nb_available_seats: null,
|
|
328
|
+
has_seat_limit: false,
|
|
329
|
+
has_seats_left: true,
|
|
330
|
+
},
|
|
328
331
|
};
|
|
329
332
|
});
|
|
330
333
|
|
|
@@ -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
|
);
|
package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx
CHANGED
|
@@ -28,7 +28,7 @@ describe('<ContractNavLink />', () => {
|
|
|
28
28
|
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', []);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('should render a ContractNavLink with route and label when neither organizationId and
|
|
31
|
+
it('should render a ContractNavLink with route and label when neither organizationId and offerId are given', () => {
|
|
32
32
|
const link: MenuLink = {
|
|
33
33
|
to: '/dummy/url/',
|
|
34
34
|
label: 'My contract navigation link',
|
|
@@ -45,31 +45,31 @@ describe('<ContractNavLink />', () => {
|
|
|
45
45
|
it.each([
|
|
46
46
|
{
|
|
47
47
|
organizationId: faker.string.uuid(),
|
|
48
|
-
|
|
48
|
+
offerId: undefined,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
organizationId: faker.string.uuid(),
|
|
52
|
-
|
|
52
|
+
offerId: faker.string.uuid(),
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
organizationId: undefined,
|
|
56
|
-
|
|
56
|
+
offerId: faker.string.uuid(),
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
organizationId: undefined,
|
|
60
|
-
|
|
60
|
+
offerId: undefined,
|
|
61
61
|
},
|
|
62
62
|
])(
|
|
63
|
-
'should never render Badge for organizationId: $organizationId and courseProductId: $
|
|
64
|
-
async ({ organizationId,
|
|
63
|
+
'should never render Badge for organizationId: $organizationId and courseProductId: $offerId',
|
|
64
|
+
async ({ organizationId, offerId }) => {
|
|
65
65
|
let contractQueryParams: ContractResourceQuery = {
|
|
66
66
|
signature_state: ContractState.LEARNER_SIGNED,
|
|
67
67
|
page: 1,
|
|
68
68
|
page_size: PER_PAGE.teacherContractList,
|
|
69
69
|
};
|
|
70
|
-
if (
|
|
70
|
+
if (offerId) {
|
|
71
71
|
contractQueryParams = {
|
|
72
|
-
|
|
72
|
+
offer_id: offerId,
|
|
73
73
|
...contractQueryParams,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -94,7 +94,7 @@ describe('<ContractNavLink />', () => {
|
|
|
94
94
|
label: 'My contract navigation link',
|
|
95
95
|
}}
|
|
96
96
|
organizationId={organizationId}
|
|
97
|
-
|
|
97
|
+
offerId={offerId}
|
|
98
98
|
/>,
|
|
99
99
|
);
|
|
100
100
|
|
|
@@ -112,25 +112,25 @@ describe('<ContractNavLink />', () => {
|
|
|
112
112
|
// with 1 contracts to sign
|
|
113
113
|
{
|
|
114
114
|
organizationId: faker.string.uuid(),
|
|
115
|
-
|
|
115
|
+
offerId: undefined,
|
|
116
116
|
nbContractsToSign: 1,
|
|
117
117
|
expectedBadgeCount: 1,
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
organizationId: faker.string.uuid(),
|
|
121
|
-
|
|
121
|
+
offerId: faker.string.uuid(),
|
|
122
122
|
nbContractsToSign: 1,
|
|
123
123
|
expectedBadgeCount: 1,
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
organizationId: undefined,
|
|
127
|
-
|
|
127
|
+
offerId: faker.string.uuid(),
|
|
128
128
|
nbContractsToSign: 1,
|
|
129
129
|
expectedBadgeCount: undefined,
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
organizationId: undefined,
|
|
133
|
-
|
|
133
|
+
offerId: undefined,
|
|
134
134
|
nbContractsToSign: 1,
|
|
135
135
|
expectedBadgeCount: undefined,
|
|
136
136
|
},
|
|
@@ -138,44 +138,39 @@ describe('<ContractNavLink />', () => {
|
|
|
138
138
|
// with 0 contracts to sign
|
|
139
139
|
{
|
|
140
140
|
organizationId: faker.string.uuid(),
|
|
141
|
-
|
|
141
|
+
offerId: undefined,
|
|
142
142
|
nbContractsToSign: 0,
|
|
143
143
|
expectedBadgeCount: undefined,
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
organizationId: faker.string.uuid(),
|
|
147
|
-
|
|
147
|
+
offerId: faker.string.uuid(),
|
|
148
148
|
nbContractsToSign: 0,
|
|
149
149
|
expectedBadgeCount: undefined,
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
organizationId: undefined,
|
|
153
|
-
|
|
153
|
+
offerId: faker.string.uuid(),
|
|
154
154
|
nbContractsToSign: 0,
|
|
155
155
|
expectedBadgeCount: undefined,
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
organizationId: undefined,
|
|
159
|
-
|
|
159
|
+
offerId: undefined,
|
|
160
160
|
nbContractsToSign: 0,
|
|
161
161
|
expectedBadgeCount: undefined,
|
|
162
162
|
},
|
|
163
163
|
])(
|
|
164
|
-
'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $
|
|
165
|
-
async ({
|
|
166
|
-
nbContractsToSign,
|
|
167
|
-
organizationId,
|
|
168
|
-
courseProductRelationId,
|
|
169
|
-
expectedBadgeCount,
|
|
170
|
-
}) => {
|
|
164
|
+
'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $offerId',
|
|
165
|
+
async ({ nbContractsToSign, organizationId, offerId, expectedBadgeCount }) => {
|
|
171
166
|
let contractQueryParams: ContractResourceQuery = {
|
|
172
167
|
signature_state: ContractState.LEARNER_SIGNED,
|
|
173
168
|
page: 1,
|
|
174
169
|
page_size: PER_PAGE.teacherContractList,
|
|
175
170
|
};
|
|
176
|
-
if (
|
|
171
|
+
if (offerId) {
|
|
177
172
|
contractQueryParams = {
|
|
178
|
-
|
|
173
|
+
offer_id: offerId,
|
|
179
174
|
...contractQueryParams,
|
|
180
175
|
};
|
|
181
176
|
}
|
|
@@ -199,7 +194,7 @@ describe('<ContractNavLink />', () => {
|
|
|
199
194
|
label: 'My contract navigation link',
|
|
200
195
|
}}
|
|
201
196
|
organizationId={organizationId}
|
|
202
|
-
|
|
197
|
+
offerId={offerId}
|
|
203
198
|
/>,
|
|
204
199
|
);
|
|
205
200
|
|