richie-education 2.30.1-dev13 → 2.30.1-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/components/CourseGlimpse/utils.ts +3 -3
- package/js/components/CourseGlimpseList/utils.ts +2 -2
- package/js/components/PurchaseButton/index.spec.tsx +20 -2
- package/js/components/PurchaseButton/index.tsx +3 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +1 -1
- package/js/components/SaleTunnel/WithdrawRightCheckbox/index.tsx +4 -4
- package/js/components/SaleTunnel/index.credential.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process.spec.tsx +6 -2
- package/js/components/SaleTunnel/index.spec.tsx +10 -10
- package/js/components/SaleTunnel/index.stories.tsx +1 -0
- package/js/components/SaleTunnel/index.tsx +1 -1
- package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
- package/js/hooks/useCourseProductUnion/index.ts +2 -1
- package/js/hooks/useTeacherCoursesSearch/index.tsx +2 -2
- package/js/types/Joanie.ts +8 -5
- package/js/utils/test/factories/joanie.ts +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +2 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +29 -5
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +7 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +1 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +2 -0
- package/package.json +3 -2
|
@@ -3,14 +3,14 @@ import { generatePath } from 'react-router-dom';
|
|
|
3
3
|
import { Course as RichieCourse, isRichieCourse } from 'types/Course';
|
|
4
4
|
import {
|
|
5
5
|
CourseListItem as JoanieCourse,
|
|
6
|
-
|
|
6
|
+
CourseProductRelationLight,
|
|
7
7
|
isCourseProductRelation,
|
|
8
8
|
} from 'types/Joanie';
|
|
9
9
|
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherDashboardPaths';
|
|
10
10
|
import { CourseGlimpseCourse } from '.';
|
|
11
11
|
|
|
12
12
|
const getCourseGlimpsePropsFromCourseProductRelation = (
|
|
13
|
-
courseProductRelation:
|
|
13
|
+
courseProductRelation: CourseProductRelationLight,
|
|
14
14
|
intl: IntlShape,
|
|
15
15
|
organizationId?: string,
|
|
16
16
|
): CourseGlimpseCourse => {
|
|
@@ -95,7 +95,7 @@ const getCourseGlimpsePropsFromJoanieCourse = (
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
export const getCourseGlimpseProps = (
|
|
98
|
-
course: RichieCourse | (JoanieCourse |
|
|
98
|
+
course: RichieCourse | (JoanieCourse | CourseProductRelationLight),
|
|
99
99
|
intl?: IntlShape,
|
|
100
100
|
organizationId?: string,
|
|
101
101
|
): CourseGlimpseCourse => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { IntlShape } from 'react-intl';
|
|
2
|
-
import {
|
|
2
|
+
import { CourseProductRelationLight, CourseListItem as JoanieCourse } from 'types/Joanie';
|
|
3
3
|
import { Course as RichieCourse } from 'types/Course';
|
|
4
4
|
import { CourseGlimpseCourse, getCourseGlimpseProps } from 'components/CourseGlimpse';
|
|
5
5
|
|
|
6
6
|
export const getCourseGlimpseListProps = (
|
|
7
|
-
courses: RichieCourse[] | (JoanieCourse |
|
|
7
|
+
courses: RichieCourse[] | (JoanieCourse | CourseProductRelationLight)[],
|
|
8
8
|
intl?: IntlShape,
|
|
9
9
|
organizationId?: string,
|
|
10
10
|
): CourseGlimpseCourse[] => {
|
|
@@ -91,6 +91,7 @@ describe('PurchaseButton', () => {
|
|
|
91
91
|
product={product}
|
|
92
92
|
disabled={false}
|
|
93
93
|
course={PacedCourseFactory({ code: '00000' }).one()}
|
|
94
|
+
isWithdrawable={true}
|
|
94
95
|
/>
|
|
95
96
|
</Wrapper>,
|
|
96
97
|
);
|
|
@@ -122,6 +123,7 @@ describe('PurchaseButton', () => {
|
|
|
122
123
|
product={product}
|
|
123
124
|
disabled={false}
|
|
124
125
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
126
|
+
isWithdrawable={true}
|
|
125
127
|
/>
|
|
126
128
|
</Wrapper>,
|
|
127
129
|
);
|
|
@@ -164,6 +166,7 @@ describe('PurchaseButton', () => {
|
|
|
164
166
|
product={product}
|
|
165
167
|
disabled={false}
|
|
166
168
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
169
|
+
isWithdrawable={true}
|
|
167
170
|
/>
|
|
168
171
|
</Wrapper>,
|
|
169
172
|
);
|
|
@@ -207,6 +210,7 @@ describe('PurchaseButton', () => {
|
|
|
207
210
|
product={product}
|
|
208
211
|
disabled={false}
|
|
209
212
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
213
|
+
isWithdrawable={true}
|
|
210
214
|
/>
|
|
211
215
|
</Wrapper>,
|
|
212
216
|
);
|
|
@@ -243,6 +247,7 @@ describe('PurchaseButton', () => {
|
|
|
243
247
|
product={product}
|
|
244
248
|
disabled={false}
|
|
245
249
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
250
|
+
isWithdrawable={true}
|
|
246
251
|
/>
|
|
247
252
|
</Wrapper>,
|
|
248
253
|
);
|
|
@@ -284,6 +289,7 @@ describe('PurchaseButton', () => {
|
|
|
284
289
|
product={product}
|
|
285
290
|
disabled={false}
|
|
286
291
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
292
|
+
isWithdrawable={true}
|
|
287
293
|
/>
|
|
288
294
|
</Wrapper>,
|
|
289
295
|
);
|
|
@@ -333,7 +339,12 @@ describe('PurchaseButton', () => {
|
|
|
333
339
|
|
|
334
340
|
render(
|
|
335
341
|
<Wrapper client={createTestQueryClient({ user: true })}>
|
|
336
|
-
<PurchaseButton
|
|
342
|
+
<PurchaseButton
|
|
343
|
+
product={product}
|
|
344
|
+
disabled={false}
|
|
345
|
+
enrollment={enrollment}
|
|
346
|
+
isWithdrawable={true}
|
|
347
|
+
/>
|
|
337
348
|
</Wrapper>,
|
|
338
349
|
);
|
|
339
350
|
|
|
@@ -390,7 +401,12 @@ describe('PurchaseButton', () => {
|
|
|
390
401
|
|
|
391
402
|
render(
|
|
392
403
|
<Wrapper client={createTestQueryClient({ user: true })}>
|
|
393
|
-
<PurchaseButton
|
|
404
|
+
<PurchaseButton
|
|
405
|
+
product={product}
|
|
406
|
+
disabled={false}
|
|
407
|
+
enrollment={enrollment}
|
|
408
|
+
isWithdrawable={true}
|
|
409
|
+
/>
|
|
394
410
|
</Wrapper>,
|
|
395
411
|
);
|
|
396
412
|
|
|
@@ -428,6 +444,7 @@ describe('PurchaseButton', () => {
|
|
|
428
444
|
product={product}
|
|
429
445
|
disabled={false}
|
|
430
446
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
447
|
+
isWithdrawable={true}
|
|
431
448
|
/>
|
|
432
449
|
</Wrapper>,
|
|
433
450
|
);
|
|
@@ -462,6 +479,7 @@ describe('PurchaseButton', () => {
|
|
|
462
479
|
product={product}
|
|
463
480
|
disabled={true}
|
|
464
481
|
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
482
|
+
isWithdrawable={true}
|
|
465
483
|
/>
|
|
466
484
|
</Wrapper>,
|
|
467
485
|
);
|
|
@@ -43,6 +43,7 @@ const messages = defineMessages({
|
|
|
43
43
|
interface PurchaseButtonPropsBase {
|
|
44
44
|
product: Joanie.CredentialProduct | Joanie.CertificateProduct;
|
|
45
45
|
orderGroup?: Joanie.OrderGroup;
|
|
46
|
+
isWithdrawable: boolean;
|
|
46
47
|
disabled?: boolean;
|
|
47
48
|
className?: string;
|
|
48
49
|
buttonProps?: ButtonProps;
|
|
@@ -67,6 +68,7 @@ const PurchaseButton = ({
|
|
|
67
68
|
course,
|
|
68
69
|
enrollment,
|
|
69
70
|
orderGroup,
|
|
71
|
+
isWithdrawable,
|
|
70
72
|
organizations,
|
|
71
73
|
disabled = false,
|
|
72
74
|
className,
|
|
@@ -141,6 +143,7 @@ const PurchaseButton = ({
|
|
|
141
143
|
enrollment={enrollment}
|
|
142
144
|
orderGroup={orderGroup}
|
|
143
145
|
course={course}
|
|
146
|
+
isWithdrawable={isWithdrawable}
|
|
144
147
|
onFinish={onFinish}
|
|
145
148
|
/>
|
|
146
149
|
</>
|
|
@@ -116,7 +116,7 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
if (!
|
|
119
|
+
if (!saleTunnelProps.isWithdrawable && !hasWaivedWithdrawalRight) {
|
|
120
120
|
handleError(SubscriptionErrorMessageId.ERROR_WITHDRAWAL_RIGHT);
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
@@ -19,7 +19,7 @@ const messages = defineMessages({
|
|
|
19
19
|
|
|
20
20
|
const WithdrawRightCheckbox = () => {
|
|
21
21
|
const {
|
|
22
|
-
|
|
22
|
+
props: { isWithdrawable },
|
|
23
23
|
registerSubmitCallback,
|
|
24
24
|
unregisterSubmitCallback,
|
|
25
25
|
hasWaivedWithdrawalRight,
|
|
@@ -27,8 +27,8 @@ const WithdrawRightCheckbox = () => {
|
|
|
27
27
|
} = useSaleTunnelContext();
|
|
28
28
|
const [hasErrorState, setHasError] = useState(false);
|
|
29
29
|
const setError = useCallback(async () => {
|
|
30
|
-
setHasError(!
|
|
31
|
-
}, [hasWaivedWithdrawalRight,
|
|
30
|
+
setHasError(!isWithdrawable && !hasWaivedWithdrawalRight);
|
|
31
|
+
}, [hasWaivedWithdrawalRight, isWithdrawable]);
|
|
32
32
|
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
registerSubmitCallback('withdrawalRight', setError);
|
|
@@ -37,7 +37,7 @@ const WithdrawRightCheckbox = () => {
|
|
|
37
37
|
};
|
|
38
38
|
}, [setError]);
|
|
39
39
|
|
|
40
|
-
if (
|
|
40
|
+
if (isWithdrawable) return null;
|
|
41
41
|
return (
|
|
42
42
|
<section
|
|
43
43
|
className="mt-t subscription-button__waiveCheckbox"
|
|
@@ -53,8 +53,8 @@ describe('SaleTunnel / Credential', () => {
|
|
|
53
53
|
let richieUser: User;
|
|
54
54
|
let openApiEdxProfile: OpenEdxApiProfile;
|
|
55
55
|
|
|
56
|
-
const Wrapper = (props: Omit<SaleTunnelProps, 'isOpen' | 'onClose'>) => {
|
|
57
|
-
return <SaleTunnel {...props} isOpen={true} onClose={() => {}} />;
|
|
56
|
+
const Wrapper = (props: Omit<SaleTunnelProps, 'isWithdrawable' | 'isOpen' | 'onClose'>) => {
|
|
57
|
+
return <SaleTunnel {...props} isWithdrawable={true} isOpen={true} onClose={() => {}} />;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
setupJoanieSession();
|
|
@@ -98,8 +98,12 @@ describe('SaleTunnel', () => {
|
|
|
98
98
|
* Initialization.
|
|
99
99
|
*/
|
|
100
100
|
const course = PacedCourseFactory().one();
|
|
101
|
-
const product = ProductFactory(
|
|
102
|
-
const relation = CourseProductRelationFactory({
|
|
101
|
+
const product = ProductFactory().one();
|
|
102
|
+
const relation = CourseProductRelationFactory({
|
|
103
|
+
course,
|
|
104
|
+
product,
|
|
105
|
+
is_withdrawable: false,
|
|
106
|
+
}).one();
|
|
103
107
|
const paymentSchedule = PaymentInstallmentFactory().many(2);
|
|
104
108
|
|
|
105
109
|
fetchMock.get(
|
|
@@ -175,7 +175,7 @@ describe.each([
|
|
|
175
175
|
overwriteRoutes: true,
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
render(<Wrapper product={product} />, {
|
|
178
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
179
179
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
180
180
|
});
|
|
181
181
|
nbApiCalls += 1; // useProductOrder call.
|
|
@@ -262,7 +262,7 @@ describe.each([
|
|
|
262
262
|
overwriteRoutes: true,
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
render(<Wrapper product={product} />, {
|
|
265
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
266
266
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
267
267
|
});
|
|
268
268
|
nbApiCalls += 1; // useProductOrder get order with filters
|
|
@@ -337,7 +337,7 @@ describe.each([
|
|
|
337
337
|
overwriteRoutes: true,
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
-
render(<Wrapper product={product} />, {
|
|
340
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
341
341
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
342
342
|
});
|
|
343
343
|
|
|
@@ -373,7 +373,7 @@ describe.each([
|
|
|
373
373
|
overwriteRoutes: true,
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
-
render(<Wrapper product={product} />, {
|
|
376
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
377
377
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
378
378
|
});
|
|
379
379
|
|
|
@@ -398,7 +398,7 @@ describe.each([
|
|
|
398
398
|
schedule,
|
|
399
399
|
);
|
|
400
400
|
|
|
401
|
-
render(<Wrapper product={product} />, {
|
|
401
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
402
402
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
403
403
|
});
|
|
404
404
|
|
|
@@ -447,7 +447,7 @@ describe.each([
|
|
|
447
447
|
schedule,
|
|
448
448
|
);
|
|
449
449
|
|
|
450
|
-
render(<Wrapper product={product} />, {
|
|
450
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
451
451
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
452
452
|
});
|
|
453
453
|
|
|
@@ -455,7 +455,7 @@ describe.each([
|
|
|
455
455
|
});
|
|
456
456
|
|
|
457
457
|
it('should show a checkbox to waive withdrawal right if the product is not withdrawable', async () => {
|
|
458
|
-
const product = ProductFactory(
|
|
458
|
+
const product = ProductFactory().one();
|
|
459
459
|
const schedule = PaymentInstallmentFactory().many(2);
|
|
460
460
|
fetchMock
|
|
461
461
|
.get(
|
|
@@ -467,7 +467,7 @@ describe.each([
|
|
|
467
467
|
schedule,
|
|
468
468
|
);
|
|
469
469
|
|
|
470
|
-
render(<Wrapper product={product} />, {
|
|
470
|
+
render(<Wrapper product={product} isWithdrawable={false} />, {
|
|
471
471
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
472
472
|
});
|
|
473
473
|
|
|
@@ -475,7 +475,7 @@ describe.each([
|
|
|
475
475
|
});
|
|
476
476
|
|
|
477
477
|
it('should not show a checkbox to waive withdrawal right if the product is withdrawable', async () => {
|
|
478
|
-
const product = ProductFactory(
|
|
478
|
+
const product = ProductFactory().one();
|
|
479
479
|
const schedule = PaymentInstallmentFactory().many(2);
|
|
480
480
|
fetchMock
|
|
481
481
|
.get(
|
|
@@ -487,7 +487,7 @@ describe.each([
|
|
|
487
487
|
schedule,
|
|
488
488
|
);
|
|
489
489
|
|
|
490
|
-
render(<Wrapper product={product} />, {
|
|
490
|
+
render(<Wrapper product={product} isWithdrawable={true} />, {
|
|
491
491
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -17,7 +17,7 @@ import { PacedCourse } from 'types';
|
|
|
17
17
|
export interface SaleTunnelProps extends Pick<ModalProps, 'isOpen' | 'onClose'> {
|
|
18
18
|
product: Product;
|
|
19
19
|
organizations?: Organization[];
|
|
20
|
-
|
|
20
|
+
isWithdrawable: boolean;
|
|
21
21
|
course?: PacedCourse | CourseLight;
|
|
22
22
|
enrollment?: Enrollment;
|
|
23
23
|
orderGroup?: OrderGroup;
|
|
@@ -6,7 +6,7 @@ import { CourseGlimpseList, getCourseGlimpseListProps } from 'components/CourseG
|
|
|
6
6
|
import { Spinner } from 'components/Spinner';
|
|
7
7
|
import context from 'utils/context';
|
|
8
8
|
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
|
|
9
|
-
import { CourseListItem,
|
|
9
|
+
import { CourseListItem, CourseProductRelationLight } from 'types/Joanie';
|
|
10
10
|
import Banner from 'components/Banner';
|
|
11
11
|
|
|
12
12
|
const messages = defineMessages({
|
|
@@ -31,7 +31,7 @@ interface TeacherDashboardCourseListProps {
|
|
|
31
31
|
titleTranslated?: string;
|
|
32
32
|
organizationId?: string;
|
|
33
33
|
loadMore: () => void;
|
|
34
|
-
courseAndProductList?: (CourseListItem |
|
|
34
|
+
courseAndProductList?: (CourseListItem | CourseProductRelationLight)[];
|
|
35
35
|
isLoadingMore?: boolean;
|
|
36
36
|
hasMore?: boolean;
|
|
37
37
|
isNewSearchLoading?: boolean;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CourseQueryFilters,
|
|
8
8
|
CourseProductRelationQueryFilters,
|
|
9
9
|
ProductType,
|
|
10
|
+
CourseProductRelationLight,
|
|
10
11
|
} from 'types/Joanie';
|
|
11
12
|
import useUnionResource, { ResourceUnionPaginationProps } from 'hooks/useUnionResource';
|
|
12
13
|
|
|
@@ -40,7 +41,7 @@ export const useCourseProductUnion = ({
|
|
|
40
41
|
const api = useJoanieApi();
|
|
41
42
|
return useUnionResource<
|
|
42
43
|
CourseListItem,
|
|
43
|
-
CourseProductRelation,
|
|
44
|
+
CourseProductRelation | CourseProductRelationLight,
|
|
44
45
|
CourseQueryFilters,
|
|
45
46
|
CourseProductRelationQueryFilters
|
|
46
47
|
>({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
3
3
|
import { useCourseProductUnion } from 'hooks/useCourseProductUnion';
|
|
4
|
-
import { CourseListItem,
|
|
4
|
+
import { CourseListItem, CourseProductRelationLight, ProductType } from 'types/Joanie';
|
|
5
5
|
import { Maybe, Nullable } from 'types/utils';
|
|
6
6
|
|
|
7
7
|
const useTeacherCoursesSearch = () => {
|
|
@@ -9,7 +9,7 @@ const useTeacherCoursesSearch = () => {
|
|
|
9
9
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
10
10
|
const [count, setCount] = useState<Maybe<number>>(0);
|
|
11
11
|
const [courseAndProductList, setCourseAndProductList] = useState<
|
|
12
|
-
(CourseListItem |
|
|
12
|
+
(CourseListItem | CourseProductRelationLight)[]
|
|
13
13
|
>([]);
|
|
14
14
|
const [isNewSearchLoading, setIsNewSearchLoading] = useState(false);
|
|
15
15
|
const query = searchParams.get('query') || undefined;
|
package/js/types/Joanie.ts
CHANGED
|
@@ -149,7 +149,6 @@ export interface Product {
|
|
|
149
149
|
state: CourseState;
|
|
150
150
|
instructions: Nullable<string>;
|
|
151
151
|
contract_definition?: ContractDefinition;
|
|
152
|
-
is_withdrawable: boolean;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
export interface CredentialProduct extends Product {
|
|
@@ -175,17 +174,21 @@ export interface DefinitionResourcesProduct {
|
|
|
175
174
|
contract_definition_id: Nullable<ContractDefinition['id']>;
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
export interface
|
|
177
|
+
export interface CourseProductRelationLight {
|
|
179
178
|
id: string;
|
|
180
179
|
course: CourseLight;
|
|
181
180
|
organizations: Organization[];
|
|
182
181
|
product: Product;
|
|
183
182
|
created_on: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface CourseProductRelation extends CourseProductRelationLight {
|
|
184
186
|
order_groups: OrderGroup[];
|
|
187
|
+
is_withdrawable: boolean;
|
|
185
188
|
}
|
|
186
189
|
export function isCourseProductRelation(
|
|
187
|
-
entity: CourseListItem |
|
|
188
|
-
): entity is
|
|
190
|
+
entity: CourseListItem | CourseProductRelationLight | RichieCourse,
|
|
191
|
+
): entity is CourseProductRelationLight {
|
|
189
192
|
return 'course' in entity && 'product' in entity;
|
|
190
193
|
}
|
|
191
194
|
|
|
@@ -699,7 +702,7 @@ export interface API {
|
|
|
699
702
|
filters?: Filters,
|
|
700
703
|
): Filters extends { id: string }
|
|
701
704
|
? Promise<Nullable<CourseProductRelation>>
|
|
702
|
-
: Promise<PaginatedResponse<
|
|
705
|
+
: Promise<PaginatedResponse<CourseProductRelationLight>>;
|
|
703
706
|
};
|
|
704
707
|
contractDefinitions: {
|
|
705
708
|
previewTemplate(id: string): Promise<File>;
|
|
@@ -206,7 +206,6 @@ export const CredentialProductFactory = factory((): CredentialProduct => {
|
|
|
206
206
|
remaining_order_count: faker.number.int({ min: 1, max: 100 }),
|
|
207
207
|
state: CourseStateFactory().one(),
|
|
208
208
|
instructions: null,
|
|
209
|
-
is_withdrawable: true,
|
|
210
209
|
};
|
|
211
210
|
});
|
|
212
211
|
|
|
@@ -338,6 +337,7 @@ export const CourseProductRelationFactory = factory((): CourseProductRelation =>
|
|
|
338
337
|
product: ProductFactory().one(),
|
|
339
338
|
organizations: OrganizationFactory().many(1),
|
|
340
339
|
order_groups: [],
|
|
340
|
+
is_withdrawable: true,
|
|
341
341
|
};
|
|
342
342
|
});
|
|
343
343
|
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx
CHANGED
|
@@ -33,13 +33,14 @@ export const DashboardItemEnrollment = ({ enrollment }: DashboardItemCourseRunPr
|
|
|
33
33
|
</div>
|
|
34
34
|
</div>,
|
|
35
35
|
];
|
|
36
|
-
enrollment.product_relations.forEach(({ product }) => {
|
|
36
|
+
enrollment.product_relations.forEach(({ product, is_withdrawable }) => {
|
|
37
37
|
if (isCertificateProduct(product)) {
|
|
38
38
|
partialFooterList.push(
|
|
39
39
|
<ProductCertificateFooter
|
|
40
40
|
key={[enrollment.id, product.id].join('_')}
|
|
41
41
|
product={product}
|
|
42
42
|
enrollment={enrollment}
|
|
43
|
+
isWithdrawable={is_withdrawable}
|
|
43
44
|
/>,
|
|
44
45
|
);
|
|
45
46
|
}
|
|
@@ -124,6 +124,7 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
124
124
|
course,
|
|
125
125
|
}).one(),
|
|
126
126
|
}).one()}
|
|
127
|
+
isWithdrawable={true}
|
|
127
128
|
/>,
|
|
128
129
|
);
|
|
129
130
|
expect(screen.getByTestId('PurchaseButton__cta')).toBeInTheDocument();
|
|
@@ -155,6 +156,7 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
155
156
|
course,
|
|
156
157
|
}).one(),
|
|
157
158
|
}).one()}
|
|
159
|
+
isWithdrawable={true}
|
|
158
160
|
/>,
|
|
159
161
|
);
|
|
160
162
|
|
|
@@ -176,7 +178,9 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
176
178
|
'https://joanie.endpoint/api/v1.0/certificates/FAKE_CERTIFICATE_ID/',
|
|
177
179
|
CertificateFactory({ id: order.certificate_id }).one(),
|
|
178
180
|
);
|
|
179
|
-
render(
|
|
181
|
+
render(
|
|
182
|
+
<ProductCertificateFooter product={product} enrollment={enrollment} isWithdrawable={true} />,
|
|
183
|
+
);
|
|
180
184
|
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument();
|
|
181
185
|
expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
|
|
182
186
|
});
|
|
@@ -193,7 +197,13 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
193
197
|
orders: [order],
|
|
194
198
|
course_run: CourseRunFactory({ course }).one(),
|
|
195
199
|
}).one();
|
|
196
|
-
render(
|
|
200
|
+
render(
|
|
201
|
+
<ProductCertificateFooter
|
|
202
|
+
product={product}
|
|
203
|
+
enrollment={enrollment}
|
|
204
|
+
isWithdrawable={true}
|
|
205
|
+
/>,
|
|
206
|
+
);
|
|
197
207
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
198
208
|
expect(screen.getByTestId('PurchaseButton__cta')).toBeInTheDocument();
|
|
199
209
|
},
|
|
@@ -208,7 +218,9 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
208
218
|
orders: [order],
|
|
209
219
|
course_run: CourseRunFactory({ course }).one(),
|
|
210
220
|
}).one();
|
|
211
|
-
render(
|
|
221
|
+
render(
|
|
222
|
+
<ProductCertificateFooter product={product} enrollment={enrollment} isWithdrawable={true} />,
|
|
223
|
+
);
|
|
212
224
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
213
225
|
expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
|
|
214
226
|
});
|
|
@@ -274,7 +286,13 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
274
286
|
}).one();
|
|
275
287
|
const enrollment = EnrollmentFactory({ orders: [order] }).one();
|
|
276
288
|
|
|
277
|
-
render(
|
|
289
|
+
render(
|
|
290
|
+
<ProductCertificateFooter
|
|
291
|
+
product={product}
|
|
292
|
+
enrollment={enrollment}
|
|
293
|
+
isWithdrawable={true}
|
|
294
|
+
/>,
|
|
295
|
+
);
|
|
278
296
|
|
|
279
297
|
if (order.state === OrderState.PENDING) {
|
|
280
298
|
// As the order is in pending state, the user should see the following message.
|
|
@@ -315,7 +333,13 @@ describe('<ProductCertificateFooter/>', () => {
|
|
|
315
333
|
|
|
316
334
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/orders/${order.id}/`, order);
|
|
317
335
|
|
|
318
|
-
render(
|
|
336
|
+
render(
|
|
337
|
+
<ProductCertificateFooter
|
|
338
|
+
product={product}
|
|
339
|
+
enrollment={enrollment}
|
|
340
|
+
isWithdrawable={true}
|
|
341
|
+
/>,
|
|
342
|
+
);
|
|
319
343
|
|
|
320
344
|
if (order.state === OrderState.NO_PAYMENT) {
|
|
321
345
|
// As the order is in no_payment state, the user should see the following message.
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx
CHANGED
|
@@ -63,9 +63,14 @@ const messages = defineMessages({
|
|
|
63
63
|
export interface ProductCertificateFooterProps {
|
|
64
64
|
product: CertificateProduct;
|
|
65
65
|
enrollment: Enrollment;
|
|
66
|
+
isWithdrawable: boolean;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const ProductCertificateFooter = ({
|
|
69
|
+
const ProductCertificateFooter = ({
|
|
70
|
+
product,
|
|
71
|
+
enrollment,
|
|
72
|
+
isWithdrawable,
|
|
73
|
+
}: ProductCertificateFooterProps) => {
|
|
69
74
|
const [order, setOrder] = useState(
|
|
70
75
|
OrderHelper.getActiveEnrollmentOrder(enrollment.orders || [], product.id),
|
|
71
76
|
);
|
|
@@ -103,6 +108,7 @@ const ProductCertificateFooter = ({ product, enrollment }: ProductCertificateFoo
|
|
|
103
108
|
className="dashboard-item__button"
|
|
104
109
|
product={product}
|
|
105
110
|
enrollment={enrollment}
|
|
111
|
+
isWithdrawable={isWithdrawable}
|
|
106
112
|
buttonProps={{ size: 'small' }}
|
|
107
113
|
disabled={!isPurchasable}
|
|
108
114
|
onFinish={(o) => {
|
|
@@ -43,6 +43,7 @@ const CourseProductItemFooter = ({
|
|
|
43
43
|
course={course}
|
|
44
44
|
product={courseProductRelation.product as CredentialProduct}
|
|
45
45
|
organizations={courseProductRelation.organizations}
|
|
46
|
+
isWithdrawable={courseProductRelation.is_withdrawable}
|
|
46
47
|
disabled={!canPurchase}
|
|
47
48
|
buttonProps={{ fullWidth: true }}
|
|
48
49
|
/>
|
|
@@ -61,6 +62,7 @@ const CourseProductItemFooter = ({
|
|
|
61
62
|
course={course}
|
|
62
63
|
product={courseProductRelation.product as CredentialProduct}
|
|
63
64
|
organizations={courseProductRelation.organizations}
|
|
65
|
+
isWithdrawable={courseProductRelation.is_withdrawable}
|
|
64
66
|
disabled={!canPurchase}
|
|
65
67
|
orderGroup={orderGroup}
|
|
66
68
|
buttonProps={{ fullWidth: true }}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "2.30.1-
|
|
3
|
+
"version": "2.30.1-dev17",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -148,7 +148,8 @@
|
|
|
148
148
|
"workerDirectory": "../richie/static/richie/js"
|
|
149
149
|
},
|
|
150
150
|
"volta": {
|
|
151
|
-
"node": "20.11.0"
|
|
151
|
+
"node": "20.11.0",
|
|
152
|
+
"yarn": "1.22.22"
|
|
152
153
|
},
|
|
153
154
|
"devDependencies": {
|
|
154
155
|
"@storybook/addon-mdx-gfm": "8.3.6",
|