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.
Files changed (89) hide show
  1. package/js/api/joanie.ts +42 -17
  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/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.spec.tsx +15 -45
  8. package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.tsx +17 -24
  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/PaymentScheduleGrid/_styles.scss +13 -0
  17. package/js/components/PaymentScheduleGrid/index.tsx +50 -70
  18. package/js/components/PurchaseButton/index.spec.tsx +84 -37
  19. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
  20. package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
  21. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
  22. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +80 -27
  23. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +16 -20
  24. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
  25. package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
  26. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
  27. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.scss +4 -5
  28. package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +39 -11
  29. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
  30. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
  31. package/js/components/SaleTunnel/_styles.scss +16 -5
  32. package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
  33. package/js/components/SaleTunnel/index.credential.spec.tsx +14 -25
  34. package/js/components/SaleTunnel/index.full-process.spec.tsx +116 -48
  35. package/js/components/SaleTunnel/index.spec.tsx +334 -717
  36. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
  37. package/js/components/SignContractButton/index.spec.tsx +16 -20
  38. package/js/components/SignContractButton/index.tsx +3 -1
  39. package/js/hooks/useCreditCards/index.spec.tsx +70 -6
  40. package/js/hooks/useCreditCards/index.ts +49 -11
  41. package/js/hooks/useOrders/index.spec.tsx +322 -0
  42. package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
  43. package/js/hooks/usePaymentSchedule.tsx +23 -0
  44. package/js/hooks/useProductOrder/index.spec.tsx +77 -60
  45. package/js/hooks/useProductOrder/index.tsx +2 -2
  46. package/js/hooks/useResources/useResourcesRoot.ts +4 -3
  47. package/js/index.tsx +2 -0
  48. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
  49. package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
  50. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
  51. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
  52. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
  53. package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
  54. package/js/settings/settings.test.ts +11 -2
  55. package/js/types/Joanie.ts +77 -31
  56. package/js/utils/OrderHelper/index.ts +47 -38
  57. package/js/utils/test/factories/joanie.ts +66 -68
  58. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
  59. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
  60. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
  61. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +114 -5
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +99 -12
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
  64. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
  65. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +7 -0
  66. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +126 -0
  67. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +209 -0
  68. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
  69. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +40 -25
  70. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +28 -22
  71. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
  72. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
  73. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
  74. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
  76. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
  77. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
  78. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
  79. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
  80. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
  81. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
  82. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
  83. package/package.json +2 -1
  84. package/scss/components/_index.scss +4 -2
  85. package/js/components/PaymentButton/_styles.scss +0 -27
  86. package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -338
  87. package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
  88. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
  89. /package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/_styles.scss +0 -0
@@ -1,11 +1,7 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
2
  import { screen } from '@testing-library/react';
3
3
  import { createIntl } from 'react-intl';
4
- import {
5
- ContractDefinitionFactory,
6
- ContractFactory,
7
- CredentialOrderFactory,
8
- } from 'utils/test/factories/joanie';
4
+ import { ContractFactory, CredentialOrderFactory } from 'utils/test/factories/joanie';
9
5
  import { OrderState } from 'types/Joanie';
10
6
  import { render } from 'utils/test/render';
11
7
  import { IntlWrapper } from 'utils/test/wrappers/IntlWrapper';
@@ -15,100 +11,49 @@ const intl = createIntl({ locale: 'en' });
15
11
 
16
12
  describe('<OrderStateTeacherMessage/>', () => {
17
13
  it.each([
18
- [OrderState.DRAFT, 'Pending'],
19
- [OrderState.SUBMITTED, 'Pending'],
20
- [OrderState.PENDING, 'Pending'],
14
+ [OrderState.ASSIGNED, 'Pending'],
21
15
  [OrderState.CANCELED, 'Canceled'],
22
- ])(
23
- 'should display message from order state: %s when order have no contract',
24
- (state, expectedMessage) => {
25
- const order = CredentialOrderFactory({ state }).one();
26
- render(<OrderStateTeacherMessage order={order} />, {
27
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
28
- });
29
- expect(screen.getByText(expectedMessage)).toBeInTheDocument();
30
- },
31
- );
32
-
33
- it.each([
16
+ [OrderState.COMPLETED, 'On going'],
34
17
  [OrderState.DRAFT, 'Pending'],
35
- [OrderState.SUBMITTED, 'Pending'],
36
- [OrderState.PENDING, 'Pending'],
37
- [OrderState.CANCELED, 'Canceled'],
38
- ])(
39
- 'should display message from order state: %s when order have contract',
40
- (state, expectedMessage) => {
41
- const orderWithContract = CredentialOrderFactory({
42
- state,
43
- contract: ContractFactory().one(),
44
- }).one();
45
- render(
46
- <OrderStateTeacherMessage
47
- order={orderWithContract}
48
- contractDefinition={ContractDefinitionFactory().one()}
49
- />,
50
- {
51
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
52
- },
53
- );
54
- expect(screen.getByText(expectedMessage)).toBeInTheDocument();
55
- },
56
- );
57
-
58
- it('should display message for validated order that need learner signature', () => {
59
- const order = CredentialOrderFactory({
60
- state: OrderState.VALIDATED,
61
- contract: null,
62
- }).one();
63
-
64
- const contractDefinition = ContractDefinitionFactory().one();
65
-
66
- render(<OrderStateTeacherMessage order={order} contractDefinition={contractDefinition} />, {
18
+ [OrderState.FAILED_PAYMENT, 'Last direct debit has failed'],
19
+ [OrderState.NO_PAYMENT, 'First direct debit has failed'],
20
+ [OrderState.PENDING, 'Pending for the first direct debit'],
21
+ [OrderState.PENDING_PAYMENT, 'On going'],
22
+ [OrderState.SIGNING, "Pending for learner's signature"],
23
+ [OrderState.TO_SAVE_PAYMENT_METHOD, 'Payment method is missing'],
24
+ [OrderState.TO_SIGN, "Pending for learner's signature"],
25
+ ])('should display message from order state: %s', (state, expectedMessage) => {
26
+ const order = CredentialOrderFactory({ state }).one();
27
+ render(<OrderStateTeacherMessage order={order} />, {
67
28
  wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
68
29
  });
69
- expect(screen.getByText("Pending for learner's signature")).toBeInTheDocument();
30
+ expect(screen.getByText(expectedMessage)).toBeInTheDocument();
70
31
  });
71
32
 
72
33
  it('should display message for validated order that need organization signature', () => {
73
34
  const order = CredentialOrderFactory({
74
- state: OrderState.VALIDATED,
35
+ state: OrderState.PENDING_PAYMENT,
75
36
  contract: ContractFactory({
76
37
  student_signed_on: new Date().toISOString(),
77
38
  }).one(),
78
39
  }).one();
79
- const contractDefinition = ContractDefinitionFactory().one();
80
-
81
- render(<OrderStateTeacherMessage order={order} contractDefinition={contractDefinition} />, {
82
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
83
- });
84
- expect(screen.getByText('To be signed')).toBeInTheDocument();
85
- });
86
40
 
87
- it("should display message for validated order that don't have a generated certificate", () => {
88
- const order = CredentialOrderFactory({
89
- state: OrderState.VALIDATED,
90
- certificate_id: undefined,
91
- }).one();
92
41
  render(<OrderStateTeacherMessage order={order} />, {
93
42
  wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
94
43
  });
95
- expect(
96
- screen.getByText(intl.formatMessage(messages.statusOnGoing), {
97
- exact: false,
98
- }),
99
- );
44
+ expect(screen.getByText('To be signed')).toBeInTheDocument();
100
45
  });
101
46
 
102
47
  it('should display message for validated order that have a generated certificate', () => {
103
48
  const order = CredentialOrderFactory({
104
- state: OrderState.VALIDATED,
49
+ state: OrderState.COMPLETED,
105
50
  certificate_id: 'FAKE_CERTIFICATE_ID',
106
51
  }).one();
107
52
  render(<OrderStateTeacherMessage order={order} />, {
108
53
  wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
109
54
  });
110
55
  expect(
111
- screen.getByText(intl.formatMessage(messages.statusCompleted), {
56
+ screen.getByText(intl.formatMessage(messages.statusPassed), {
112
57
  exact: false,
113
58
  }),
114
59
  );
@@ -1,33 +1,32 @@
1
1
  import { defineMessages } from 'react-intl';
2
- import OrderStateMessage, { OrderStateMessageBaseProps } from '../OrderStateMessage';
2
+ import OrderStateMessage, { MessageKeys, OrderStateMessageBaseProps } from '../OrderStateMessage';
3
3
 
4
- export const messages = defineMessages({
4
+ export const messages = defineMessages<MessageKeys>({
5
5
  statusDraft: {
6
6
  id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusDraft',
7
7
  description: 'Status shown on the dashboard order item when order is draft.',
8
8
  defaultMessage: 'Pending',
9
9
  },
10
- statusSubmitted: {
11
- id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusSubmitted',
12
- description: 'Status shown on the dashboard order item when order is submitted.',
10
+ statusAssigned: {
11
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusAssigned',
12
+ description: 'Status shown on the dashboard order item when order is assigned.',
13
13
  defaultMessage: 'Pending',
14
14
  },
15
15
  statusPending: {
16
16
  id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusPending',
17
17
  description: 'Status shown on the dashboard order item when order is pending.',
18
- defaultMessage: 'Pending',
18
+ defaultMessage: 'Pending for the first direct debit',
19
19
  },
20
- statusOnGoing: {
21
- id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusOnGoing',
20
+ statusPendingPayment: {
21
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusPendingPayment',
22
22
  description:
23
23
  'Status shown on the dashboard order item when order is validated with no certificate',
24
- defaultMessage: 'Enrolled',
24
+ defaultMessage: 'On going',
25
25
  },
26
26
  statusCompleted: {
27
27
  id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusCompleted',
28
- description:
29
- 'Status shown on the dashboard order item when order is validated with certificate',
30
- defaultMessage: 'Certified',
28
+ description: 'Status shown on the dashboard order item when order is completed',
29
+ defaultMessage: 'On going',
31
30
  },
32
31
  statusWaitingSignature: {
33
32
  id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusWaitingSignature',
@@ -35,6 +34,12 @@ export const messages = defineMessages({
35
34
  "Status shown on the dashboard order item when order is validated with contract's learner signature missing.",
36
35
  defaultMessage: "Pending for learner's signature",
37
36
  },
37
+ statusWaitingPaymentMethod: {
38
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusWaitingPaymentMethod',
39
+ description:
40
+ 'Status shown on the dashboard order item when order is in to_save_payment_method state.',
41
+ defaultMessage: 'Payment method is missing',
42
+ },
38
43
  statusWaitingCounterSignature: {
39
44
  id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusWaitingCounterSignature',
40
45
  description:
@@ -46,10 +51,21 @@ export const messages = defineMessages({
46
51
  description: 'Status shown on the dashboard order item when order is canceled',
47
52
  defaultMessage: 'Canceled',
48
53
  },
49
- statusOther: {
50
- id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusOther',
51
- description: 'Status shown on the dashboard order item when order status is unknown',
52
- defaultMessage: '{state}',
54
+ statusNoPayment: {
55
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusNoPayment',
56
+ description: 'Status shown on the dashboard order item when order is in no payment state',
57
+ defaultMessage: 'First direct debit has failed',
58
+ },
59
+ statusFailedPayment: {
60
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusFailedPayment',
61
+ description: 'Status shown on the dashboard order item when order is in failed payment state',
62
+ defaultMessage: 'Last direct debit has failed',
63
+ },
64
+ statusPassed: {
65
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusPassed',
66
+ description:
67
+ 'Status shown on the dashboard order item when order is completed with certificate',
68
+ defaultMessage: 'Certified',
53
69
  },
54
70
  });
55
71
 
@@ -4,7 +4,6 @@ import { useMemo } from 'react';
4
4
  import { useOmniscientOrder } from 'hooks/useOrders';
5
5
  import { Spinner } from 'components/Spinner';
6
6
  import Banner, { BannerType } from 'components/Banner';
7
- import { useCourseProduct } from 'hooks/useCourseProducts';
8
7
  import { isCredentialOrder } from 'pages/DashboardCourses/useOrdersEnrollments';
9
8
  import { handle } from 'utils/errors/handle';
10
9
  import { OrderHelper } from 'utils/OrderHelper';
@@ -39,10 +38,6 @@ export const DashboardOrderLoader = () => {
39
38
  item: order,
40
39
  states: { fetching: fetchingOrder, error: errorOrder },
41
40
  } = useOmniscientOrder(params.orderId);
42
- const {
43
- item: courseProduct,
44
- states: { fetching: fetchingCourseProduct, error: errorCourseProduct },
45
- } = useCourseProduct({ course_id: order?.course?.code, product_id: order?.product_id });
46
41
  const intl = useIntl();
47
42
 
48
43
  const credentialOrder = order && isCredentialOrder(order) ? order : undefined;
@@ -52,12 +47,9 @@ export const DashboardOrderLoader = () => {
52
47
  return intl.formatMessage(messages.wrongLinkedProductError);
53
48
  }
54
49
  }, [credentialOrder]);
55
- const error = errorOrder || errorCourseProduct || wrongLinkedProductError;
56
- const fetching = fetchingOrder || fetchingCourseProduct;
57
- const needsSignature = OrderHelper.orderNeedsSignature(
58
- order,
59
- courseProduct?.product.contract_definition,
60
- );
50
+ const error = errorOrder || wrongLinkedProductError;
51
+ const fetching = fetchingOrder;
52
+ const needsSignature = order ? OrderHelper.orderNeedsSignature(order) : false;
61
53
 
62
54
  return (
63
55
  <>
@@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage } from 'react-intl';
4
4
  import { SignatureProps } from 'components/ContractFrame';
5
5
  import { DummyContractPlaceholder } from 'widgets/Dashboard/components/Signature/DummyContractPlaceholder';
6
6
  import { CONTRACT_SETTINGS } from 'settings';
7
+ import { getAPIEndpoint } from 'api/joanie';
7
8
 
8
9
  const messages = defineMessages({
9
10
  button: {
@@ -23,12 +24,31 @@ enum SignatureDummySteps {
23
24
  SIGNING_LOADING,
24
25
  }
25
26
 
26
- export const SignatureDummy = ({ onDone }: SignatureProps) => {
27
+ export const SignatureDummy = ({ invitationLink, onDone }: SignatureProps) => {
27
28
  const [step, setStep] = useState(SignatureDummySteps.SIGNING);
28
29
 
30
+ const baseUrl = getAPIEndpoint();
31
+ // eslint-disable-next-line compat/compat
32
+ const link = new URL(invitationLink);
33
+ const reference = link.searchParams.get('reference');
34
+ const event = link.searchParams.get('eventTarget');
35
+ const sendSignatureNotification = () => {
36
+ fetch(`${baseUrl}/signature/notifications/`, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ },
41
+ body: JSON.stringify({
42
+ event_type: event,
43
+ reference,
44
+ }),
45
+ });
46
+ };
47
+
29
48
  const sign = () => {
30
49
  setStep(SignatureDummySteps.SIGNING_LOADING);
31
50
  setTimeout(() => {
51
+ sendSignatureNotification();
32
52
  onDone();
33
53
  }, CONTRACT_SETTINGS.dummySignatureSignTimeout);
34
54
  };
@@ -44,11 +64,13 @@ export const SignatureDummy = ({ onDone }: SignatureProps) => {
44
64
  </div>
45
65
  )}
46
66
  {step === SignatureDummySteps.SIGNING_LOADING && (
47
- <div className="ContractFrame__loading-container">
67
+ <div className="ContractFrame__container">
48
68
  <h3 className="ContractFrame__caption">
49
69
  <FormattedMessage {...messages.signing} />
50
70
  </h3>
51
- <Loader />
71
+ <div className="ContractFrame__footer">
72
+ <Loader />
73
+ </div>
52
74
  </div>
53
75
  )}
54
76
  </>
@@ -11,7 +11,6 @@ import useDateFormat from 'hooks/useDateFormat';
11
11
  import { IntlHelper } from 'utils/IntlHelper';
12
12
  import WebAnalyticsAPIHandler from 'api/web-analytics';
13
13
  import EnrollmentDate from 'components/EnrollmentDate';
14
- import { Product } from 'types/Joanie';
15
14
  import { OrderHelper } from 'utils/OrderHelper';
16
15
  import { messages as sharedMessages } from '../CourseRunItem';
17
16
  import CourseRunSection, { messages as sectionMessages } from './CourseRunSection';
@@ -53,16 +52,13 @@ const messages = defineMessages({
53
52
  interface Props {
54
53
  courseRuns: Joanie.CourseRun[];
55
54
  order: Joanie.Order;
56
- product: Product;
57
55
  }
58
56
 
59
- const EnrollableCourseRunList = ({ courseRuns, order, product }: Props) => {
57
+ const EnrollableCourseRunList = ({ courseRuns, order }: Props) => {
60
58
  const intl = useIntl();
61
59
  const formatDate = useDateFormat();
62
60
  const formRef = useRef<HTMLFormElement>(null);
63
- const needsSignature = order
64
- ? OrderHelper.orderNeedsSignature(order, product.contract_definition)
65
- : false;
61
+ const needsSignature = order ? OrderHelper.orderNeedsSignature(order) : false;
66
62
 
67
63
  const [selectedCourseRun, setSelectedCourseRun] = useState<Maybe<Joanie.CourseRun>>();
68
64
  const [submitted, setSubmitted] = useState(false);
@@ -11,7 +11,7 @@ import {
11
11
  CredentialOrderFactory,
12
12
  ProductFactory,
13
13
  } from 'utils/test/factories/joanie';
14
- import type { CourseLight, CourseRun, Enrollment } from 'types/Joanie';
14
+ import { CourseLight, CourseRun, Enrollment, OrderState } from 'types/Joanie';
15
15
  import { Deferred } from 'utils/test/deferred';
16
16
  import { CourseStateTextEnum, Priority } from 'types';
17
17
  import { IntlHelper } from 'utils/IntlHelper';
@@ -114,10 +114,9 @@ describe('CourseProductCourseRuns', () => {
114
114
 
115
115
  it('renders a warning message when no course runs are provided', () => {
116
116
  const order = CredentialOrderFactory().one();
117
- const product = ProductFactory().one();
118
117
 
119
118
  fetchMock.get('https://joanie.endpoint/api/v1.0/enrollments/', []);
120
- render(<EnrollableCourseRunList courseRuns={[]} order={order} product={product} />, {
119
+ render(<EnrollableCourseRunList courseRuns={[]} order={order} />, {
121
120
  wrapper: BaseJoanieAppWrapper,
122
121
  });
123
122
 
@@ -127,12 +126,9 @@ describe('CourseProductCourseRuns', () => {
127
126
  it('renders a list of course runs with a call to action to enroll', async () => {
128
127
  const courseRuns: CourseRun[] = CourseRunFactory().many(2);
129
128
  const order = CredentialOrderFactory().one();
130
- const product = ProductFactory({
131
- contract_definition: undefined,
132
- }).one();
133
129
 
134
130
  fetchMock.get('https://joanie.endpoint/api/v1.0/enrollments/', []);
135
- render(<EnrollableCourseRunList courseRuns={courseRuns} order={order} product={product} />, {
131
+ render(<EnrollableCourseRunList courseRuns={courseRuns} order={order} />, {
136
132
  wrapper: BaseJoanieAppWrapper,
137
133
  });
138
134
 
@@ -247,7 +243,7 @@ describe('CourseProductCourseRuns', () => {
247
243
  fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/${course.code}/`, HttpStatusCode.OK);
248
244
  fetchMock.get('https://joanie.endpoint/api/v1.0/enrollments/', []);
249
245
 
250
- render(<EnrollableCourseRunList courseRuns={courseRuns} order={order} product={product} />, {
246
+ render(<EnrollableCourseRunList courseRuns={courseRuns} order={order} />, {
251
247
  wrapper: BaseJoanieAppWrapper,
252
248
  });
253
249
 
@@ -355,12 +351,10 @@ describe('CourseProductCourseRuns', () => {
355
351
  text: CourseStateTextEnum.STARTING_ON,
356
352
  },
357
353
  }).one();
358
- const product = ProductFactory().one();
359
- product.contract_definition = undefined;
360
354
  const order = CredentialOrderFactory().one();
361
355
 
362
356
  fetchMock.get('https://joanie.endpoint/api/v1.0/enrollments/', []);
363
- render(<EnrollableCourseRunList courseRuns={[courseRun]} order={order} product={product} />, {
357
+ render(<EnrollableCourseRunList courseRuns={[courseRun]} order={order} />, {
364
358
  wrapper: BaseJoanieAppWrapper,
365
359
  });
366
360
 
@@ -438,11 +432,10 @@ describe('CourseProductCourseRuns', () => {
438
432
  text: CourseStateTextEnum.STARTING_ON,
439
433
  },
440
434
  }).one();
441
- const product = ProductFactory().one();
442
- const order = CredentialOrderFactory().one();
435
+ const order = CredentialOrderFactory({ state: OrderState.TO_SIGN }).one();
443
436
 
444
437
  fetchMock.get('https://joanie.endpoint/api/v1.0/enrollments/', []);
445
- render(<EnrollableCourseRunList courseRuns={[courseRun]} order={order} product={product} />, {
438
+ render(<EnrollableCourseRunList courseRuns={[courseRun]} order={order} />, {
446
439
  wrapper: BaseJoanieAppWrapper,
447
440
  });
448
441
 
@@ -1,6 +1,6 @@
1
1
  import { faker } from '@faker-js/faker';
2
2
  import { screen, getByText } from '@testing-library/react';
3
- import { CredentialOrderFactory, ProductFactory } from 'utils/test/factories/joanie';
3
+ import { CredentialOrderFactory } from 'utils/test/factories/joanie';
4
4
  import type { CourseRun, CredentialOrder } from 'types/Joanie';
5
5
  import { OrderState } from 'types/Joanie';
6
6
  import { render } from 'utils/test/render';
@@ -15,14 +15,16 @@ jest.mock('../CourseProductCourseRuns', () => ({
15
15
  describe('CourseRunItem', () => {
16
16
  it('does not allow user which purchase the product to enroll to course if order state is not validated', async () => {
17
17
  const order: CredentialOrder = CredentialOrderFactory({
18
- state: faker.helpers.arrayElement([OrderState.CANCELED, OrderState.PENDING]),
18
+ state: faker.helpers.arrayElement([
19
+ OrderState.CANCELED,
20
+ OrderState.PENDING,
21
+ OrderState.NO_PAYMENT,
22
+ ]),
19
23
  }).one();
20
- const product = ProductFactory().one();
21
- product.contract_definition = undefined;
22
24
 
23
25
  const targetCourse = order.target_courses[0];
24
26
 
25
- render(<CourseRunItem targetCourse={targetCourse} order={order} product={product} />, {
27
+ render(<CourseRunItem targetCourse={targetCourse} order={order} />, {
26
28
  wrapper: null,
27
29
  });
28
30
 
@@ -2,8 +2,8 @@ import { useEffect, useRef } from 'react';
2
2
  import { defineMessages } from 'react-intl';
3
3
  import { Priority } from 'types';
4
4
  import type * as Joanie from 'types/Joanie';
5
- import { OrderState, Product } from 'types/Joanie';
6
5
  import { CoursesHelper } from 'utils/CoursesHelper';
6
+ import { OrderHelper } from 'utils/OrderHelper';
7
7
  import {
8
8
  CourseRunList,
9
9
  EnrollableCourseRunList,
@@ -26,13 +26,12 @@ other {Languages:}
26
26
  interface Props {
27
27
  targetCourse: Joanie.TargetCourse;
28
28
  order?: Joanie.CredentialOrder;
29
- product: Product;
30
29
  }
31
30
 
32
- const CourseRunItem = ({ targetCourse, order, product }: Props) => {
33
- const isEnrollable = order?.state === OrderState.VALIDATED;
31
+ const CourseRunItem = ({ targetCourse, order }: Props) => {
32
+ const isEnrollable = OrderHelper.allowEnrollment(order);
34
33
  const courseRunEnrollment = isEnrollable
35
- ? CoursesHelper.findActiveCourseEnrollmentInOrder(targetCourse, order)
34
+ ? CoursesHelper.findActiveCourseEnrollmentInOrder(targetCourse, order!)
36
35
  : undefined;
37
36
  const isEnrolled = !!courseRunEnrollment?.is_active;
38
37
 
@@ -92,8 +91,7 @@ const CourseRunItem = ({ targetCourse, order, product }: Props) => {
92
91
  {isEnrollable && !isEnrolled && (
93
92
  <EnrollableCourseRunList
94
93
  courseRuns={targetCourse.course_runs.filter(isOpenedCourseRun)}
95
- order={order}
96
- product={product}
94
+ order={order!}
97
95
  />
98
96
  )}
99
97
  {isEnrollable && isEnrolled && <EnrolledCourseRun enrollment={courseRunEnrollment} />}