richie-education 2.28.2-dev39 → 2.28.2-dev58

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.
Files changed (102) hide show
  1. package/.eslintrc.json +11 -2
  2. package/i18n/locales/ar-SA.json +209 -125
  3. package/i18n/locales/es-ES.json +210 -126
  4. package/i18n/locales/fa-IR.json +209 -125
  5. package/i18n/locales/fr-CA.json +209 -125
  6. package/i18n/locales/fr-FR.json +209 -125
  7. package/i18n/locales/ko-KR.json +209 -125
  8. package/i18n/locales/pt-PT.json +212 -128
  9. package/i18n/locales/ru-RU.json +209 -125
  10. package/i18n/locales/vi-VN.json +209 -125
  11. package/js/api/joanie.ts +14 -17
  12. package/js/api/lms/dummy.ts +1 -12
  13. package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
  14. package/js/components/ContractFrame/AbstractContractFrame.tsx +32 -25
  15. package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
  16. package/js/components/ContractFrame/_styles.scss +6 -14
  17. package/js/components/CreditCardSelector/index.spec.tsx +7 -7
  18. package/js/components/CreditCardSelector/index.tsx +2 -2
  19. package/js/components/DownloadContractButton/index.spec.tsx +1 -1
  20. package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
  21. package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
  22. package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
  23. package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
  24. package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
  25. package/js/components/PaymentInterfaces/types.ts +5 -2
  26. package/js/components/PurchaseButton/index.spec.tsx +69 -37
  27. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
  28. package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
  29. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
  30. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +75 -41
  31. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +0 -30
  32. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
  33. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
  34. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
  35. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +5 -0
  36. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
  37. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +202 -0
  38. package/js/components/SaleTunnel/_styles.scss +10 -1
  39. package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
  40. package/js/components/SaleTunnel/index.credential.spec.tsx +12 -21
  41. package/js/components/SaleTunnel/index.full-process.spec.tsx +110 -48
  42. package/js/components/SaleTunnel/index.spec.tsx +330 -779
  43. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
  44. package/js/components/SignContractButton/index.spec.tsx +16 -20
  45. package/js/components/SignContractButton/index.tsx +3 -1
  46. package/js/hooks/useCreditCards/index.spec.tsx +70 -6
  47. package/js/hooks/useCreditCards/index.ts +49 -11
  48. package/js/hooks/useOrders/index.spec.tsx +322 -0
  49. package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
  50. package/js/hooks/useProductOrder/index.spec.tsx +77 -60
  51. package/js/hooks/useProductOrder/index.tsx +2 -2
  52. package/js/hooks/useResources/useResourcesRoot.ts +1 -0
  53. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
  54. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
  55. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
  56. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
  57. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
  58. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
  59. package/js/settings/settings.test.ts +11 -2
  60. package/js/translations/ar-SA.json +1 -1
  61. package/js/translations/es-ES.json +1 -1
  62. package/js/translations/fa-IR.json +1 -1
  63. package/js/translations/fr-CA.json +1 -1
  64. package/js/translations/fr-FR.json +1 -1
  65. package/js/translations/ko-KR.json +1 -1
  66. package/js/translations/pt-PT.json +1 -1
  67. package/js/translations/ru-RU.json +1 -1
  68. package/js/translations/vi-VN.json +1 -1
  69. package/js/types/Joanie.ts +49 -34
  70. package/js/utils/OrderHelper/index.ts +38 -42
  71. package/js/utils/search/getSuggestionsSection/index.spec.ts +3 -2
  72. package/js/utils/test/factories/joanie.ts +36 -51
  73. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
  74. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
  75. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
  76. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +7 -6
  77. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +9 -10
  78. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
  79. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
  80. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +28 -8
  81. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +4 -6
  82. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
  83. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +34 -35
  84. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +27 -24
  85. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
  86. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
  87. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
  88. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
  89. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
  90. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
  91. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
  92. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
  93. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
  94. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
  95. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
  96. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
  97. package/package.json +27 -27
  98. package/scss/components/_index.scss +2 -1
  99. package/js/components/PaymentButton/_styles.scss +0 -27
  100. package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -333
  101. package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
  102. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
@@ -9,10 +9,7 @@ const PaymentInterface = ({ onError, onSuccess }: PaymentInterfaceProps) => (
9
9
  >
10
10
  Simulate payment failure
11
11
  </button>
12
- <button
13
- data-testid="payment-abort"
14
- onClick={() => onError(PaymentErrorMessageId.ERROR_ABORTING)}
15
- >
12
+ <button data-testid="payment-abort" onClick={() => onError(PaymentErrorMessageId.ERROR_ABORT)}>
16
13
  Simulate payment abort
17
14
  </button>
18
15
  <button data-testid="payment-success" onClick={onSuccess}>
@@ -1,10 +1,13 @@
1
1
  export enum PaymentErrorMessageId {
2
2
  ERROR_ABORT = 'errorAbort',
3
- ERROR_ABORTING = 'errorAborting',
3
+ ERROR_DEFAULT = 'errorDefault',
4
+ }
5
+
6
+ export enum SubscriptionErrorMessageId {
7
+ ERROR_ABORT = 'errorAbort',
4
8
  ERROR_ADDRESS = 'errorAddress',
5
9
  ERROR_DEFAULT = 'errorDefault',
6
10
  ERROR_FULL_PRODUCT = 'errorFullProduct',
7
- ERROR_TERMS = 'errorTerms',
8
11
  }
9
12
 
10
13
  export enum PaymentProviders {
@@ -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,11 +103,14 @@ 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();
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)}`;
105
112
  fetchMock
106
- .get(
107
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
108
- {},
109
- )
113
+ .get(url, {})
110
114
  .get(
111
115
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
112
116
  [],
@@ -142,11 +146,14 @@ describe('PurchaseButton', () => {
142
146
  const product = ProductFactory({ remaining_order_count: null }).one();
143
147
  fetchMock.get(`https://demo.endpoint/api/user/v1/accounts/${user.username}`, {});
144
148
  fetchMock.get(`https://demo.endpoint/api/user/v1/preferences/${user.username}`, {});
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)}`;
145
155
  fetchMock
146
- .get(
147
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
148
- {},
149
- )
156
+ .get(url, {})
150
157
  .get(
151
158
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
152
159
  [],
@@ -180,11 +187,14 @@ describe('PurchaseButton', () => {
180
187
  it('shows cta to open sale tunnel when remaining orders is undefined', async () => {
181
188
  const courseCode = '00000';
182
189
  const product = ProductFactory().one();
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)}`;
183
196
  fetchMock
184
- .get(
185
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
186
- {},
187
- )
197
+ .get(url, {})
188
198
  .get(
189
199
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${product.id}/payment-schedule/`,
190
200
  [],
@@ -220,10 +230,13 @@ describe('PurchaseButton', () => {
220
230
  it('renders a disabled CTA if the product have no remaining orders', async () => {
221
231
  const courseCode = '00000';
222
232
  const product = ProductFactory({ remaining_order_count: 0 }).one();
223
- fetchMock.get(
224
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
225
- {},
226
- );
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, {});
227
240
  render(
228
241
  <Wrapper client={createTestQueryClient({ user: richieUser })}>
229
242
  <PurchaseButton
@@ -256,10 +269,14 @@ describe('PurchaseButton', () => {
256
269
  const courseCode = '00000';
257
270
  const product = ProductFactory({ ...productData, type: ProductType.CREDENTIAL }).one();
258
271
  product.target_courses[0].course_runs = [];
259
- fetchMock.get(
260
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
261
- {},
262
- );
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, {});
263
280
 
264
281
  render(
265
282
  <Wrapper client={createTestQueryClient({ user: richieUser })}>
@@ -305,10 +322,14 @@ describe('PurchaseButton', () => {
305
322
  const product = CertificateProductFactory().one();
306
323
  const enrollment = EnrollmentFactory().one();
307
324
  enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
308
- fetchMock.get(
309
- `https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollment.id}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
310
- {},
311
- );
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, {});
312
333
 
313
334
  render(
314
335
  <Wrapper client={createTestQueryClient({ user: true })}>
@@ -359,10 +380,13 @@ describe('PurchaseButton', () => {
359
380
  const enrollment = EnrollmentFactory().one();
360
381
  enrollment.course_run.state = CourseStateFactory(courseRunStateData).one();
361
382
 
362
- fetchMock.get(
363
- `https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollment.id}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
364
- {},
365
- );
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, {});
366
390
 
367
391
  render(
368
392
  <Wrapper client={createTestQueryClient({ user: true })}>
@@ -388,10 +412,14 @@ describe('PurchaseButton', () => {
388
412
  it('renders a disabled CTA if product has no target courses', async () => {
389
413
  const courseCode = '00000';
390
414
  const product = ProductFactory().one();
391
- fetchMock.get(
392
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
393
- {},
394
- );
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, {});
395
423
  product.target_courses = [];
396
424
 
397
425
  render(
@@ -419,10 +447,14 @@ describe('PurchaseButton', () => {
419
447
  it('does not render CTA if disabled property is false', async () => {
420
448
  const courseCode = '00000';
421
449
  const product = ProductFactory().one();
422
- fetchMock.get(
423
- `https://joanie.endpoint/api/v1.0/orders/?course_code=${courseCode}&product_id=${product.id}&state=pending&state=validated&state=submitted`,
424
- {},
425
- );
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, {});
426
458
 
427
459
  render(
428
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.PAYMENT,
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
- <GenericPaymentButton
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
- <GenericPaymentButton
28
- buildOrderPayload={(payload) => {
29
- return {
30
- ...payload,
31
- course_code: course!.code,
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,9 +1,16 @@
1
- import { Alert, Modal, ModalSize, VariantType } from '@openfun/cunningham-react';
2
- import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
3
- import { FormattedMessage, defineMessages } from 'react-intl';
1
+ import { Modal, ModalSize } from '@openfun/cunningham-react';
2
+ import {
3
+ createContext,
4
+ ReactNode,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ useCallback,
10
+ } from 'react';
4
11
  import { SaleTunnelSponsors } from 'components/SaleTunnel/Sponsors/SaleTunnelSponsors';
5
12
  import { SaleTunnelProps } from 'components/SaleTunnel/index';
6
- import { Address, CreditCard, Order, Product } from 'types/Joanie';
13
+ import { Address, CreditCard, Order, OrderState, Product } from 'types/Joanie';
7
14
  import useProductOrder from 'hooks/useProductOrder';
8
15
  import { SaleTunnelSuccess } from 'components/SaleTunnel/SaleTunnelSuccess';
9
16
  import WebAnalyticsAPIHandler from 'api/web-analytics';
@@ -11,16 +18,8 @@ import { CourseProductEvent } from 'types/web-analytics';
11
18
  import { useOmniscientOrders, useOrders } from 'hooks/useOrders';
12
19
  import { SaleTunnelInformation } from 'components/SaleTunnel/SaleTunnelInformation';
13
20
  import { useEnrollments } from 'hooks/useEnrollments';
14
- import SaleTunnelNotValidated from './SaleTunnelNotValidated';
15
-
16
- const messages = defineMessages({
17
- walkthrough: {
18
- id: 'components.SaleTunnel.GenericSaleTunnel.walkthrough',
19
- defaultMessage:
20
- 'To enroll in the training, you will first be invited to sign the training agreement and then to define a payment method. You will not be charged during this step; the first payment will take place on the date mentioned in the payment schedule above.',
21
- description: 'Message explaining the credential sale tunnel process',
22
- },
23
- });
21
+ import SaleTunnelSavePaymentMethod from 'components/SaleTunnel/SaleTunnelSavePaymentMethod';
22
+ import { LearnerContractFrame } from 'components/ContractFrame';
24
23
 
25
24
  export interface SaleTunnelContextType {
26
25
  props: SaleTunnelProps;
@@ -29,7 +28,7 @@ export interface SaleTunnelContextType {
29
28
  webAnalyticsEventKey: string;
30
29
 
31
30
  // internal
32
- onPaymentSuccess: (validated?: boolean) => void;
31
+ onPaymentSuccess: () => void;
33
32
  step: SaleTunnelStep;
34
33
 
35
34
  // meta
@@ -40,6 +39,7 @@ export interface SaleTunnelContextType {
40
39
  registerSubmitCallback: (key: string, callback: () => Promise<void>) => void;
41
40
  unregisterSubmitCallback: (key: string) => void;
42
41
  runSubmitCallbacks: () => Promise<void>;
42
+ nextStep: () => void;
43
43
  }
44
44
 
45
45
  export const SaleTunnelContext = createContext<SaleTunnelContextType>({} as any);
@@ -55,9 +55,10 @@ export const useSaleTunnelContext = () => {
55
55
  };
56
56
 
57
57
  export enum SaleTunnelStep {
58
- PAYMENT,
58
+ IDLE,
59
+ SIGN,
60
+ SAVE_PAYMENT,
59
61
  SUCCESS,
60
- NOT_VALIDATED,
61
62
  }
62
63
 
63
64
  interface GenericSaleTunnelProps extends SaleTunnelProps {
@@ -86,11 +87,35 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
86
87
  } = useEnrollments(undefined, { enabled: false });
87
88
  const [billingAddress, setBillingAddress] = useState<Address>();
88
89
  const [creditCard, setCreditCard] = useState<CreditCard>();
89
- const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.PAYMENT);
90
+ const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.IDLE);
90
91
  const [submitCallbacks, setSubmitCallbacks] = useState<Map<string, () => Promise<void>>>(
91
92
  new Map(),
92
93
  );
93
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
+
94
119
  const context: SaleTunnelContextType = useMemo(
95
120
  () => ({
96
121
  webAnalyticsEventKey: props.eventKey,
@@ -101,12 +126,9 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
101
126
  setBillingAddress,
102
127
  creditCard,
103
128
  setCreditCard,
104
- onPaymentSuccess: (validated: boolean = true) => {
105
- if (validated) {
106
- setStep(SaleTunnelStep.SUCCESS);
107
- } else {
108
- setStep(SaleTunnelStep.NOT_VALIDATED);
109
- }
129
+ nextStep,
130
+ onPaymentSuccess: () => {
131
+ nextStep();
110
132
  WebAnalyticsAPIHandler()?.sendCourseProductEvent(
111
133
  CourseProductEvent.PAYMENT_SUCCEED,
112
134
  props.eventKey,
@@ -154,13 +176,16 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
154
176
 
155
177
  export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
156
178
  const { step } = useSaleTunnelContext();
179
+
157
180
  switch (step) {
158
- case SaleTunnelStep.PAYMENT:
159
- return <GenericSaleTunnelPaymentStep {...props} />;
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} />;
160
187
  case SaleTunnelStep.SUCCESS:
161
188
  return <GenericSaleTunnelSuccessStep {...props} />;
162
- case SaleTunnelStep.NOT_VALIDATED:
163
- return <GenericSaleTunnelNotValidatedStep {...props} />;
164
189
  }
165
190
  throw new Error('Invalid step: ' + step);
166
191
  };
@@ -169,7 +194,7 @@ export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
169
194
  * Steps.
170
195
  */
171
196
 
172
- export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
197
+ export const GenericSaleTunnelInitialStep = (props: GenericSaleTunnelProps) => {
173
198
  const { webAnalyticsEventKey } = useSaleTunnelContext();
174
199
 
175
200
  useEffect(() => {
@@ -184,7 +209,7 @@ export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
184
209
  }, []);
185
210
 
186
211
  return (
187
- <Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title} closeOnEsc={false}>
212
+ <Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title}>
188
213
  <div className="sale-tunnel" data-testid="generic-sale-tunnel-payment-step">
189
214
  <div className="sale-tunnel__main">
190
215
  <div className="sale-tunnel__main__column sale-tunnel__main__left ">
@@ -198,14 +223,7 @@ export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
198
223
  <SaleTunnelInformation />
199
224
  </div>
200
225
  </div>
201
- <div className="sale-tunnel__footer">
202
- <div style={{ maxWidth: '680px' }} className="mb-s" data-testid="walkthrough-banner">
203
- <Alert type={VariantType.INFO}>
204
- <FormattedMessage {...messages.walkthrough} />
205
- </Alert>
206
- </div>
207
- {props.paymentNode}
208
- </div>
226
+ <div className="sale-tunnel__footer">{props.paymentNode}</div>
209
227
  </div>
210
228
  </Modal>
211
229
  );
@@ -219,10 +237,26 @@ export const GenericSaleTunnelSuccessStep = (props: SaleTunnelProps) => {
219
237
  );
220
238
  };
221
239
 
222
- export const GenericSaleTunnelNotValidatedStep = (props: SaleTunnelProps) => {
240
+ export const GenericSaleTunnelSavePaymentMethodStep = (props: SaleTunnelProps) => {
223
241
  return (
224
- <Modal {...props} size={ModalSize.MEDIUM}>
225
- <SaleTunnelNotValidated closeModal={props.onClose} />
242
+ <Modal {...props} size={ModalSize.LARGE}>
243
+ <SaleTunnelSavePaymentMethod />
226
244
  </Modal>
227
245
  );
228
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,6 +1,5 @@
1
1
  import { defineMessages, FormattedMessage, FormattedNumber } from 'react-intl';
2
2
  import { AddressSelector } from 'components/SaleTunnel/AddressSelector';
3
- import { CreditCardSelector } from 'components/CreditCardSelector';
4
3
  import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
5
4
  import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
6
5
  import OpenEdxFullNameForm from 'components/OpenEdxFullNameForm';
@@ -25,16 +24,6 @@ const messages = defineMessages({
25
24
  description: 'Label for the full name input',
26
25
  defaultMessage: 'Full name',
27
26
  },
28
- paymentMethodTitle: {
29
- id: 'components.SaleTunnel.CreditCardSelector.title',
30
- description: 'Title for the credit card section',
31
- defaultMessage: 'Payment method',
32
- },
33
- paymentMethodDescription: {
34
- id: 'components.SaleTunnel.CreditCardSelector.description',
35
- description: 'Description for the credit card section',
36
- defaultMessage: 'Choose your payment method or add a new one during the payment.',
37
- },
38
27
  totalInfo: {
39
28
  id: 'components.SaleTunnel.Information.total.info',
40
29
  description: 'Information about the total amount',
@@ -74,9 +63,6 @@ export const SaleTunnelInformation = () => {
74
63
  <Email />
75
64
  </div>
76
65
  </div>
77
- <div>
78
- <CreditCardSelectorWrapper />
79
- </div>
80
66
  <div>
81
67
  <PaymentScheduleBlock />
82
68
  <Total />
@@ -84,22 +70,6 @@ export const SaleTunnelInformation = () => {
84
70
  </div>
85
71
  );
86
72
  };
87
-
88
- const CreditCardSelectorWrapper = () => {
89
- const { creditCard, setCreditCard } = useSaleTunnelContext();
90
- return (
91
- <>
92
- <h4 className="block-title mb-t">
93
- <FormattedMessage {...messages.paymentMethodTitle} />
94
- </h4>
95
- <div className="description mb-s">
96
- <FormattedMessage {...messages.paymentMethodDescription} />
97
- </div>
98
- <CreditCardSelector creditCard={creditCard} setCreditCard={setCreditCard} />
99
- </>
100
- );
101
- };
102
-
103
73
  const Email = () => {
104
74
  const { user } = useSession();
105
75
  const { data: openEdxProfileData } = useOpenEdxProfile({
@@ -0,0 +1,12 @@
1
+ .sale-tunnel-step--save-payment-method {
2
+ .credit-card-selector {
3
+ display: inline-block;
4
+ }
5
+
6
+ .sale-tunnel-step__footer {
7
+ align-items: center;
8
+ justify-content: center;
9
+ display: flex;
10
+ flex-direction: column;
11
+ }
12
+ }