richie-education 2.28.2-dev39 → 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.
Files changed (82) hide show
  1. package/js/api/joanie.ts +12 -16
  2. package/js/api/lms/dummy.ts +1 -12
  3. package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
  4. package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
  5. package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
  6. package/js/components/ContractFrame/_styles.scss +6 -14
  7. package/js/components/CreditCardSelector/index.spec.tsx +7 -7
  8. package/js/components/CreditCardSelector/index.tsx +2 -2
  9. package/js/components/DownloadContractButton/index.spec.tsx +1 -1
  10. package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
  11. package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
  12. package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
  13. package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
  14. package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
  15. package/js/components/PaymentInterfaces/types.ts +5 -2
  16. package/js/components/PurchaseButton/index.spec.tsx +69 -37
  17. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
  18. package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
  19. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
  20. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +75 -41
  21. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +0 -30
  22. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
  23. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
  24. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
  25. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +5 -0
  26. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
  27. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
  28. package/js/components/SaleTunnel/_styles.scss +10 -1
  29. package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
  30. package/js/components/SaleTunnel/index.credential.spec.tsx +12 -21
  31. package/js/components/SaleTunnel/index.full-process.spec.tsx +110 -48
  32. package/js/components/SaleTunnel/index.spec.tsx +330 -779
  33. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
  34. package/js/components/SignContractButton/index.spec.tsx +16 -20
  35. package/js/components/SignContractButton/index.tsx +3 -1
  36. package/js/hooks/useCreditCards/index.spec.tsx +70 -6
  37. package/js/hooks/useCreditCards/index.ts +49 -11
  38. package/js/hooks/useOrders/index.spec.tsx +322 -0
  39. package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
  40. package/js/hooks/useProductOrder/index.spec.tsx +77 -60
  41. package/js/hooks/useProductOrder/index.tsx +2 -2
  42. package/js/hooks/useResources/useResourcesRoot.ts +1 -0
  43. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
  44. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
  45. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
  46. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
  47. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
  48. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
  49. package/js/settings/settings.test.ts +11 -2
  50. package/js/types/Joanie.ts +49 -34
  51. package/js/utils/OrderHelper/index.ts +38 -42
  52. package/js/utils/test/factories/joanie.ts +36 -51
  53. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
  54. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
  55. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
  56. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +7 -6
  57. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +9 -10
  58. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
  59. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
  60. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +28 -8
  61. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +2 -5
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +34 -35
  64. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +27 -24
  65. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
  66. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
  67. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
  68. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
  71. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
  72. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
  73. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
  74. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
  76. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
  77. package/package.json +1 -1
  78. package/scss/components/_index.scss +2 -1
  79. package/js/components/PaymentButton/_styles.scss +0 -27
  80. package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -333
  81. package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
  82. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
@@ -5,6 +5,7 @@ import { IntlProvider } from 'react-intl';
5
5
  import { QueryClientProvider } from '@tanstack/react-query';
6
6
  import fetchMock from 'fetch-mock';
7
7
  import userEvent from '@testing-library/user-event';
8
+ import { CunninghamProvider } from '@openfun/cunningham-react';
8
9
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
9
10
  import JoanieApiProvider from 'contexts/JoanieApiContext';
10
11
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
@@ -21,11 +22,13 @@ jest.mock('utils/context', () => ({
21
22
  describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () => {
22
23
  const Wrapper = ({ children }: PropsWithChildren) => {
23
24
  return (
24
- <IntlProvider locale="en">
25
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
26
- <JoanieApiProvider>{children}</JoanieApiProvider>
27
- </QueryClientProvider>
28
- </IntlProvider>
25
+ <CunninghamProvider>
26
+ <IntlProvider locale="en">
27
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
28
+ <JoanieApiProvider>{children}</JoanieApiProvider>
29
+ </QueryClientProvider>
30
+ </IntlProvider>
31
+ </CunninghamProvider>
29
32
  );
30
33
  };
31
34
 
@@ -46,8 +49,6 @@ describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () =>
46
49
  expect(
47
50
  screen.getByRole('button', { name: 'Sign all pending contracts (12)' }),
48
51
  ).toBeInTheDocument();
49
- const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
50
- expect(DashboardContractFramePortal).toHaveLength(1);
51
52
  });
52
53
 
53
54
  it("shouldn't display sign button user don't have some contract to sign", () => {
@@ -63,8 +64,6 @@ describe('TeacherDashboardContractsLayout/SignOrganizationContractButton', () =>
63
64
  expect(
64
65
  screen.queryByRole('button', { name: /Sign all pending contracts/ }),
65
66
  ).not.toBeInTheDocument();
66
- const DashboardContractFramePortal = document.getElementsByClassName('ReactModalPortal');
67
- expect(DashboardContractFramePortal).toHaveLength(1);
68
67
  });
69
68
 
70
69
  it.each([
@@ -14,7 +14,7 @@ import CourseLearnerDataGrid from '.';
14
14
  describe('pages/CourseLearnerDataGrid', () => {
15
15
  it('should render a list of user', async () => {
16
16
  const courseOrderList = NestedCourseOrderFactory({
17
- state: OrderState.VALIDATED,
17
+ state: OrderState.COMPLETED,
18
18
  certificate_id: faker.string.uuid(),
19
19
  }).many(3);
20
20
  render(
@@ -74,12 +74,7 @@ const CourseLearnerDataGrid = ({
74
74
  headerName: intl.formatMessage(messages.columnState),
75
75
  enableSorting: false,
76
76
  renderCell: (params: { row: Row }) => {
77
- return (
78
- <OrderStateTeacherMessage
79
- order={params.row.courseOrder}
80
- contractDefinition={params.row.courseOrder.product.contract_definition_id}
81
- />
82
- );
77
+ return <OrderStateTeacherMessage order={params.row.courseOrder} />;
83
78
  },
84
79
  },
85
80
  {
@@ -1,5 +1,5 @@
1
- // This confiuration file is used for testing.
2
- // Mostly usefull to test our test tools.
1
+ // This configuration file is used for testing.
2
+ // Mostly useful to test our test tools.
3
3
  import { DevDemoUser } from 'api/lms/dummy';
4
4
 
5
5
  /**
@@ -14,3 +14,12 @@ import { DevDemoUser } from 'api/lms/dummy';
14
14
  * * student_user
15
15
  */
16
16
  export const CURRENT_JOANIE_DEV_DEMO_USER: DevDemoUser = 'admin';
17
+
18
+ export const CONTRACT_SETTINGS = {
19
+ // Interval in ms to poll the related order when a signature has succeeded.
20
+ pollInterval: 150,
21
+ // Number of retries
22
+ pollLimit: 45,
23
+ // Simulated sign request delay
24
+ dummySignatureSignTimeout: 100,
25
+ };
@@ -259,17 +259,42 @@ export interface EnrollmentLight {
259
259
 
260
260
  // Order
261
261
  export enum OrderState {
262
- DRAFT = 'draft',
263
- SUBMITTED = 'submitted',
262
+ ASSIGNED = 'assigned',
264
263
  CANCELED = 'canceled',
264
+ COMPLETED = 'completed',
265
+ DRAFT = 'draft',
266
+ FAILED_PAYMENT = 'failed_payment',
267
+ NO_PAYMENT = 'no_payment',
265
268
  PENDING = 'pending',
266
269
  PENDING_PAYMENT = 'pending_payment',
267
- VALIDATED = 'validated',
268
- NO_PAYMENT = 'no_payment',
269
- FAILED_PAYMENT = 'failed_payment',
270
- }
271
-
272
- export const ACTIVE_ORDER_STATES = [OrderState.PENDING, OrderState.VALIDATED, OrderState.SUBMITTED];
270
+ SIGNING = 'signing',
271
+ TO_SAVE_PAYMENT_METHOD = 'to_save_payment_method',
272
+ TO_SIGN = 'to_sign',
273
+ }
274
+
275
+ export const PURCHASABLE_ORDER_STATES = [
276
+ OrderState.DRAFT,
277
+ OrderState.ASSIGNED,
278
+ OrderState.TO_SIGN,
279
+ OrderState.SIGNING,
280
+ OrderState.TO_SAVE_PAYMENT_METHOD,
281
+ ];
282
+
283
+ export const ACTIVE_ORDER_STATES = [
284
+ OrderState.PENDING,
285
+ OrderState.PENDING_PAYMENT,
286
+ OrderState.NO_PAYMENT,
287
+ OrderState.FAILED_PAYMENT,
288
+ OrderState.COMPLETED,
289
+ ];
290
+
291
+ export const NOT_CANCELED_ORDER_STATES = [...ACTIVE_ORDER_STATES, ...PURCHASABLE_ORDER_STATES];
292
+
293
+ export const ENROLLABLE_ORDER_STATES = [
294
+ OrderState.COMPLETED,
295
+ OrderState.PENDING_PAYMENT,
296
+ OrderState.FAILED_PAYMENT,
297
+ ];
273
298
 
274
299
  export interface Order {
275
300
  id: string;
@@ -290,6 +315,7 @@ export interface Order {
290
315
  organization: Organization;
291
316
  order_group_id?: OrderGroup['id'];
292
317
  payment_schedule?: PaymentSchedule;
318
+ credit_card_id?: CreditCard['id'];
293
319
  }
294
320
 
295
321
  export interface CredentialOrder extends Order {
@@ -297,20 +323,12 @@ export interface CredentialOrder extends Order {
297
323
  enrollment: null;
298
324
  }
299
325
 
300
- export interface CredentialOrderWithPaymentInfo extends CredentialOrder {
301
- payment_info: Payment;
302
- }
303
-
304
326
  export interface CertificateOrder extends Order {
305
327
  course: null;
306
328
  enrollment: EnrollmentLight;
307
329
  target_courses: never[];
308
330
  }
309
331
 
310
- export interface CertificateOrderWithPaymentInfo extends CertificateOrder {
311
- payment_info: Payment;
312
- }
313
-
314
332
  export type OrderLite = Pick<
315
333
  Order,
316
334
  | 'id'
@@ -329,9 +347,11 @@ export interface AbstractNestedOrder {
329
347
  product_title: string;
330
348
  owner_name: string;
331
349
  state: OrderState;
350
+ course: Nullable<CourseLight>;
351
+ enrollment: Nullable<EnrollmentLight>;
332
352
  }
333
353
  export interface NestedCertificateOrder extends AbstractNestedOrder {
334
- course: undefined;
354
+ course: null;
335
355
  enrollment: EnrollmentLight;
336
356
  }
337
357
  export const isNestedCredentialOrder = (
@@ -342,7 +362,7 @@ export const isNestedCredentialOrder = (
342
362
 
343
363
  export interface NestedCredentialOrder extends AbstractNestedOrder {
344
364
  course: CourseLight;
345
- enrollment: undefined;
365
+ enrollment: null;
346
366
  }
347
367
 
348
368
  export type OrderEnrollment = Pick<Order, 'id' | 'state' | 'product_id' | 'certificate_id'>;
@@ -376,10 +396,10 @@ export interface OrderGroup {
376
396
  }
377
397
 
378
398
  export enum CreditCardBrand {
379
- MASTERCARD = 'Mastercard',
380
- MAESTRO = 'Maestro',
381
- VISA = 'Visa',
382
- CB = 'CB',
399
+ MASTERCARD = 'mastercard',
400
+ MAESTRO = 'maestro',
401
+ VISA = 'visa',
402
+ CB = 'cb',
383
403
  }
384
404
 
385
405
  // Credit Card
@@ -444,7 +464,7 @@ export interface AddressCreationPayload extends Omit<Address, 'id' | 'is_main'>
444
464
  interface AbstractOrderProductCreationPayload {
445
465
  product_id: Product['id'];
446
466
  order_group_id?: OrderGroup['id'];
447
- has_consent_to_terms: boolean;
467
+ billing_address: Omit<Address, 'id' | 'is_main'>;
448
468
  }
449
469
 
450
470
  interface OrderCertificateCreationPayload extends AbstractOrderProductCreationPayload {
@@ -460,15 +480,9 @@ export type OrderSubmitInstallmentPayment = {
460
480
  credit_card_id?: string;
461
481
  };
462
482
 
463
- interface OrderAbortPayload {
464
- id: Order['id'];
465
- payment_id?: string;
466
- }
467
-
468
- interface OrderSubmitPayload {
483
+ interface OrderSetPaymentMethodPayload {
469
484
  id: Order['id'];
470
- billing_address: Omit<Address, 'id' | 'is_main'>;
471
- credit_card_id?: CreditCard['id'];
485
+ credit_card_id: CreditCard['id'];
472
486
  }
473
487
 
474
488
  export interface PaginatedResourceQuery extends ResourcesQuery {
@@ -563,14 +577,15 @@ interface APIUser {
563
577
  update(payload: Address): Promise<Address>;
564
578
  };
565
579
  creditCards: {
566
- create(payload: Omit<CreditCard, 'id'>): Promise<CreditCard>;
567
580
  delete(id: CreditCard['id']): Promise<void>;
581
+ get(): Promise<PaginatedResponse<CreditCard>>;
568
582
  get(filters?: ResourcesQuery): Promise<CreditCard>;
569
583
  get(): Promise<CreditCard[]>;
570
584
  update(payload: CreditCard): Promise<CreditCard>;
585
+ tokenize(): Promise<Payment>;
571
586
  };
572
587
  orders: {
573
- abort(payload: OrderAbortPayload): Promise<void>;
588
+ cancel(id: Order['id']): Promise<void>;
574
589
  create(payload: OrderCreationPayload): Promise<CredentialOrder | CertificateOrder>;
575
590
  get<Filters extends OrderResourcesQuery = OrderResourcesQuery>(
576
591
  filters?: Filters,
@@ -580,12 +595,12 @@ interface APIUser {
580
595
  invoice: {
581
596
  download(payload: { order_id: Order['id']; invoice_reference: string }): Promise<File>;
582
597
  };
583
- submit(payload: OrderSubmitPayload): Promise<OrderPaymentInfo>;
584
598
  submit_for_signature(id: string): Promise<ContractInvitationLinkResponse>;
585
599
  submit_installment_payment(
586
600
  id: string,
587
601
  payload?: OrderSubmitInstallmentPayment,
588
602
  ): Promise<Payment>;
603
+ set_payment_method(payload: OrderSetPaymentMethodPayload): Promise<void>;
589
604
  };
590
605
  certificates: {
591
606
  download(id: string): Promise<File>;
@@ -1,56 +1,52 @@
1
1
  import {
2
- OrderEnrollment,
3
2
  ACTIVE_ORDER_STATES,
3
+ ENROLLABLE_ORDER_STATES,
4
+ NestedCourseOrder,
4
5
  Order,
6
+ OrderEnrollment,
5
7
  OrderState,
6
- ContractDefinition,
7
- NestedCourseOrder,
8
8
  PaymentScheduleState,
9
9
  } from 'types/Joanie';
10
10
 
11
11
  export enum OrderStatus {
12
- DRAFT = 'draft',
13
- SUBMITTED = 'submitted',
14
- PENDING = 'pending',
12
+ ASSIGNED = 'assigned',
15
13
  CANCELED = 'canceled',
16
- WAITING_SIGNATURE = 'waiting_signature',
17
- WAITING_COUNTER_SIGNATURE = 'waiting_counter_signature',
18
14
  COMPLETED = 'completed',
19
- ON_GOING = 'on_going',
15
+ DRAFT = 'draft',
16
+ FAILED_PAYMENT = 'failed_payment',
20
17
  NO_PAYMENT = 'no_payment',
18
+ PASSED = 'passed',
19
+ PENDING = 'pending',
21
20
  PENDING_PAYMENT = 'pending_payment',
22
- FAILED_PAYMENT = 'failed_payment',
21
+ WAITING_COUNTER_SIGNATURE = 'waiting_counter_signature',
22
+ WAITING_PAYMENT_METHOD = 'waiting_payment_method',
23
+ WAITING_SIGNATURE = 'waiting_signature',
23
24
  }
24
25
 
25
26
  /**
26
27
  * Helper class for orders
27
28
  */
28
29
  export class OrderHelper {
29
- static getState(order: Order | NestedCourseOrder, contractDefinition?: ContractDefinition) {
30
- const { certificate_id: certificateId } = order;
31
-
32
- if (order.state === OrderState.VALIDATED) {
33
- if (OrderHelper.orderNeedsSignature(order, contractDefinition)) {
34
- return OrderStatus.WAITING_SIGNATURE;
35
- }
36
- if (OrderHelper.orderNeedsCounterSignature(order)) {
37
- return OrderStatus.WAITING_COUNTER_SIGNATURE;
38
- }
39
- if (certificateId) {
40
- return OrderStatus.COMPLETED;
41
- } else {
42
- return OrderStatus.ON_GOING;
43
- }
30
+ static getState(order: Order | NestedCourseOrder) {
31
+ if (OrderHelper.allowEnrollment(order) && OrderHelper.orderNeedsCounterSignature(order)) {
32
+ return OrderStatus.WAITING_COUNTER_SIGNATURE;
33
+ }
34
+ if (order.state === OrderState.COMPLETED && order.certificate_id) {
35
+ return OrderStatus.PASSED;
44
36
  }
45
37
 
46
38
  const orderStatusMap = {
47
- [OrderState.DRAFT]: OrderStatus.DRAFT,
48
- [OrderState.SUBMITTED]: OrderStatus.SUBMITTED,
49
- [OrderState.PENDING]: OrderStatus.PENDING,
39
+ [OrderState.ASSIGNED]: OrderStatus.ASSIGNED,
50
40
  [OrderState.CANCELED]: OrderStatus.CANCELED,
41
+ [OrderState.COMPLETED]: OrderStatus.COMPLETED,
42
+ [OrderState.DRAFT]: OrderStatus.DRAFT,
43
+ [OrderState.FAILED_PAYMENT]: OrderStatus.FAILED_PAYMENT,
51
44
  [OrderState.NO_PAYMENT]: OrderStatus.NO_PAYMENT,
45
+ [OrderState.PENDING]: OrderStatus.PENDING,
52
46
  [OrderState.PENDING_PAYMENT]: OrderStatus.PENDING_PAYMENT,
53
- [OrderState.FAILED_PAYMENT]: OrderStatus.PENDING_PAYMENT,
47
+ [OrderState.SIGNING]: OrderStatus.WAITING_SIGNATURE,
48
+ [OrderState.TO_SAVE_PAYMENT_METHOD]: OrderStatus.WAITING_PAYMENT_METHOD,
49
+ [OrderState.TO_SIGN]: OrderStatus.WAITING_SIGNATURE,
54
50
  };
55
51
 
56
52
  if (order.state in orderStatusMap) {
@@ -69,18 +65,8 @@ export class OrderHelper {
69
65
  return orders.find(filter);
70
66
  }
71
67
 
72
- /**
73
- * tell us if a order need to be sign by it's owner (the learner user).
74
- */
75
- static orderNeedsSignature(
76
- order: Order | NestedCourseOrder,
77
- contractDefinition?: ContractDefinition,
78
- ) {
79
- return (
80
- order?.state === OrderState.VALIDATED &&
81
- contractDefinition &&
82
- !(order.contract && order.contract.student_signed_on)
83
- );
68
+ static orderNeedsSignature(order: Order | NestedCourseOrder) {
69
+ return [OrderState.TO_SIGN, OrderState.SIGNING].includes(order.state);
84
70
  }
85
71
 
86
72
  /**
@@ -88,7 +74,7 @@ export class OrderHelper {
88
74
  */
89
75
  static orderNeedsCounterSignature(order: Order | NestedCourseOrder) {
90
76
  return (
91
- order?.state === OrderState.VALIDATED &&
77
+ ACTIVE_ORDER_STATES.includes(order.state) &&
92
78
  order.contract &&
93
79
  order.contract.student_signed_on &&
94
80
  !order.contract.organization_signed_on
@@ -100,4 +86,14 @@ export class OrderHelper {
100
86
  (installment) => installment.state === PaymentScheduleState.REFUSED,
101
87
  );
102
88
  }
89
+
90
+ static allowEnrollment(order?: Order | NestedCourseOrder | OrderEnrollment) {
91
+ if (!order) return false;
92
+ return ENROLLABLE_ORDER_STATES.includes(order.state);
93
+ }
94
+
95
+ static isActive(order?: Order | NestedCourseOrder | OrderEnrollment) {
96
+ if (!order) return false;
97
+ return ACTIVE_ORDER_STATES.includes(order.state);
98
+ }
103
99
  }
@@ -5,7 +5,6 @@ import {
5
5
  Certificate,
6
6
  CertificateDefinition,
7
7
  CertificateOrder,
8
- CertificateOrderWithPaymentInfo,
9
8
  CertificateProduct,
10
9
  Contract,
11
10
  ContractDefinition,
@@ -16,7 +15,6 @@ import {
16
15
  CourseProductRelation,
17
16
  CourseRun,
18
17
  CredentialOrder,
19
- CredentialOrderWithPaymentInfo,
20
18
  CredentialProduct,
21
19
  CreditCard,
22
20
  CreditCardBrand,
@@ -45,6 +43,9 @@ import { Payment, PaymentProviders } from 'components/PaymentInterfaces/types';
45
43
  import { CourseStateFactory } from 'utils/test/factories/richie';
46
44
  import { FactoryHelper } from 'utils/test/factories/helper';
47
45
  import { JoanieUserApiAbilityActions, JoanieUserProfile } from 'types/User';
46
+ import { SaleTunnelContextType, SaleTunnelStep } from 'components/SaleTunnel/GenericSaleTunnel';
47
+ import { SaleTunnelProps } from 'components/SaleTunnel';
48
+ import { noop } from 'utils/index';
48
49
  import { factory } from './factories';
49
50
 
50
51
  export const UserLightFactory = factory((): UserLight => {
@@ -140,7 +141,7 @@ export const ContractFactory = factory((): Contract => {
140
141
  organization_signatory: null,
141
142
  created_on: faker.date.past().toISOString(),
142
143
  definition: ContractDefinitionFactory().one(),
143
- order: NestedCredentialOrderFactory().one(),
144
+ order: NestedCredentialOrderFactory({ state: OrderState.TO_SIGN }).one(),
144
145
  };
145
146
  });
146
147
 
@@ -314,7 +315,7 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
314
315
  owner: UserLightFactory().one(),
315
316
  course_id: faker.string.uuid(),
316
317
  product_id: faker.string.uuid(),
317
- state: OrderState.VALIDATED,
318
+ state: OrderState.COMPLETED,
318
319
  enrollment_id: faker.string.uuid(),
319
320
  organization: OrganizationFactory().one(),
320
321
  certificate_id: faker.string.uuid(),
@@ -363,7 +364,7 @@ export const OrderEnrollmentFactory = factory((): OrderEnrollment => {
363
364
  return {
364
365
  id: faker.string.uuid(),
365
366
  product_id: faker.string.uuid(),
366
- state: OrderState.VALIDATED,
367
+ state: OrderState.COMPLETED,
367
368
  };
368
369
  });
369
370
 
@@ -375,19 +376,19 @@ export const OrderLiteFactory = factory((): OrderLite => {
375
376
  main_invoice_reference: faker.string.uuid(),
376
377
  total: faker.number.int(),
377
378
  product_id: faker.string.uuid(),
378
- state: OrderState.VALIDATED,
379
+ state: OrderState.COMPLETED,
379
380
  };
380
381
  });
381
382
 
382
383
  export const NestedCertificateOrderFactory = factory((): NestedCertificateOrder => {
383
384
  return {
384
385
  id: faker.string.uuid(),
385
- course: undefined,
386
+ course: null,
386
387
  enrollment: EnrollmentLightFactory().one(),
387
388
  organization: OrganizationFactory().one(),
388
389
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
389
390
  owner_name: faker.internet.userName(),
390
- state: OrderState.VALIDATED,
391
+ state: OrderState.COMPLETED,
391
392
  };
392
393
  });
393
394
 
@@ -395,11 +396,11 @@ export const NestedCredentialOrderFactory = factory((): NestedCredentialOrder =>
395
396
  return {
396
397
  id: faker.string.uuid(),
397
398
  course: CourseLightFactory().one(),
398
- enrollment: undefined,
399
+ enrollment: null,
399
400
  organization: OrganizationFactory().one(),
400
401
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
401
402
  owner_name: faker.internet.userName(),
402
- state: OrderState.VALIDATED,
403
+ state: OrderState.COMPLETED,
403
404
  };
404
405
  });
405
406
 
@@ -411,7 +412,7 @@ const AbstractOrderFactory = factory((): Order => {
411
412
  total: faker.number.int(),
412
413
  total_currency: faker.finance.currencyCode(),
413
414
  main_invoice_reference: faker.string.uuid(),
414
- state: OrderState.VALIDATED,
415
+ state: OrderState.COMPLETED,
415
416
  product_id: faker.string.uuid(),
416
417
  target_courses: TargetCourseFactory().many(5),
417
418
  target_enrollments: [],
@@ -422,56 +423,22 @@ const AbstractOrderFactory = factory((): Order => {
422
423
  };
423
424
  });
424
425
 
425
- export const CredentialOrderFactory = factory((): CredentialOrder => {
426
- const order = {
426
+ export const CredentialOrderFactory = factory(
427
+ (): CredentialOrder => ({
427
428
  ...AbstractOrderFactory().one(),
428
429
  course: CourseLightFactory().one(),
429
430
  enrollment: null,
430
431
  payment_schedule: PaymentInstallmentFactory().many(3),
431
- };
432
- return order;
433
- });
434
-
435
- export const CredentialOrderWithPaymentFactory = factory((): CredentialOrderWithPaymentInfo => {
436
- return {
437
- ...CredentialOrderFactory().one(),
438
- payment_info: PaymentFactory().one(),
439
- };
440
- });
441
-
442
- export const CredentialOrderWithOneClickPaymentFactory = factory(
443
- (): CredentialOrderWithPaymentInfo => {
444
- return {
445
- ...CredentialOrderFactory().one(),
446
- payment_info: PaymentFactory().one(),
447
- };
448
- },
432
+ }),
449
433
  );
450
434
 
451
- export const CertificateOrderFactory = factory((): CertificateOrder => {
452
- const order = {
435
+ export const CertificateOrderFactory = factory(
436
+ (): CertificateOrder => ({
453
437
  ...AbstractOrderFactory().one(),
454
438
  course: null,
455
439
  target_courses: [],
456
440
  enrollment: EnrollmentLightFactory().one(),
457
- };
458
- return order;
459
- });
460
-
461
- export const CertificateOrderWithPaymentFactory = factory((): CertificateOrderWithPaymentInfo => {
462
- return {
463
- ...CertificateOrderFactory().one(),
464
- payment_info: PaymentFactory().one(),
465
- };
466
- });
467
-
468
- export const CertificateOrderWithOneClickPaymentFactory = factory(
469
- (): CertificateOrderWithPaymentInfo => {
470
- return {
471
- ...CertificateOrderFactory().one(),
472
- payment_info: PaymentFactory().one(),
473
- };
474
- },
441
+ }),
475
442
  );
476
443
 
477
444
  export const AddressFactory = factory((): Address => {
@@ -507,3 +474,21 @@ export const PaymentFactory = factory((): Payment => {
507
474
  url: faker.internet.url(),
508
475
  };
509
476
  });
477
+
478
+ export const SaleTunnelContextFactory = factory(
479
+ (): SaleTunnelContextType => ({
480
+ webAnalyticsEventKey: 'eventKey',
481
+ order: CredentialOrderFactory().one(),
482
+ product: ProductFactory().one(),
483
+ props: {} as SaleTunnelProps,
484
+ billingAddress: undefined,
485
+ setBillingAddress: noop,
486
+ setCreditCard: noop,
487
+ onPaymentSuccess: noop,
488
+ step: SaleTunnelStep.IDLE,
489
+ registerSubmitCallback: noop,
490
+ unregisterSubmitCallback: noop,
491
+ runSubmitCallbacks: () => new Promise((resolve) => resolve()),
492
+ nextStep: noop,
493
+ }),
494
+ );
@@ -9,7 +9,6 @@ import {
9
9
  CourseRun,
10
10
  CredentialOrder,
11
11
  Enrollment,
12
- Product,
13
12
  } from 'types/Joanie';
14
13
  import { Spinner } from 'components/Spinner';
15
14
  import Banner, { BannerType } from 'components/Banner';
@@ -68,10 +67,11 @@ const messages = defineMessages({
68
67
  description: 'Text displayed when course runs list is loading',
69
68
  id: 'components.DashboardItemCourseEnrollingRun.courseRunsLoading',
70
69
  },
71
- contractUnsigned: {
72
- id: 'components.DashboardItemCourseEnrollingRun.contractUnsigned',
73
- description: 'Message displayed as disabled button title when a contract needs to be signed.',
74
- defaultMessage: 'You have to sign the training contract before enrolling to your course.',
70
+ cannotEnroll: {
71
+ id: 'components.DashboardItemCourseEnrollingRun.cannotEnroll',
72
+ description:
73
+ 'Message displayed as disabled button title when the order state does not allow enrollment.',
74
+ defaultMessage: 'You cannot enroll yet to this training.',
75
75
  },
76
76
  });
77
77
 
@@ -81,7 +81,6 @@ interface DashboardItemCourseEnrollingProps {
81
81
  course: AbstractCourse;
82
82
  activeEnrollment?: Enrollment;
83
83
  order?: CredentialOrder;
84
- product?: Product;
85
84
  writable: boolean;
86
85
  hideEnrollButtons?: boolean;
87
86
  icon?: boolean;
@@ -93,7 +92,6 @@ export const DashboardItemCourseEnrolling = ({
93
92
  activeEnrollment,
94
93
  writable,
95
94
  order,
96
- product,
97
95
  icon = false,
98
96
  notEnrolledUrl = '#',
99
97
  hideEnrollButtons,
@@ -121,7 +119,6 @@ export const DashboardItemCourseEnrolling = ({
121
119
  course={course}
122
120
  enrollments={CoursesHelper.findCourseEnrollmentsInOrder(course, order)}
123
121
  order={order}
124
- product={product}
125
122
  />
126
123
  )}
127
124
  </div>
@@ -132,14 +129,12 @@ interface DashboardItemCourseEnrollingRunsProps {
132
129
  course: AbstractCourse;
133
130
  enrollments: Enrollment[];
134
131
  order?: CredentialOrder;
135
- product?: Product;
136
132
  }
137
133
 
138
134
  const DashboardItemCourseEnrollingRuns = ({
139
135
  course,
140
136
  enrollments,
141
137
  order,
142
- product,
143
138
  }: DashboardItemCourseEnrollingRunsProps) => {
144
139
  const { enroll, isLoading, error } = useEnroll(enrollments, order);
145
140
 
@@ -176,7 +171,6 @@ const DashboardItemCourseEnrollingRuns = ({
176
171
  selected={data.selected}
177
172
  enroll={() => enroll(data.courseRun)}
178
173
  order={order}
179
- product={product}
180
174
  />
181
175
  ))}
182
176
  {isLoading && (
@@ -200,7 +194,6 @@ interface DashboardItemCourseEnrollingRunProps {
200
194
  selected: boolean;
201
195
  enroll: () => void;
202
196
  order?: CredentialOrder | CertificateOrder;
203
- product?: Product;
204
197
  }
205
198
 
206
199
  export const DashboardItemCourseEnrollingRun = ({
@@ -208,14 +201,11 @@ export const DashboardItemCourseEnrollingRun = ({
208
201
  selected,
209
202
  enroll,
210
203
  order,
211
- product,
212
204
  }: DashboardItemCourseEnrollingRunProps) => {
213
205
  const intl = useIntl();
214
206
  const formatDate = useDateFormat();
215
207
  const courseRunPeriodMessage = useCourseRunPeriodMessage(courseRun, selected);
216
- const haveToSignContract = order
217
- ? OrderHelper.orderNeedsSignature(order, product?.contract_definition)
218
- : false;
208
+ const canEnroll = OrderHelper.allowEnrollment(order);
219
209
  const isOpenedForEnrollment = useMemo(
220
210
  () => courseRun.state.priority < Priority.FUTURE_NOT_YET_OPEN,
221
211
  [courseRun],
@@ -266,11 +256,11 @@ export const DashboardItemCourseEnrollingRun = ({
266
256
  ) : (
267
257
  <div>
268
258
  <Button
269
- disabled={!isOpenedForEnrollment || haveToSignContract}
259
+ disabled={!isOpenedForEnrollment || !canEnroll}
270
260
  color="tertiary"
271
261
  size="small"
272
262
  onClick={enroll}
273
- title={haveToSignContract ? intl.formatMessage(messages.contractUnsigned) : ''}
263
+ title={!canEnroll ? intl.formatMessage(messages.cannotEnroll) : ''}
274
264
  >
275
265
  <FormattedMessage {...messages.enrollRun} />
276
266
  </Button>