richie-education 2.28.2-dev26 → 2.28.2-dev53
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 +42 -17
- package/js/api/lms/dummy.ts +1 -12
- package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
- package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
- package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
- package/js/components/ContractFrame/_styles.scss +6 -14
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.spec.tsx +15 -45
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.tsx +17 -24
- package/js/components/DownloadContractButton/index.spec.tsx +1 -1
- package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
- package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
- package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
- package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
- package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
- package/js/components/PaymentInterfaces/types.ts +5 -2
- package/js/components/PaymentScheduleGrid/_styles.scss +13 -0
- package/js/components/PaymentScheduleGrid/index.tsx +50 -70
- package/js/components/PurchaseButton/index.spec.tsx +84 -37
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
- package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
- package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +80 -27
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +16 -20
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
- package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.scss +4 -5
- package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +39 -11
- package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
- package/js/components/SaleTunnel/_styles.scss +16 -5
- package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
- package/js/components/SaleTunnel/index.credential.spec.tsx +14 -25
- package/js/components/SaleTunnel/index.full-process.spec.tsx +116 -48
- package/js/components/SaleTunnel/index.spec.tsx +334 -717
- package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
- package/js/components/SignContractButton/index.spec.tsx +16 -20
- package/js/components/SignContractButton/index.tsx +3 -1
- package/js/hooks/useCreditCards/index.spec.tsx +70 -6
- package/js/hooks/useCreditCards/index.ts +49 -11
- package/js/hooks/useOrders/index.spec.tsx +322 -0
- package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
- package/js/hooks/usePaymentSchedule.tsx +23 -0
- package/js/hooks/useProductOrder/index.spec.tsx +77 -60
- package/js/hooks/useProductOrder/index.tsx +2 -2
- package/js/hooks/useResources/useResourcesRoot.ts +4 -3
- package/js/index.tsx +2 -0
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
- package/js/settings/settings.test.ts +11 -2
- package/js/types/Joanie.ts +77 -31
- package/js/utils/OrderHelper/index.ts +47 -38
- package/js/utils/test/factories/joanie.ts +66 -68
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +114 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +99 -12
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +7 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +126 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +209 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +40 -25
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +28 -22
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
- package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
- package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
- package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
- package/package.json +2 -1
- package/scss/components/_index.scss +4 -2
- package/js/components/PaymentButton/_styles.scss +0 -27
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -338
- package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
- /package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/_styles.scss +0 -0
|
@@ -4,6 +4,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
4
4
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
7
|
+
import queryString from 'query-string';
|
|
7
8
|
import {
|
|
8
9
|
CourseStateFactory,
|
|
9
10
|
PacedCourseFactory,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
} from 'utils/test/factories/joanie';
|
|
18
19
|
import { SessionProvider } from 'contexts/SessionContext';
|
|
19
20
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
20
|
-
import { ProductType } from 'types/Joanie';
|
|
21
|
+
import { NOT_CANCELED_ORDER_STATES, ProductType } from 'types/Joanie';
|
|
21
22
|
import { Priority } from 'types';
|
|
22
23
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
23
24
|
import { User } from 'types/User';
|
|
@@ -102,10 +103,18 @@ describe('PurchaseButton', () => {
|
|
|
102
103
|
it('shows cta to open sale tunnel when user is authenticated', async () => {
|
|
103
104
|
const courseCode = '00000';
|
|
104
105
|
const product = ProductFactory().one();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
const orderQueryParameters = {
|
|
107
|
+
course_code: courseCode,
|
|
108
|
+
product_id: product.id,
|
|
109
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
110
|
+
};
|
|
111
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
112
|
+
fetchMock
|
|
113
|
+
.get(url, {})
|
|
114
|
+
.get(
|
|
115
|
+
`https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
|
|
116
|
+
[],
|
|
117
|
+
);
|
|
109
118
|
|
|
110
119
|
render(
|
|
111
120
|
<Wrapper client={createTestQueryClient({ user: richieUser })}>
|
|
@@ -137,10 +146,18 @@ describe('PurchaseButton', () => {
|
|
|
137
146
|
const product = ProductFactory({ remaining_order_count: null }).one();
|
|
138
147
|
fetchMock.get(`https://demo.endpoint/api/user/v1/accounts/${user.username}`, {});
|
|
139
148
|
fetchMock.get(`https://demo.endpoint/api/user/v1/preferences/${user.username}`, {});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const orderQueryParameters = {
|
|
150
|
+
course_code: courseCode,
|
|
151
|
+
product_id: product.id,
|
|
152
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
153
|
+
};
|
|
154
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
155
|
+
fetchMock
|
|
156
|
+
.get(url, {})
|
|
157
|
+
.get(
|
|
158
|
+
`https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
|
|
159
|
+
[],
|
|
160
|
+
);
|
|
144
161
|
render(
|
|
145
162
|
<Wrapper client={createTestQueryClient({ user })}>
|
|
146
163
|
<PurchaseButton
|
|
@@ -170,10 +187,18 @@ describe('PurchaseButton', () => {
|
|
|
170
187
|
it('shows cta to open sale tunnel when remaining orders is undefined', async () => {
|
|
171
188
|
const courseCode = '00000';
|
|
172
189
|
const product = ProductFactory().one();
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
const orderQueryParameters = {
|
|
191
|
+
course_code: courseCode,
|
|
192
|
+
product_id: product.id,
|
|
193
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
194
|
+
};
|
|
195
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
196
|
+
fetchMock
|
|
197
|
+
.get(url, {})
|
|
198
|
+
.get(
|
|
199
|
+
`https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
|
|
200
|
+
[],
|
|
201
|
+
);
|
|
177
202
|
delete product.remaining_order_count;
|
|
178
203
|
|
|
179
204
|
render(
|
|
@@ -205,10 +230,13 @@ describe('PurchaseButton', () => {
|
|
|
205
230
|
it('renders a disabled CTA if the product have no remaining orders', async () => {
|
|
206
231
|
const courseCode = '00000';
|
|
207
232
|
const product = ProductFactory({ remaining_order_count: 0 }).one();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
233
|
+
const orderQueryParameters = {
|
|
234
|
+
course_code: courseCode,
|
|
235
|
+
product_id: product.id,
|
|
236
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
237
|
+
};
|
|
238
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
239
|
+
fetchMock.get(url, {});
|
|
212
240
|
render(
|
|
213
241
|
<Wrapper client={createTestQueryClient({ user: richieUser })}>
|
|
214
242
|
<PurchaseButton
|
|
@@ -241,10 +269,14 @@ describe('PurchaseButton', () => {
|
|
|
241
269
|
const courseCode = '00000';
|
|
242
270
|
const product = ProductFactory({ ...productData, type: ProductType.CREDENTIAL }).one();
|
|
243
271
|
product.target_courses[0].course_runs = [];
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
272
|
+
|
|
273
|
+
const orderQueryParameters = {
|
|
274
|
+
course_code: courseCode,
|
|
275
|
+
product_id: product.id,
|
|
276
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
277
|
+
};
|
|
278
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
279
|
+
fetchMock.get(url, {});
|
|
248
280
|
|
|
249
281
|
render(
|
|
250
282
|
<Wrapper client={createTestQueryClient({ user: richieUser })}>
|
|
@@ -290,10 +322,14 @@ describe('PurchaseButton', () => {
|
|
|
290
322
|
const product = CertificateProductFactory().one();
|
|
291
323
|
const enrollment = EnrollmentFactory().one();
|
|
292
324
|
enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
325
|
+
|
|
326
|
+
const orderQueryParameters = {
|
|
327
|
+
enrollment_id: enrollment.id,
|
|
328
|
+
product_id: product.id,
|
|
329
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
330
|
+
};
|
|
331
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
332
|
+
fetchMock.get(url, {});
|
|
297
333
|
|
|
298
334
|
render(
|
|
299
335
|
<Wrapper client={createTestQueryClient({ user: true })}>
|
|
@@ -344,10 +380,13 @@ describe('PurchaseButton', () => {
|
|
|
344
380
|
const enrollment = EnrollmentFactory().one();
|
|
345
381
|
enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
|
|
346
382
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
383
|
+
const orderQueryParameters = {
|
|
384
|
+
enrollment_id: enrollment.id,
|
|
385
|
+
product_id: product.id,
|
|
386
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
387
|
+
};
|
|
388
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
389
|
+
fetchMock.get(url, {});
|
|
351
390
|
|
|
352
391
|
render(
|
|
353
392
|
<Wrapper client={createTestQueryClient({ user: true })}>
|
|
@@ -373,10 +412,14 @@ describe('PurchaseButton', () => {
|
|
|
373
412
|
it('renders a disabled CTA if product has no target courses', async () => {
|
|
374
413
|
const courseCode = '00000';
|
|
375
414
|
const product = ProductFactory().one();
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
415
|
+
|
|
416
|
+
const orderQueryParameters = {
|
|
417
|
+
course_code: courseCode,
|
|
418
|
+
product_id: product.id,
|
|
419
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
420
|
+
};
|
|
421
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
422
|
+
fetchMock.get(url, {});
|
|
380
423
|
product.target_courses = [];
|
|
381
424
|
|
|
382
425
|
render(
|
|
@@ -404,10 +447,14 @@ describe('PurchaseButton', () => {
|
|
|
404
447
|
it('does not render CTA if disabled property is false', async () => {
|
|
405
448
|
const courseCode = '00000';
|
|
406
449
|
const product = ProductFactory().one();
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
450
|
+
|
|
451
|
+
const orderQueryParameters = {
|
|
452
|
+
course_code: courseCode,
|
|
453
|
+
product_id: product.id,
|
|
454
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
455
|
+
};
|
|
456
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`;
|
|
457
|
+
fetchMock.get(url, {});
|
|
411
458
|
|
|
412
459
|
render(
|
|
413
460
|
<Wrapper client={createTestQueryClient({ user: true })}>
|
|
@@ -59,10 +59,11 @@ describe('AddressSelector', () => {
|
|
|
59
59
|
setBillingAddress,
|
|
60
60
|
setCreditCard: jest.fn(),
|
|
61
61
|
onPaymentSuccess: jest.fn(),
|
|
62
|
-
step: SaleTunnelStep.
|
|
62
|
+
step: SaleTunnelStep.IDLE,
|
|
63
63
|
registerSubmitCallback: jest.fn(),
|
|
64
64
|
unregisterSubmitCallback: jest.fn(),
|
|
65
65
|
runSubmitCallbacks: jest.fn(),
|
|
66
|
+
nextStep: jest.fn(),
|
|
66
67
|
}),
|
|
67
68
|
[billingAddress],
|
|
68
69
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SaleTunnelProps } from 'components/SaleTunnel/index';
|
|
2
2
|
import { CertificateProduct } from 'types/Joanie';
|
|
3
3
|
import { GenericSaleTunnel } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
4
|
-
import { GenericPaymentButton } from 'components/SaleTunnel/GenericPaymentButton';
|
|
5
4
|
import { CertificateProductPath } from 'components/SaleTunnel/CertificateSaleTunnel/CertificateProductPath';
|
|
5
|
+
import SubscriptionButton from 'components/SaleTunnel/SubscriptionButton';
|
|
6
6
|
|
|
7
7
|
interface CertificateSaleTunnelProps extends Omit<SaleTunnelProps, 'product'> {
|
|
8
8
|
product: CertificateProduct;
|
|
@@ -23,7 +23,7 @@ const CertificatePaymentButton = ({
|
|
|
23
23
|
enrollment,
|
|
24
24
|
}: Pick<CertificateSaleTunnelProps, 'enrollment'>) => {
|
|
25
25
|
return (
|
|
26
|
-
<
|
|
26
|
+
<SubscriptionButton
|
|
27
27
|
buildOrderPayload={(payload) => {
|
|
28
28
|
return {
|
|
29
29
|
...payload,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CredentialProduct } from 'types/Joanie';
|
|
2
2
|
import { GenericSaleTunnel } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
3
3
|
import { SaleTunnelProps } from 'components/SaleTunnel/index';
|
|
4
|
-
import { GenericPaymentButton } from 'components/SaleTunnel/GenericPaymentButton';
|
|
5
4
|
import { CredentialProductPath } from 'components/SaleTunnel/CredentialSaleTunnel/CredentialProductPath';
|
|
5
|
+
import SubscriptionButton from 'components/SaleTunnel/SubscriptionButton';
|
|
6
6
|
|
|
7
7
|
interface CredentialSaleTunnelProps extends Omit<SaleTunnelProps, 'product'> {
|
|
8
8
|
product: CredentialProduct;
|
|
@@ -21,17 +21,13 @@ export const CredentialSaleTunnel = (props: CredentialSaleTunnelProps) => {
|
|
|
21
21
|
|
|
22
22
|
const CredentialPaymentButton = ({
|
|
23
23
|
course,
|
|
24
|
-
orderGroup,
|
|
25
24
|
}: Pick<CredentialSaleTunnelProps, 'course' | 'orderGroup'>) => {
|
|
26
25
|
return (
|
|
27
|
-
<
|
|
28
|
-
buildOrderPayload={(payload) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
...(orderGroup ? { order_group_id: orderGroup.id } : {}),
|
|
33
|
-
};
|
|
34
|
-
}}
|
|
26
|
+
<SubscriptionButton
|
|
27
|
+
buildOrderPayload={(payload) => ({
|
|
28
|
+
...payload,
|
|
29
|
+
course_code: course!.code,
|
|
30
|
+
})}
|
|
35
31
|
/>
|
|
36
32
|
);
|
|
37
33
|
};
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { Modal, ModalSize } from '@openfun/cunningham-react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
useCallback,
|
|
10
|
+
} from 'react';
|
|
3
11
|
import { SaleTunnelSponsors } from 'components/SaleTunnel/Sponsors/SaleTunnelSponsors';
|
|
4
12
|
import { SaleTunnelProps } from 'components/SaleTunnel/index';
|
|
5
|
-
import { Address, CreditCard, Order, Product } from 'types/Joanie';
|
|
13
|
+
import { Address, CreditCard, Order, OrderState, Product } from 'types/Joanie';
|
|
6
14
|
import useProductOrder from 'hooks/useProductOrder';
|
|
7
15
|
import { SaleTunnelSuccess } from 'components/SaleTunnel/SaleTunnelSuccess';
|
|
8
16
|
import WebAnalyticsAPIHandler from 'api/web-analytics';
|
|
@@ -10,7 +18,8 @@ import { CourseProductEvent } from 'types/web-analytics';
|
|
|
10
18
|
import { useOmniscientOrders, useOrders } from 'hooks/useOrders';
|
|
11
19
|
import { SaleTunnelInformation } from 'components/SaleTunnel/SaleTunnelInformation';
|
|
12
20
|
import { useEnrollments } from 'hooks/useEnrollments';
|
|
13
|
-
import
|
|
21
|
+
import SaleTunnelSavePaymentMethod from 'components/SaleTunnel/SaleTunnelSavePaymentMethod';
|
|
22
|
+
import { LearnerContractFrame } from 'components/ContractFrame';
|
|
14
23
|
|
|
15
24
|
export interface SaleTunnelContextType {
|
|
16
25
|
props: SaleTunnelProps;
|
|
@@ -19,7 +28,7 @@ export interface SaleTunnelContextType {
|
|
|
19
28
|
webAnalyticsEventKey: string;
|
|
20
29
|
|
|
21
30
|
// internal
|
|
22
|
-
onPaymentSuccess: (
|
|
31
|
+
onPaymentSuccess: () => void;
|
|
23
32
|
step: SaleTunnelStep;
|
|
24
33
|
|
|
25
34
|
// meta
|
|
@@ -30,6 +39,7 @@ export interface SaleTunnelContextType {
|
|
|
30
39
|
registerSubmitCallback: (key: string, callback: () => Promise<void>) => void;
|
|
31
40
|
unregisterSubmitCallback: (key: string) => void;
|
|
32
41
|
runSubmitCallbacks: () => Promise<void>;
|
|
42
|
+
nextStep: () => void;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
export const SaleTunnelContext = createContext<SaleTunnelContextType>({} as any);
|
|
@@ -45,9 +55,10 @@ export const useSaleTunnelContext = () => {
|
|
|
45
55
|
};
|
|
46
56
|
|
|
47
57
|
export enum SaleTunnelStep {
|
|
48
|
-
|
|
58
|
+
IDLE,
|
|
59
|
+
SIGN,
|
|
60
|
+
SAVE_PAYMENT,
|
|
49
61
|
SUCCESS,
|
|
50
|
-
NOT_VALIDATED,
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
interface GenericSaleTunnelProps extends SaleTunnelProps {
|
|
@@ -76,11 +87,35 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
76
87
|
} = useEnrollments(undefined, { enabled: false });
|
|
77
88
|
const [billingAddress, setBillingAddress] = useState<Address>();
|
|
78
89
|
const [creditCard, setCreditCard] = useState<CreditCard>();
|
|
79
|
-
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.
|
|
90
|
+
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.IDLE);
|
|
80
91
|
const [submitCallbacks, setSubmitCallbacks] = useState<Map<string, () => Promise<void>>>(
|
|
81
92
|
new Map(),
|
|
82
93
|
);
|
|
83
94
|
|
|
95
|
+
const nextStep = useCallback(() => {
|
|
96
|
+
if (order)
|
|
97
|
+
switch (step) {
|
|
98
|
+
case SaleTunnelStep.IDLE:
|
|
99
|
+
if ([OrderState.TO_SIGN, OrderState.SIGNING].includes(order.state)) {
|
|
100
|
+
setStep(SaleTunnelStep.SIGN);
|
|
101
|
+
} else if (order.state === OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
102
|
+
setStep(SaleTunnelStep.SAVE_PAYMENT);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case SaleTunnelStep.SIGN:
|
|
106
|
+
if (order.state === OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
107
|
+
setStep(SaleTunnelStep.SAVE_PAYMENT);
|
|
108
|
+
}
|
|
109
|
+
if (order.state === OrderState.COMPLETED) {
|
|
110
|
+
setStep(SaleTunnelStep.SUCCESS);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case SaleTunnelStep.SAVE_PAYMENT:
|
|
114
|
+
setStep(SaleTunnelStep.SUCCESS);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}, [order, step]);
|
|
118
|
+
|
|
84
119
|
const context: SaleTunnelContextType = useMemo(
|
|
85
120
|
() => ({
|
|
86
121
|
webAnalyticsEventKey: props.eventKey,
|
|
@@ -91,12 +126,9 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
91
126
|
setBillingAddress,
|
|
92
127
|
creditCard,
|
|
93
128
|
setCreditCard,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} else {
|
|
98
|
-
setStep(SaleTunnelStep.NOT_VALIDATED);
|
|
99
|
-
}
|
|
129
|
+
nextStep,
|
|
130
|
+
onPaymentSuccess: () => {
|
|
131
|
+
nextStep();
|
|
100
132
|
WebAnalyticsAPIHandler()?.sendCourseProductEvent(
|
|
101
133
|
CourseProductEvent.PAYMENT_SUCCEED,
|
|
102
134
|
props.eventKey,
|
|
@@ -144,13 +176,16 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
144
176
|
|
|
145
177
|
export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
|
|
146
178
|
const { step } = useSaleTunnelContext();
|
|
179
|
+
|
|
147
180
|
switch (step) {
|
|
148
|
-
case SaleTunnelStep.
|
|
149
|
-
return <
|
|
181
|
+
case SaleTunnelStep.IDLE:
|
|
182
|
+
return <GenericSaleTunnelInitialStep {...props} />;
|
|
183
|
+
case SaleTunnelStep.SIGN:
|
|
184
|
+
return <GenericSaleTunnelSignStep {...props} />;
|
|
185
|
+
case SaleTunnelStep.SAVE_PAYMENT:
|
|
186
|
+
return <GenericSaleTunnelSavePaymentMethodStep {...props} />;
|
|
150
187
|
case SaleTunnelStep.SUCCESS:
|
|
151
188
|
return <GenericSaleTunnelSuccessStep {...props} />;
|
|
152
|
-
case SaleTunnelStep.NOT_VALIDATED:
|
|
153
|
-
return <GenericSaleTunnelNotValidatedStep {...props} />;
|
|
154
189
|
}
|
|
155
190
|
throw new Error('Invalid step: ' + step);
|
|
156
191
|
};
|
|
@@ -159,7 +194,7 @@ export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
|
|
|
159
194
|
* Steps.
|
|
160
195
|
*/
|
|
161
196
|
|
|
162
|
-
export const
|
|
197
|
+
export const GenericSaleTunnelInitialStep = (props: GenericSaleTunnelProps) => {
|
|
163
198
|
const { webAnalyticsEventKey } = useSaleTunnelContext();
|
|
164
199
|
|
|
165
200
|
useEffect(() => {
|
|
@@ -174,19 +209,21 @@ export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
|
|
|
174
209
|
}, []);
|
|
175
210
|
|
|
176
211
|
return (
|
|
177
|
-
<Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title}
|
|
212
|
+
<Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title}>
|
|
178
213
|
<div className="sale-tunnel" data-testid="generic-sale-tunnel-payment-step">
|
|
179
214
|
<div className="sale-tunnel__main">
|
|
180
|
-
<div className="sale-tunnel__main__left">
|
|
215
|
+
<div className="sale-tunnel__main__column sale-tunnel__main__left ">
|
|
216
|
+
<div>{props.asideNode}</div>
|
|
217
|
+
<div>
|
|
218
|
+
<SaleTunnelSponsors />
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
181
221
|
<div className="sale-tunnel__main__separator" />
|
|
182
222
|
<div className="sale-tunnel__main__right">
|
|
183
223
|
<SaleTunnelInformation />
|
|
184
224
|
</div>
|
|
185
225
|
</div>
|
|
186
|
-
<div className="sale-tunnel__footer">
|
|
187
|
-
{props.paymentNode}
|
|
188
|
-
<SaleTunnelSponsors />
|
|
189
|
-
</div>
|
|
226
|
+
<div className="sale-tunnel__footer">{props.paymentNode}</div>
|
|
190
227
|
</div>
|
|
191
228
|
</Modal>
|
|
192
229
|
);
|
|
@@ -200,10 +237,26 @@ export const GenericSaleTunnelSuccessStep = (props: SaleTunnelProps) => {
|
|
|
200
237
|
);
|
|
201
238
|
};
|
|
202
239
|
|
|
203
|
-
export const
|
|
240
|
+
export const GenericSaleTunnelSavePaymentMethodStep = (props: SaleTunnelProps) => {
|
|
204
241
|
return (
|
|
205
|
-
<Modal {...props} size={ModalSize.
|
|
206
|
-
<
|
|
242
|
+
<Modal {...props} size={ModalSize.LARGE}>
|
|
243
|
+
<SaleTunnelSavePaymentMethod />
|
|
207
244
|
</Modal>
|
|
208
245
|
);
|
|
209
246
|
};
|
|
247
|
+
|
|
248
|
+
export const GenericSaleTunnelSignStep = ({ isOpen, onClose }: SaleTunnelProps) => {
|
|
249
|
+
const { order, nextStep } = useSaleTunnelContext();
|
|
250
|
+
|
|
251
|
+
const handleDone = useCallback(nextStep, [order]);
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (![OrderState.TO_SIGN, OrderState.SIGNING].includes(order!.state)) {
|
|
255
|
+
nextStep();
|
|
256
|
+
}
|
|
257
|
+
}, [order]);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<LearnerContractFrame order={order!} isOpen={isOpen} onClose={onClose} onDone={handleDone} />
|
|
261
|
+
);
|
|
262
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Alert, VariantType } from '@openfun/cunningham-react';
|
|
2
1
|
import { defineMessages, FormattedMessage, FormattedNumber } from 'react-intl';
|
|
3
2
|
import { AddressSelector } from 'components/SaleTunnel/AddressSelector';
|
|
4
|
-
import { CreditCardSelector } from 'components/SaleTunnel/CreditCardSelector';
|
|
5
3
|
import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
|
|
6
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
7
5
|
import OpenEdxFullNameForm from 'components/OpenEdxFullNameForm';
|
|
8
6
|
import { useSession } from 'contexts/SessionContext';
|
|
9
7
|
import useOpenEdxProfile from 'hooks/useOpenEdxProfile';
|
|
8
|
+
import { usePaymentSchedule } from 'hooks/usePaymentSchedule';
|
|
9
|
+
import { Spinner } from 'components/Spinner';
|
|
10
10
|
|
|
11
11
|
const messages = defineMessages({
|
|
12
12
|
title: {
|
|
@@ -49,7 +49,7 @@ const messages = defineMessages({
|
|
|
49
49
|
|
|
50
50
|
export const SaleTunnelInformation = () => {
|
|
51
51
|
return (
|
|
52
|
-
<div className="sale-tunnel__information">
|
|
52
|
+
<div className="sale-tunnel__main__column sale-tunnel__information">
|
|
53
53
|
<div>
|
|
54
54
|
<h3 className="block-title mb-t">
|
|
55
55
|
<FormattedMessage {...messages.title} />
|
|
@@ -64,15 +64,12 @@ export const SaleTunnelInformation = () => {
|
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|
|
66
66
|
<div>
|
|
67
|
-
<
|
|
68
|
-
</div>
|
|
69
|
-
<div>
|
|
67
|
+
<PaymentScheduleBlock />
|
|
70
68
|
<Total />
|
|
71
69
|
</div>
|
|
72
70
|
</div>
|
|
73
71
|
);
|
|
74
72
|
};
|
|
75
|
-
|
|
76
73
|
const Email = () => {
|
|
77
74
|
const { user } = useSession();
|
|
78
75
|
const { data: openEdxProfileData } = useOpenEdxProfile({
|
|
@@ -98,9 +95,6 @@ const Total = () => {
|
|
|
98
95
|
const { product } = useSaleTunnelContext();
|
|
99
96
|
return (
|
|
100
97
|
<div className="sale-tunnel__total">
|
|
101
|
-
<Alert type={VariantType.INFO}>
|
|
102
|
-
<FormattedMessage {...messages.totalInfo} />
|
|
103
|
-
</Alert>
|
|
104
98
|
<div className="sale-tunnel__total__amount mt-t" data-testid="sale-tunnel__total__amount">
|
|
105
99
|
<div className="block-title">
|
|
106
100
|
<FormattedMessage {...messages.totalLabel} />
|
|
@@ -117,20 +111,22 @@ const Total = () => {
|
|
|
117
111
|
);
|
|
118
112
|
};
|
|
119
113
|
|
|
120
|
-
/**
|
|
121
|
-
* Ready for V2.
|
|
122
|
-
*/
|
|
123
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
124
114
|
const PaymentScheduleBlock = () => {
|
|
125
|
-
|
|
115
|
+
const { props } = useSaleTunnelContext();
|
|
116
|
+
const query = usePaymentSchedule({
|
|
117
|
+
course_code: props.course?.code || props.enrollment!.course_run.course.code,
|
|
118
|
+
product_id: props.product.id,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!query.data || query.isLoading) {
|
|
122
|
+
return <Spinner size="large" />;
|
|
123
|
+
}
|
|
124
|
+
|
|
126
125
|
return (
|
|
127
126
|
<div className="payment-schedule">
|
|
128
|
-
<h4 className="block-title mb-t">
|
|
129
|
-
<Alert type={VariantType.INFO}>
|
|
130
|
-
The first payment occurs in 14 days, you will be notified to pay the first 30%.
|
|
131
|
-
</Alert>
|
|
127
|
+
<h4 className="block-title mb-t">Payment schedule</h4>
|
|
132
128
|
<div className="mt-t">
|
|
133
|
-
<PaymentScheduleGrid />
|
|
129
|
+
<PaymentScheduleGrid schedule={query.data} />
|
|
134
130
|
</div>
|
|
135
131
|
</div>
|
|
136
132
|
);
|