richie-education 2.30.1-dev9 → 2.31.1-dev4
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/i18n/locales/ar-SA.json +32 -0
- package/i18n/locales/es-ES.json +32 -0
- package/i18n/locales/fa-IR.json +32 -0
- package/i18n/locales/fr-CA.json +32 -0
- package/i18n/locales/fr-FR.json +32 -0
- package/i18n/locales/ko-KR.json +32 -0
- package/i18n/locales/pt-PT.json +125 -93
- package/i18n/locales/ru-RU.json +32 -0
- package/i18n/locales/vi-VN.json +32 -0
- package/js/api/lms/openedx-fonzie.spec.ts +1 -1
- package/js/api/lms/openedx-hawthorn.spec.ts +1 -1
- package/js/components/CourseGlimpse/utils.ts +3 -3
- package/js/components/CourseGlimpseList/utils.ts +2 -2
- package/js/components/PaymentInterfaces/types.ts +1 -0
- package/js/components/PurchaseButton/index.spec.tsx +20 -2
- package/js/components/PurchaseButton/index.tsx +3 -0
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -0
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +6 -1
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -0
- package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +8 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +17 -2
- package/js/components/SaleTunnel/WithdrawRightCheckbox/index.tsx +105 -0
- package/js/components/SaleTunnel/index.credential.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process.spec.tsx +22 -2
- package/js/components/SaleTunnel/index.spec.tsx +83 -6
- 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/contexts/SessionContext/index.spec.tsx +2 -2
- package/js/hooks/useCourseProductUnion/index.ts +2 -1
- package/js/hooks/useTeacherCoursesSearch/index.tsx +2 -2
- package/js/translations/ar-SA.json +1 -1
- package/js/translations/es-ES.json +1 -1
- package/js/translations/fa-IR.json +1 -1
- package/js/translations/fr-CA.json +1 -1
- package/js/translations/fr-FR.json +1 -1
- package/js/translations/ko-KR.json +1 -1
- package/js/translations/pt-PT.json +1 -1
- package/js/translations/ru-RU.json +1 -1
- package/js/translations/vi-VN.json +1 -1
- package/js/types/Joanie.ts +9 -4
- package/js/utils/test/factories/factories.spec.ts +1 -1
- package/js/utils/test/factories/joanie.ts +8 -5
- package/js/utils/test/factories/openEdx.tsx +1 -1
- package/js/utils/test/factories/richie.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
- package/scss/colors/_theme.scss +4 -0
- package/scss/components/_index.scss +1 -0
- package/scss/components/templates/richie/_multiple-columns.scss +8 -5
- package/scss/components/templates/richie/simpletext/_simpletext.scss +43 -0
package/i18n/locales/ru-RU.json
CHANGED
|
@@ -1355,6 +1355,34 @@
|
|
|
1355
1355
|
"description": "Message explaining the subscription process with a training agreement to sign and a payment method to set.",
|
|
1356
1356
|
"message": "To enroll in the training, you will first be invited to sign the training agreement and then to define a payment method."
|
|
1357
1357
|
},
|
|
1358
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate .waiveCheckboxHelperClause2": {
|
|
1359
|
+
"description": "Second clause item for the waiver checkbox.",
|
|
1360
|
+
"message": "I understand that if I access the exam during this period, I expressly waive my right of withdrawal."
|
|
1361
|
+
},
|
|
1362
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate.waiveCheckboxHelperClause1": {
|
|
1363
|
+
"description": "First clause item for the waiver checkbox.",
|
|
1364
|
+
"message": "I acknowledge that I have been informed of my legal right of withdrawal, which allows me to cancel my registration within 14 days from the date of payment."
|
|
1365
|
+
},
|
|
1366
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate.waiverLabel": {
|
|
1367
|
+
"description": "Text to explain why the user has to waive to its withdrawal right.",
|
|
1368
|
+
"message": "If you access the exam, you acknowledge waiving your 14-day withdrawal right, as provided for in Article L221-18 of the French Consumer Code."
|
|
1369
|
+
},
|
|
1370
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause1": {
|
|
1371
|
+
"description": "First clause item for the waiver checkbox.",
|
|
1372
|
+
"message": "I acknowledge that I have expressly requested to begin the training before the expiration date of the withdrawal period."
|
|
1373
|
+
},
|
|
1374
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause2": {
|
|
1375
|
+
"description": "Second clause item for the waiver checkbox.",
|
|
1376
|
+
"message": "I expressly waive my right of withdrawal in order to begin the training before the expiration of the withdrawal period."
|
|
1377
|
+
},
|
|
1378
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiverLabel": {
|
|
1379
|
+
"description": "Text to explain why the user has to waive to its withdrawal right.",
|
|
1380
|
+
"message": "The training program you wish to enroll in begins before the end of the 14-day withdrawal period mentioned in Article L221-18 of the French Consumer Code. You must check the box below to proceed with your registration."
|
|
1381
|
+
},
|
|
1382
|
+
"components.SaleTunnel.WithdrawRightCheckbox.waiveCheckboxLabel": {
|
|
1383
|
+
"description": "Label of the checkbox to waive the withdrawal right.",
|
|
1384
|
+
"message": "By checking this box:"
|
|
1385
|
+
},
|
|
1358
1386
|
"components.SaleTunnel.callToActionDescription": {
|
|
1359
1387
|
"description": "Additional description announced by screen readers when focusing the call to action buying button",
|
|
1360
1388
|
"message": "Purchase {product}"
|
|
@@ -1535,6 +1563,10 @@
|
|
|
1535
1563
|
"description": "Error message shown when order creation request failed because there is no remaining available seat for the product.",
|
|
1536
1564
|
"message": "There are no more places available for this product."
|
|
1537
1565
|
},
|
|
1566
|
+
"components.SubscriptionButton.errorWithdrawalRight": {
|
|
1567
|
+
"description": "Error message shown when the user must waive its withdrawal right but doesn't.",
|
|
1568
|
+
"message": "You must waive your withdrawal right."
|
|
1569
|
+
},
|
|
1538
1570
|
"components.SubscriptionButton.orderCreationInProgress": {
|
|
1539
1571
|
"description": "Label for screen reader when an order creation is in progress.",
|
|
1540
1572
|
"message": "Order creation in progress"
|
package/i18n/locales/vi-VN.json
CHANGED
|
@@ -1355,6 +1355,34 @@
|
|
|
1355
1355
|
"description": "Message explaining the subscription process with a training agreement to sign and a payment method to set.",
|
|
1356
1356
|
"message": "To enroll in the training, you will first be invited to sign the training agreement and then to define a payment method."
|
|
1357
1357
|
},
|
|
1358
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate .waiveCheckboxHelperClause2": {
|
|
1359
|
+
"description": "Second clause item for the waiver checkbox.",
|
|
1360
|
+
"message": "I understand that if I access the exam during this period, I expressly waive my right of withdrawal."
|
|
1361
|
+
},
|
|
1362
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate.waiveCheckboxHelperClause1": {
|
|
1363
|
+
"description": "First clause item for the waiver checkbox.",
|
|
1364
|
+
"message": "I acknowledge that I have been informed of my legal right of withdrawal, which allows me to cancel my registration within 14 days from the date of payment."
|
|
1365
|
+
},
|
|
1366
|
+
"components.SaleTunnel.WithdrawRightCheckbox.certificate.waiverLabel": {
|
|
1367
|
+
"description": "Text to explain why the user has to waive to its withdrawal right.",
|
|
1368
|
+
"message": "If you access the exam, you acknowledge waiving your 14-day withdrawal right, as provided for in Article L221-18 of the French Consumer Code."
|
|
1369
|
+
},
|
|
1370
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause1": {
|
|
1371
|
+
"description": "First clause item for the waiver checkbox.",
|
|
1372
|
+
"message": "I acknowledge that I have expressly requested to begin the training before the expiration date of the withdrawal period."
|
|
1373
|
+
},
|
|
1374
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause2": {
|
|
1375
|
+
"description": "Second clause item for the waiver checkbox.",
|
|
1376
|
+
"message": "I expressly waive my right of withdrawal in order to begin the training before the expiration of the withdrawal period."
|
|
1377
|
+
},
|
|
1378
|
+
"components.SaleTunnel.WithdrawRightCheckbox.credential.waiverLabel": {
|
|
1379
|
+
"description": "Text to explain why the user has to waive to its withdrawal right.",
|
|
1380
|
+
"message": "The training program you wish to enroll in begins before the end of the 14-day withdrawal period mentioned in Article L221-18 of the French Consumer Code. You must check the box below to proceed with your registration."
|
|
1381
|
+
},
|
|
1382
|
+
"components.SaleTunnel.WithdrawRightCheckbox.waiveCheckboxLabel": {
|
|
1383
|
+
"description": "Label of the checkbox to waive the withdrawal right.",
|
|
1384
|
+
"message": "By checking this box:"
|
|
1385
|
+
},
|
|
1358
1386
|
"components.SaleTunnel.callToActionDescription": {
|
|
1359
1387
|
"description": "Additional description announced by screen readers when focusing the call to action buying button",
|
|
1360
1388
|
"message": "Purchase {product}"
|
|
@@ -1535,6 +1563,10 @@
|
|
|
1535
1563
|
"description": "Error message shown when order creation request failed because there is no remaining available seat for the product.",
|
|
1536
1564
|
"message": "There are no more places available for this product."
|
|
1537
1565
|
},
|
|
1566
|
+
"components.SubscriptionButton.errorWithdrawalRight": {
|
|
1567
|
+
"description": "Error message shown when the user must waive its withdrawal right but doesn't.",
|
|
1568
|
+
"message": "You must waive your withdrawal right."
|
|
1569
|
+
},
|
|
1538
1570
|
"components.SubscriptionButton.orderCreationInProgress": {
|
|
1539
1571
|
"description": "Label for screen reader when an order creation is in progress.",
|
|
1540
1572
|
"message": "Order creation in progress"
|
|
@@ -22,7 +22,7 @@ describe('Fonzie API', () => {
|
|
|
22
22
|
|
|
23
23
|
it('uses its own route to get user information', async () => {
|
|
24
24
|
const user = {
|
|
25
|
-
username: faker.internet.
|
|
25
|
+
username: faker.internet.username(),
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
fetchMock.get('https://demo.endpoint.api/api/v1.0/user/me', user);
|
|
@@ -48,7 +48,7 @@ describe('OpenEdX Hawthorn API', () => {
|
|
|
48
48
|
describe('enrollment', () => {
|
|
49
49
|
beforeEach(() => {
|
|
50
50
|
courseId = faker.string.uuid();
|
|
51
|
-
username = faker.internet.
|
|
51
|
+
username = faker.internet.username();
|
|
52
52
|
fetchMock.restore();
|
|
53
53
|
mockHandle.mockRestore();
|
|
54
54
|
});
|
|
@@ -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
|
</>
|
|
@@ -35,6 +35,8 @@ export interface SaleTunnelContextType {
|
|
|
35
35
|
setBillingAddress: (address?: Address) => void;
|
|
36
36
|
creditCard?: CreditCard;
|
|
37
37
|
setCreditCard: (creditCard?: CreditCard) => void;
|
|
38
|
+
hasWaivedWithdrawalRight: boolean;
|
|
39
|
+
setHasWaivedWithdrawalRight: (hasWaivedWithdrawalRight: boolean) => void;
|
|
38
40
|
registerSubmitCallback: (key: string, callback: () => Promise<void>) => void;
|
|
39
41
|
unregisterSubmitCallback: (key: string) => void;
|
|
40
42
|
runSubmitCallbacks: () => Promise<void>;
|
|
@@ -76,6 +78,7 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
76
78
|
});
|
|
77
79
|
const [billingAddress, setBillingAddress] = useState<Address>();
|
|
78
80
|
const [creditCard, setCreditCard] = useState<CreditCard>();
|
|
81
|
+
const [hasWaivedWithdrawalRight, setHasWaivedWithdrawalRight] = useState(false);
|
|
79
82
|
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.IDLE);
|
|
80
83
|
const [submitCallbacks, setSubmitCallbacks] = useState<Map<string, () => Promise<void>>>(
|
|
81
84
|
new Map(),
|
|
@@ -115,6 +118,8 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
115
118
|
setBillingAddress,
|
|
116
119
|
creditCard,
|
|
117
120
|
setCreditCard,
|
|
121
|
+
hasWaivedWithdrawalRight,
|
|
122
|
+
setHasWaivedWithdrawalRight,
|
|
118
123
|
nextStep,
|
|
119
124
|
step,
|
|
120
125
|
registerSubmitCallback: (key, callback) => {
|
|
@@ -131,7 +136,7 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
131
136
|
await Promise.all(Array.from(submitCallbacks.values()).map((cb) => cb()));
|
|
132
137
|
},
|
|
133
138
|
}),
|
|
134
|
-
[props, order, billingAddress, creditCard, step, submitCallbacks],
|
|
139
|
+
[props, order, billingAddress, creditCard, step, submitCallbacks, hasWaivedWithdrawalRight],
|
|
135
140
|
);
|
|
136
141
|
|
|
137
142
|
return (
|
|
@@ -7,6 +7,7 @@ import { useSession } from 'contexts/SessionContext';
|
|
|
7
7
|
import useOpenEdxProfile from 'hooks/useOpenEdxProfile';
|
|
8
8
|
import { usePaymentSchedule } from 'hooks/usePaymentSchedule';
|
|
9
9
|
import { Spinner } from 'components/Spinner';
|
|
10
|
+
import WithdrawRightCheckbox from 'components/SaleTunnel/WithdrawRightCheckbox';
|
|
10
11
|
|
|
11
12
|
const messages = defineMessages({
|
|
12
13
|
title: {
|
|
@@ -71,6 +72,7 @@ export const SaleTunnelInformation = () => {
|
|
|
71
72
|
<div>
|
|
72
73
|
<PaymentScheduleBlock />
|
|
73
74
|
<Total />
|
|
75
|
+
<WithdrawRightCheckbox />
|
|
74
76
|
</div>
|
|
75
77
|
</div>
|
|
76
78
|
);
|
|
@@ -4,4 +4,12 @@
|
|
|
4
4
|
margin-top: 0.5rem;
|
|
5
5
|
margin-bottom: 0;
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
&__waiveCheckbox {
|
|
9
|
+
& > .waiveCheckbox__input {
|
|
10
|
+
/* Just add 1 px offset to prevent border input to be hidden
|
|
11
|
+
due to the overflow hidden applied to the parent block */
|
|
12
|
+
margin-left: 1px;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
7
15
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { Alert, Button, VariantType } from '@openfun/cunningham-react';
|
|
3
3
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
4
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
@@ -50,6 +50,11 @@ const messages = defineMessages({
|
|
|
50
50
|
description: "Error message shown when the user didn't select a billing address.",
|
|
51
51
|
id: 'components.SubscriptionButton.errorAddress',
|
|
52
52
|
},
|
|
53
|
+
errorWithdrawalRight: {
|
|
54
|
+
defaultMessage: 'You must waive your withdrawal right.',
|
|
55
|
+
description: "Error message shown when the user must waive its withdrawal right but doesn't.",
|
|
56
|
+
id: 'components.SubscriptionButton.errorWithdrawalRight',
|
|
57
|
+
},
|
|
53
58
|
orderCreationInProgress: {
|
|
54
59
|
defaultMessage: 'Order creation in progress',
|
|
55
60
|
description: 'Label for screen reader when an order creation is in progress.',
|
|
@@ -65,7 +70,10 @@ enum ComponentStates {
|
|
|
65
70
|
|
|
66
71
|
interface Props {
|
|
67
72
|
buildOrderPayload: (
|
|
68
|
-
payload: Pick<
|
|
73
|
+
payload: Pick<
|
|
74
|
+
OrderCreationPayload,
|
|
75
|
+
'product_id' | 'billing_address' | 'order_group_id' | 'has_waived_withdrawal_right'
|
|
76
|
+
>,
|
|
69
77
|
) => OrderCreationPayload;
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -74,6 +82,7 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
74
82
|
order,
|
|
75
83
|
creditCard,
|
|
76
84
|
billingAddress,
|
|
85
|
+
hasWaivedWithdrawalRight,
|
|
77
86
|
product,
|
|
78
87
|
nextStep,
|
|
79
88
|
runSubmitCallbacks,
|
|
@@ -107,10 +116,16 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
107
116
|
return;
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
if (!saleTunnelProps.isWithdrawable && !hasWaivedWithdrawalRight) {
|
|
120
|
+
handleError(SubscriptionErrorMessageId.ERROR_WITHDRAWAL_RIGHT);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
const payload = buildOrderPayload({
|
|
111
125
|
product_id: product.id,
|
|
112
126
|
billing_address: billingAddress!,
|
|
113
127
|
order_group_id: saleTunnelProps.orderGroup?.id,
|
|
128
|
+
has_waived_withdrawal_right: hasWaivedWithdrawalRight,
|
|
114
129
|
});
|
|
115
130
|
|
|
116
131
|
orderMethods.create(payload, {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Alert, Checkbox, VariantType } from '@openfun/cunningham-react';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
4
|
+
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
5
|
+
import { ProductType } from 'types/Joanie';
|
|
6
|
+
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
waiveCheckboxLabel: {
|
|
9
|
+
defaultMessage: 'By checking this box:',
|
|
10
|
+
description: 'Label of the checkbox to waive the withdrawal right.',
|
|
11
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.waiveCheckboxLabel',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const credentialProductMessages = defineMessages({
|
|
16
|
+
waiveCheckboxExplanation: {
|
|
17
|
+
defaultMessage:
|
|
18
|
+
'The training program you wish to enroll in begins before the end of the 14-day withdrawal period mentioned in Article L221-18 of the French Consumer Code. You must check the box below to proceed with your registration.',
|
|
19
|
+
description: 'Text to explain why the user has to waive to its withdrawal right.',
|
|
20
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.credential.waiverLabel',
|
|
21
|
+
},
|
|
22
|
+
waiveCheckboxHelperClause1: {
|
|
23
|
+
defaultMessage:
|
|
24
|
+
'I acknowledge that I have expressly requested to begin the training before the expiration date of the withdrawal period.',
|
|
25
|
+
description: 'First clause item for the waiver checkbox.',
|
|
26
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause1',
|
|
27
|
+
},
|
|
28
|
+
waiveCheckboxHelperClause2: {
|
|
29
|
+
defaultMessage:
|
|
30
|
+
'I expressly waive my right of withdrawal in order to begin the training before the expiration of the withdrawal period.',
|
|
31
|
+
description: 'Second clause item for the waiver checkbox.',
|
|
32
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.credential.waiveCheckboxHelperClause2',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const certificateProductMessages = defineMessages({
|
|
37
|
+
waiveCheckboxExplanation: {
|
|
38
|
+
defaultMessage:
|
|
39
|
+
'If you access the exam, you acknowledge waiving your 14-day withdrawal right, as provided for in Article L221-18 of the French Consumer Code.',
|
|
40
|
+
description: 'Text to explain why the user has to waive to its withdrawal right.',
|
|
41
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.certificate.waiverLabel',
|
|
42
|
+
},
|
|
43
|
+
waiveCheckboxHelperClause1: {
|
|
44
|
+
defaultMessage:
|
|
45
|
+
'I acknowledge that I have been informed of my legal right of withdrawal, which allows me to cancel my registration within 14 days from the date of payment.',
|
|
46
|
+
description: 'First clause item for the waiver checkbox.',
|
|
47
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.certificate.waiveCheckboxHelperClause1',
|
|
48
|
+
},
|
|
49
|
+
waiveCheckboxHelperClause2: {
|
|
50
|
+
defaultMessage:
|
|
51
|
+
'I understand that if I access the exam during this period, I expressly waive my right of withdrawal.',
|
|
52
|
+
description: 'Second clause item for the waiver checkbox.',
|
|
53
|
+
id: 'components.SaleTunnel.WithdrawRightCheckbox.certificate .waiveCheckboxHelperClause2',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const WithdrawRightCheckbox = () => {
|
|
58
|
+
const {
|
|
59
|
+
props: { isWithdrawable, product },
|
|
60
|
+
registerSubmitCallback,
|
|
61
|
+
unregisterSubmitCallback,
|
|
62
|
+
hasWaivedWithdrawalRight,
|
|
63
|
+
setHasWaivedWithdrawalRight,
|
|
64
|
+
} = useSaleTunnelContext();
|
|
65
|
+
const intl = useIntl();
|
|
66
|
+
const clauseMessages =
|
|
67
|
+
product.type === ProductType.CERTIFICATE
|
|
68
|
+
? certificateProductMessages
|
|
69
|
+
: credentialProductMessages;
|
|
70
|
+
const [hasErrorState, setHasError] = useState(false);
|
|
71
|
+
const setError = useCallback(async () => {
|
|
72
|
+
setHasError(!isWithdrawable && !hasWaivedWithdrawalRight);
|
|
73
|
+
}, [hasWaivedWithdrawalRight, isWithdrawable]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
registerSubmitCallback('withdrawalRight', setError);
|
|
77
|
+
return () => {
|
|
78
|
+
unregisterSubmitCallback('withdrawalRight');
|
|
79
|
+
};
|
|
80
|
+
}, [setError]);
|
|
81
|
+
|
|
82
|
+
if (isWithdrawable) return null;
|
|
83
|
+
return (
|
|
84
|
+
<section
|
|
85
|
+
className="mt-t subscription-button__waiveCheckbox"
|
|
86
|
+
data-testid="withdraw-right-checkbox"
|
|
87
|
+
>
|
|
88
|
+
<Alert type={hasErrorState ? VariantType.ERROR : VariantType.WARNING} className="mb-s">
|
|
89
|
+
<FormattedMessage {...clauseMessages.waiveCheckboxExplanation} />
|
|
90
|
+
</Alert>
|
|
91
|
+
<Checkbox
|
|
92
|
+
className="waiveCheckbox__input"
|
|
93
|
+
label={intl.formatMessage(messages.waiveCheckboxLabel)}
|
|
94
|
+
checked={hasWaivedWithdrawalRight}
|
|
95
|
+
onChange={(e) => setHasWaivedWithdrawalRight(e.target.checked)}
|
|
96
|
+
textItems={[
|
|
97
|
+
intl.formatMessage(clauseMessages.waiveCheckboxHelperClause1),
|
|
98
|
+
intl.formatMessage(clauseMessages.waiveCheckboxHelperClause2),
|
|
99
|
+
]}
|
|
100
|
+
/>
|
|
101
|
+
</section>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default WithdrawRightCheckbox;
|
|
@@ -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();
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
CreditCardFactory,
|
|
21
21
|
PaymentFactory,
|
|
22
22
|
PaymentInstallmentFactory,
|
|
23
|
+
ProductFactory,
|
|
23
24
|
} from 'utils/test/factories/joanie';
|
|
24
25
|
import { CourseRun, NOT_CANCELED_ORDER_STATES, OrderState } from 'types/Joanie';
|
|
25
26
|
import { Priority } from 'types';
|
|
@@ -97,9 +98,13 @@ describe('SaleTunnel', () => {
|
|
|
97
98
|
* Initialization.
|
|
98
99
|
*/
|
|
99
100
|
const course = PacedCourseFactory().one();
|
|
100
|
-
const
|
|
101
|
+
const product = ProductFactory().one();
|
|
102
|
+
const relation = CourseProductRelationFactory({
|
|
103
|
+
course,
|
|
104
|
+
product,
|
|
105
|
+
is_withdrawable: false,
|
|
106
|
+
}).one();
|
|
101
107
|
const paymentSchedule = PaymentInstallmentFactory().many(2);
|
|
102
|
-
const { product } = relation;
|
|
103
108
|
|
|
104
109
|
fetchMock.get(
|
|
105
110
|
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
|
|
@@ -265,6 +270,13 @@ describe('SaleTunnel', () => {
|
|
|
265
270
|
priceFormatter(product.price_currency, product.price).replace(/(\u202F|\u00a0)/g, ' '),
|
|
266
271
|
);
|
|
267
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Make sure the checkbox to waive withdrawal right is displayed
|
|
275
|
+
*/
|
|
276
|
+
const $waiveCheckbox = within(screen.getByTestId('withdraw-right-checkbox')).getByRole(
|
|
277
|
+
'checkbox',
|
|
278
|
+
);
|
|
279
|
+
|
|
268
280
|
/**
|
|
269
281
|
* Subscribe
|
|
270
282
|
*/
|
|
@@ -282,6 +294,14 @@ describe('SaleTunnel', () => {
|
|
|
282
294
|
}) as HTMLButtonElement;
|
|
283
295
|
await user.click($button);
|
|
284
296
|
|
|
297
|
+
/**
|
|
298
|
+
* An error should be displayed if the user has not waived its withdrawal right.
|
|
299
|
+
*/
|
|
300
|
+
screen.getByText('You must waive your withdrawal right.');
|
|
301
|
+
|
|
302
|
+
await user.click($waiveCheckbox);
|
|
303
|
+
await user.click($button);
|
|
304
|
+
|
|
285
305
|
order.state = OrderState.TO_SAVE_PAYMENT_METHOD;
|
|
286
306
|
order.contract = ContractFactory({ student_signed_on: new Date().toISOString() }).one();
|
|
287
307
|
|