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
@@ -1,8 +1,9 @@
1
- import { screen, within } from '@testing-library/react';
1
+ import { act, screen, within } from '@testing-library/react';
2
2
  import fetchMock from 'fetch-mock';
3
3
  import queryString from 'query-string';
4
4
  import userEvent from '@testing-library/user-event';
5
5
  import countries from 'i18n-iso-countries';
6
+ import { getAllByRole } from '@testing-library/dom';
6
7
  import {
7
8
  RichieContextFactory as mockRichieContextFactory,
8
9
  PacedCourseFactory,
@@ -13,11 +14,14 @@ import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
13
14
  import CourseProductItem from 'widgets/SyllabusCourseRunsList/components/CourseProductItem';
14
15
  import {
15
16
  AddressFactory,
16
- CredentialOrderWithPaymentFactory,
17
- PaymentInstallmentFactory,
17
+ ContractFactory,
18
18
  CourseProductRelationFactory,
19
+ CredentialOrderFactory,
20
+ CreditCardFactory,
21
+ PaymentFactory,
22
+ PaymentInstallmentFactory,
19
23
  } from 'utils/test/factories/joanie';
20
- import { ACTIVE_ORDER_STATES, CourseRun } from 'types/Joanie';
24
+ import { CourseRun, NOT_CANCELED_ORDER_STATES, OrderState } from 'types/Joanie';
21
25
  import { Priority } from 'types';
22
26
  import { expectMenuToBeClosed, expectMenuToBeOpen } from 'utils/test/Cunningham';
23
27
  import { changeSelect } from 'components/Form/test-utils';
@@ -26,6 +30,7 @@ import { OpenEdxApiProfileFactory } from 'utils/test/factories/openEdx';
26
30
  import { User } from 'types/User';
27
31
  import { OpenEdxApiProfile } from 'types/openEdx';
28
32
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
33
+ import { Deferred } from 'utils/test/deferred';
29
34
 
30
35
  jest.mock('utils/context', () => ({
31
36
  __esModule: true,
@@ -54,6 +59,12 @@ describe('SaleTunnel', () => {
54
59
  let openApiEdxProfile: OpenEdxApiProfile;
55
60
  setupJoanieSession();
56
61
 
62
+ const dateFormatter = Intl.DateTimeFormat('en', {
63
+ day: '2-digit',
64
+ month: 'short',
65
+ year: 'numeric',
66
+ });
67
+
57
68
  const priceFormatter = (currency: string, price: number) =>
58
69
  new Intl.NumberFormat('en', {
59
70
  currency,
@@ -81,13 +92,14 @@ describe('SaleTunnel', () => {
81
92
  fetchMock.get(`https://auth.test/api/v1.0/user/me`, richieUser);
82
93
  });
83
94
 
84
- it('tests the entire process of buying a credential product', async () => {
95
+ it('tests the entire process of subscribing to a credential product', async () => {
85
96
  /**
86
97
  * Initialization.
87
98
  */
88
- const relation = CourseProductRelationFactory().one();
99
+ const course = PacedCourseFactory().one();
100
+ const relation = CourseProductRelationFactory({ course }).one();
89
101
  const paymentSchedule = PaymentInstallmentFactory().many(2);
90
- const { product, course } = relation;
102
+ const { product } = relation;
91
103
 
92
104
  fetchMock.get(
93
105
  `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
@@ -101,22 +113,16 @@ describe('SaleTunnel', () => {
101
113
  const orderQueryParameters = {
102
114
  product_id: product.id,
103
115
  course_code: course.code,
104
- state: ACTIVE_ORDER_STATES,
116
+ state: NOT_CANCELED_ORDER_STATES,
105
117
  };
106
118
  fetchMock.get(
107
119
  `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
108
120
  [],
109
121
  );
110
122
 
111
- render(
112
- <CourseProductItem
113
- productId={product.id}
114
- course={PacedCourseFactory({ id: course.id, code: course.code }).one()}
115
- />,
116
- {
117
- queryOptions: { client: createTestQueryClient({ user: richieUser }) },
118
- },
119
- );
123
+ render(<CourseProductItem productId={product.id} course={course} />, {
124
+ queryOptions: { client: createTestQueryClient({ user: richieUser }) },
125
+ });
120
126
 
121
127
  // Wait for product information to be fetched
122
128
  await screen.findByRole('heading', { level: 3, name: product.title });
@@ -216,10 +222,11 @@ describe('SaleTunnel', () => {
216
222
 
217
223
  // - User fulfills address fields
218
224
  const address = AddressFactory({ is_main: true }).one();
219
- fetchMock.post('https://joanie.endpoint/api/v1.0/addresses/', address);
220
- fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', [address], {
221
- overwriteRoutes: true,
222
- });
225
+ fetchMock
226
+ .post('https://joanie.endpoint/api/v1.0/addresses/', address)
227
+ .get('https://joanie.endpoint/api/v1.0/addresses/', [address], {
228
+ overwriteRoutes: true,
229
+ });
223
230
 
224
231
  await user.type($titleField, address.title);
225
232
  await user.type($firstnameField, address.first_name);
@@ -235,16 +242,23 @@ describe('SaleTunnel', () => {
235
242
  ).toBeInTheDocument();
236
243
 
237
244
  /**
238
- * Make sure no credit card is selected.
245
+ * Make sure the payment schedule is displayed.
239
246
  */
240
- screen.getByRole('heading', {
241
- name: 'Payment method',
247
+ screen.getByRole('heading', { name: 'Payment schedule' });
248
+ paymentSchedule.forEach((installment, index) => {
249
+ const row = screen.getByTestId(installment.id);
250
+ const cells = getAllByRole(row, 'cell');
251
+ expect(cells).toHaveLength(4);
252
+ expect(cells[0]).toHaveTextContent((index + 1).toString());
253
+ expect(cells[1]).toHaveTextContent(
254
+ priceFormatter(installment.currency, installment.amount).replace(/(\u202F|\u00a0)/g, ' '),
255
+ );
256
+ expect(cells[2]).toHaveTextContent(
257
+ `Withdrawn on ${dateFormatter.format(new Date(installment.due_date))}`,
258
+ );
259
+ expect(cells[3]).toHaveTextContent(new RegExp(installment.state, 'i'));
242
260
  });
243
- screen.getByText('Use another credit card during payment');
244
261
 
245
- /**
246
- * Make sure the total is the correct one.
247
- */
248
262
  const $totalAmount = screen.getByTestId('sale-tunnel__total__amount');
249
263
  expect($totalAmount).toHaveTextContent(
250
264
  'Total' +
@@ -252,34 +266,84 @@ describe('SaleTunnel', () => {
252
266
  );
253
267
 
254
268
  /**
255
- * Pay
269
+ * Subscribe
256
270
  */
257
- const $terms = screen.getByLabelText(
258
- 'By checking this box, you accept the General Terms of Sale',
259
- );
260
- await user.click($terms);
261
-
262
- const { payment_info: paymentInfo, ...order } = CredentialOrderWithPaymentFactory().one();
271
+ const order = CredentialOrderFactory({ state: OrderState.TO_SIGN }).one();
263
272
  fetchMock
273
+ .post('https://joanie.endpoint/api/v1.0/orders/', order)
264
274
  .get(
265
275
  `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
266
276
  [order],
267
277
  { overwriteRoutes: true },
268
- )
269
- .post('https://joanie.endpoint/api/v1.0/orders/', order)
270
- .patch(`https://joanie.endpoint/api/v1.0/orders/${order.id}/submit/`, {
271
- paymentInfo,
272
- })
273
- .get(`https://joanie.endpoint/api/v1.0/orders/${order.id}/`, {
274
- ...order,
275
- });
278
+ );
276
279
 
277
280
  const $button = screen.getByRole('button', {
278
281
  name: `Subscribe`,
279
282
  }) as HTMLButtonElement;
280
283
  await user.click($button);
281
284
 
282
- await screen.findByText('Payment in progress');
285
+ order.state = OrderState.TO_SAVE_PAYMENT_METHOD;
286
+ order.contract = ContractFactory({ student_signed_on: new Date().toISOString() }).one();
287
+
288
+ const checkSignatureDeferred = new Deferred();
289
+
290
+ fetchMock
291
+ .post(`https://joanie.endpoint/api/v1.0/orders/${order.id}/submit_for_signature/`, {
292
+ invitation_link: 'https://dummysignaturebackend.fr/contract/1/sign',
293
+ })
294
+ .post(`https://joanie.endpoint/api/v1.0/signature/notifications/`, 200)
295
+ .get(`https://joanie.endpoint/api/v1.0/orders/${order.id}/`, checkSignatureDeferred.promise, {
296
+ overwriteRoutes: true,
297
+ })
298
+ .get(
299
+ `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
300
+ [order],
301
+ { overwriteRoutes: true },
302
+ );
303
+
304
+ const $signButton = await screen.findByRole('button', { name: 'Sign' });
305
+ await user.click($signButton);
306
+
307
+ screen.getByRole('heading', { name: 'Signing the contract ...' });
308
+
309
+ // Then the signature check polling should be started
310
+ await screen.findByRole('heading', { name: 'Verifying signature ...' });
311
+ expect(
312
+ screen.getByText(
313
+ 'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
314
+ ),
315
+ ).toBeInTheDocument();
316
+ expect(screen.getByRole('status')).toBeInTheDocument();
317
+
318
+ await act(async () => {
319
+ checkSignatureDeferred.resolve(order);
320
+ });
321
+
322
+ /**
323
+ * Save payment method step
324
+ */
325
+ const paymentMethod = CreditCardFactory().one();
326
+ order.state = OrderState.PENDING;
327
+ order.credit_card_id = paymentMethod.id;
328
+ fetchMock
329
+ .post('https://joanie.endpoint/api/v1.0/credit-cards/tokenize-card/', PaymentFactory().one())
330
+ .post(`https://joanie.endpoint/api/v1.0/orders/${order.id}/payment-method/`, 200)
331
+ .get(
332
+ 'https://joanie.endpoint/api/v1.0/credit-cards/',
333
+ { results: [paymentMethod] },
334
+ { overwriteRoutes: true },
335
+ )
336
+ .get(
337
+ `https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
338
+ [order],
339
+ { overwriteRoutes: true },
340
+ );
341
+ await screen.findByRole('heading', { name: 'Define a payment method' });
342
+ screen.getByText('Use another credit card');
343
+
344
+ const $defineButton = screen.getByRole('button', { name: 'Define' });
345
+ await user.click($defineButton);
346
+
283
347
  screen.getByText('Payment interface component');
284
348
  await user.click(screen.getByTestId('payment-success'));
285
349
 
@@ -288,11 +352,9 @@ describe('SaleTunnel', () => {
288
352
  */
289
353
 
290
354
  // Make sure the success step is shown.
291
- expect(screen.queryByTestId('generic-sale-tunnel-payment-step')).not.toBeInTheDocument();
292
355
  await screen.findByTestId('generic-sale-tunnel-success-step');
293
- screen.getByText('Congratulations!');
294
- screen.getByText(/Your order has been successfully created/);
295
- screen.getByRole('link', { name: 'Sign the training contract' });
356
+ screen.getByText('Subscription confirmed!');
357
+ screen.getByRole('link', { name: 'Close' });
296
358
 
297
359
  /**
298
360
  * Make sure the product is displayed as bought ( it verifies cache is well updated ).