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
@@ -0,0 +1,126 @@
1
+ import {
2
+ Alert,
3
+ Button,
4
+ Loader,
5
+ Modal,
6
+ ModalProps,
7
+ ModalSize,
8
+ useModal,
9
+ VariantType,
10
+ } from '@openfun/cunningham-react';
11
+ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
12
+ import { useState, useEffect } from 'react';
13
+ import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
14
+ import { CreditCard, Order } from 'types/Joanie';
15
+ import { CreditCardSelector } from 'components/CreditCardSelector';
16
+ import { OrderHelper } from 'utils/OrderHelper';
17
+ import { OrderPaymentRetryModal } from 'widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal';
18
+ import { Maybe } from 'types/utils';
19
+ import { useCreditCard } from 'hooks/useCreditCards';
20
+
21
+ const messages = defineMessages({
22
+ title: {
23
+ id: 'components.DashboardItemOrder.PaymentModal.title',
24
+ defaultMessage: 'Payment details',
25
+ description: 'Title of the payment modal',
26
+ },
27
+ scheduleTitle: {
28
+ id: 'components.DashboardItemOrder.PaymentModal.scheduleTitle',
29
+ defaultMessage: 'Repayment schedule',
30
+ description: 'Title of the payment schedule',
31
+ },
32
+ paymentMethodTitle: {
33
+ id: 'components.DashboardItemOrder.PaymentModal.paymentMethodTitle',
34
+ defaultMessage: 'Payment method',
35
+ description: 'Title of the payment method section',
36
+ },
37
+ paymentNeededMessage: {
38
+ id: 'components.DashboardItemOrder.paymentNeededMessage',
39
+ description: 'Message displayed when payment is needed',
40
+ defaultMessage: 'The payment failed, please update your payment method',
41
+ },
42
+ paymentNeededButton: {
43
+ id: 'components.DashboardItemOrder.paymentNeededButton',
44
+ description: 'Button label for the payment needed message',
45
+ defaultMessage: 'Pay {amount}',
46
+ },
47
+ });
48
+
49
+ interface PaymentModalProps extends Pick<ModalProps, 'isOpen' | 'onClose'> {
50
+ order: Order;
51
+ }
52
+
53
+ export const OrderPaymentDetailsModal = ({ order, ...props }: PaymentModalProps) => {
54
+ const intl = useIntl();
55
+ const retryModal = useModal();
56
+ const failedInstallment = OrderHelper.getFailedInstallment(order);
57
+ return (
58
+ <>
59
+ <Modal {...props} size={ModalSize.MEDIUM} title={intl.formatMessage(messages.title)}>
60
+ <h3 className="order-payment-details__title mb-s">
61
+ <FormattedMessage {...messages.paymentMethodTitle} />
62
+ </h3>
63
+ <CreditCardSelectorWrapper selectedCreditCardId={order.credit_card_id} />
64
+ <h3 className="order-payment-details__title mb-s mt-b">
65
+ <FormattedMessage {...messages.scheduleTitle} />
66
+ </h3>
67
+ {failedInstallment && (
68
+ <Alert
69
+ className="mb-t"
70
+ type={VariantType.ERROR}
71
+ buttons={
72
+ <Button size="small" onClick={retryModal.open}>
73
+ <FormattedMessage
74
+ {...messages.paymentNeededButton}
75
+ values={{
76
+ amount: intl.formatNumber(failedInstallment.amount, {
77
+ style: 'currency',
78
+ currency: failedInstallment.currency,
79
+ }),
80
+ }}
81
+ />
82
+ </Button>
83
+ }
84
+ >
85
+ <FormattedMessage {...messages.paymentNeededMessage} />
86
+ </Alert>
87
+ )}
88
+ {order.payment_schedule && <PaymentScheduleGrid schedule={order.payment_schedule} />}
89
+ </Modal>
90
+ {failedInstallment && (
91
+ <OrderPaymentRetryModal {...retryModal} installment={failedInstallment} order={order} />
92
+ )}
93
+ </>
94
+ );
95
+ };
96
+
97
+ const CreditCardSelectorWrapper = ({
98
+ selectedCreditCardId,
99
+ }: {
100
+ selectedCreditCardId: Maybe<string>;
101
+ }) => {
102
+ const {
103
+ item: creditCard,
104
+ states: { fetching },
105
+ } = useCreditCard(selectedCreditCardId);
106
+ const [selectedCreditCard, setSelectedCreditCard] = useState<Maybe<CreditCard>>(creditCard);
107
+
108
+ useEffect(() => {
109
+ if (!selectedCreditCard && creditCard) {
110
+ setSelectedCreditCard(creditCard);
111
+ }
112
+ }, [creditCard]);
113
+
114
+ if (fetching) {
115
+ return <Loader size="small" />;
116
+ }
117
+
118
+ return (
119
+ <CreditCardSelector
120
+ creditCard={selectedCreditCard || creditCard}
121
+ setCreditCard={setSelectedCreditCard}
122
+ quickRemove={false}
123
+ allowEdit={false}
124
+ />
125
+ );
126
+ };
@@ -0,0 +1,209 @@
1
+ import {
2
+ Alert,
3
+ Button,
4
+ Modal,
5
+ ModalProps,
6
+ ModalSize,
7
+ useModals,
8
+ VariantType,
9
+ } from '@openfun/cunningham-react';
10
+ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
11
+ import { useRef, useState } from 'react';
12
+ import { CreditCard, Order, PaymentInstallment, ACTIVE_ORDER_STATES } from 'types/Joanie';
13
+ import { CreditCardSelector } from 'components/CreditCardSelector';
14
+ import { useJoanieApi } from 'contexts/JoanieApiContext';
15
+ import { Payment, PaymentErrorMessageId } from 'components/PaymentInterfaces/types';
16
+ import PaymentInterface from 'components/PaymentInterfaces';
17
+ import { PAYMENT_SETTINGS } from 'settings';
18
+ import { useOrders } from 'hooks/useOrders';
19
+ import { Spinner } from 'components/Spinner';
20
+
21
+ const messages = defineMessages({
22
+ title: {
23
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.title',
24
+ defaultMessage: 'Retry payment',
25
+ description: 'Title of the payment retry modal',
26
+ },
27
+ description: {
28
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.description',
29
+ defaultMessage:
30
+ 'The payment failed, please choose another payment method or add a new one during the payment',
31
+ description: 'Message displayed when payment is needed',
32
+ },
33
+ submit: {
34
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.submit',
35
+ defaultMessage: 'Pay {amount}',
36
+ description: 'Message displayed when payment is needed',
37
+ },
38
+ paymentInProgress: {
39
+ defaultMessage: 'Payment in progress',
40
+ description: 'Label for screen reader when a retry payment is in progress.',
41
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.paymentInProgress',
42
+ },
43
+ successTitle: {
44
+ defaultMessage: 'Payment successful',
45
+ description: 'Title of the payment success modal',
46
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.successTitle',
47
+ },
48
+ successDescription: {
49
+ defaultMessage: 'The payment was successful',
50
+ description: 'Description of the payment success modal',
51
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.successDescription',
52
+ },
53
+ errorFailedSubmitInstallmentPayment: {
54
+ defaultMessage: 'Failed to submit installment payment, please retry later.',
55
+ description: 'Error message when submitting installment payment fails',
56
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.errorFailedSubmitInstallmentPayment',
57
+ },
58
+ errorAbortingPolling: {
59
+ defaultMessage:
60
+ 'Your payment has succeeded but your order validation is taking too long, you can close this dialog and come back later.',
61
+ description: 'Error message when submitting installment payment fails',
62
+ id: 'components.DashboardItemOrder.OrderPaymentRetryModal.errorAbortingPolling',
63
+ },
64
+ });
65
+
66
+ interface Props extends Pick<ModalProps, 'isOpen' | 'onClose'> {
67
+ installment: PaymentInstallment;
68
+ order: Order;
69
+ }
70
+
71
+ enum ComponentStates {
72
+ IDLE = 'idle',
73
+ LOADING = 'loading',
74
+ ERROR = 'error',
75
+ }
76
+
77
+ export const OrderPaymentRetryModal = ({ installment, order, ...props }: Props) => {
78
+ const intl = useIntl();
79
+ const API = useJoanieApi();
80
+ const timeoutRef = useRef<NodeJS.Timeout>();
81
+ const { methods: orderMethods } = useOrders(undefined, { enabled: false });
82
+ const [payment, setPayment] = useState<Payment>();
83
+ const [state, setState] = useState<ComponentStates>(ComponentStates.IDLE);
84
+ const [error, setError] = useState<string>();
85
+ const [creditCard, setCreditCard] = useState<CreditCard>();
86
+ const modals = useModals();
87
+
88
+ const pay = async () => {
89
+ setState(ComponentStates.LOADING);
90
+ try {
91
+ const paymentResponse = await API.user.orders.submit_installment_payment(order.id, {
92
+ credit_card_id: creditCard?.id,
93
+ });
94
+ if (paymentResponse) {
95
+ setPayment(paymentResponse);
96
+ } else {
97
+ // In case of bug.
98
+ setError(intl.formatMessage(messages.errorFailedSubmitInstallmentPayment));
99
+ setState(ComponentStates.ERROR);
100
+ }
101
+ } catch (e) {
102
+ setError(intl.formatMessage(messages.errorFailedSubmitInstallmentPayment));
103
+ setState(ComponentStates.ERROR);
104
+ }
105
+ };
106
+
107
+ const handleError = (messageId: string = PaymentErrorMessageId.ERROR_DEFAULT) => {
108
+ setState(ComponentStates.ERROR);
109
+ setError(messageId);
110
+ };
111
+
112
+ const isOrderValidated = async (id: string): Promise<Boolean> => {
113
+ const orderToCheck = await API.user.orders.get({ id });
114
+ return orderToCheck !== null && ACTIVE_ORDER_STATES.includes(orderToCheck.state);
115
+ };
116
+
117
+ const settled = async () => {
118
+ await orderMethods.invalidate();
119
+ props.onClose();
120
+ await modals.messageModal({
121
+ messageType: VariantType.SUCCESS,
122
+ title: intl.formatMessage(messages.successTitle),
123
+ children: intl.formatMessage(messages.successDescription),
124
+ });
125
+ };
126
+
127
+ const handleSuccess = () => {
128
+ let round = 0;
129
+
130
+ const checkOrderValidity = async () => {
131
+ if (round >= PAYMENT_SETTINGS.pollLimit) {
132
+ timeoutRef.current = undefined;
133
+ setState(ComponentStates.ERROR);
134
+ setError(intl.formatMessage(messages.errorAbortingPolling));
135
+ } else {
136
+ const isValidated = await isOrderValidated(order.id);
137
+ if (isValidated) {
138
+ setState(ComponentStates.IDLE);
139
+ timeoutRef.current = undefined;
140
+ settled();
141
+ } else {
142
+ round++;
143
+ timeoutRef.current = setTimeout(checkOrderValidity, PAYMENT_SETTINGS.pollInterval);
144
+ }
145
+ }
146
+ };
147
+
148
+ checkOrderValidity();
149
+ };
150
+
151
+ return (
152
+ <>
153
+ <Modal
154
+ {...props}
155
+ size={ModalSize.MEDIUM}
156
+ title={intl.formatMessage(messages.title)}
157
+ closeOnEsc={state !== ComponentStates.LOADING}
158
+ preventClose={state === ComponentStates.LOADING}
159
+ hideCloseButton={state === ComponentStates.LOADING}
160
+ actions={
161
+ <Button
162
+ color="primary"
163
+ size="small"
164
+ fullWidth={true}
165
+ onClick={pay}
166
+ disabled={state === ComponentStates.LOADING}
167
+ data-testid="order-payment-retry-modal-submit-button"
168
+ >
169
+ {state === ComponentStates.LOADING ? (
170
+ <Spinner theme="light" aria-labelledby="payment-in-progress">
171
+ <span id="payment-in-progress">
172
+ <FormattedMessage {...messages.paymentInProgress} />
173
+ </span>
174
+ </Spinner>
175
+ ) : (
176
+ <FormattedMessage
177
+ {...messages.submit}
178
+ values={{
179
+ amount: intl.formatNumber(installment.amount, {
180
+ style: 'currency',
181
+ currency: installment.currency,
182
+ }),
183
+ }}
184
+ />
185
+ )}
186
+ </Button>
187
+ }
188
+ >
189
+ {error && (
190
+ <Alert type={VariantType.ERROR} className="mb-t">
191
+ {error}
192
+ </Alert>
193
+ )}
194
+ <Alert className="mb-b">
195
+ <FormattedMessage {...messages.description} />
196
+ </Alert>
197
+ <CreditCardSelector
198
+ creditCard={creditCard}
199
+ setCreditCard={setCreditCard}
200
+ quickRemove={state !== ComponentStates.LOADING}
201
+ allowEdit={state !== ComponentStates.LOADING}
202
+ />
203
+ </Modal>
204
+ {state === ComponentStates.LOADING && payment && (
205
+ <PaymentInterface {...payment} onError={handleError} onSuccess={handleSuccess} />
206
+ )}
207
+ </>
208
+ );
209
+ };
@@ -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 { 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';
@@ -13,86 +9,37 @@ import OrderStateLearnerMessage, { messages } from '.';
13
9
 
14
10
  const intl = createIntl({ locale: 'en' });
15
11
 
16
- describe('<DashboardItemOrder/>', () => {
12
+ describe('<OrderStateLearnerMessage/>', () => {
17
13
  it.each([
18
- [OrderState.DRAFT, 'Draft'],
19
- [OrderState.SUBMITTED, 'Submitted'],
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(<OrderStateLearnerMessage order={order} />, {
27
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
28
- });
29
- expect(screen.getByText(expectedMessage)).toBeInTheDocument();
30
- },
31
- );
32
-
33
- it.each([
34
- [OrderState.DRAFT, 'Draft'],
35
- [OrderState.SUBMITTED, 'Submitted'],
36
- [OrderState.PENDING, 'Pending'],
37
- [OrderState.CANCELED, 'Canceled'],
38
- ])(
39
- 'should display message from order state: %s when order have no contract',
40
- (state, expectedMessage) => {
41
- const orderWithContract = CredentialOrderFactory({
42
- state,
43
- contract: ContractFactory().one(),
44
- }).one();
45
- render(<OrderStateLearnerMessage order={orderWithContract} />, {
46
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
47
- });
48
- expect(screen.getByText(expectedMessage)).toBeInTheDocument();
49
- },
50
- );
51
-
52
- it('should display message for validated order that need learner signature', () => {
53
- const order = CredentialOrderFactory({
54
- state: OrderState.VALIDATED,
55
- contract: null,
56
- }).one();
57
-
58
- const contractDefinition = ContractDefinitionFactory().one();
59
-
60
- render(<OrderStateLearnerMessage order={order} contractDefinition={contractDefinition} />, {
61
- wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
62
- });
63
- expect(screen.getByText('Signature required')).toBeInTheDocument();
64
- });
65
-
66
- it("should display message for validated order that don't have a generated certificate", () => {
67
- const order = CredentialOrderFactory({
68
- state: OrderState.VALIDATED,
69
- contract: ContractFactory({ student_signed_on: new Date().toISOString() }).one(),
70
- certificate_id: undefined,
71
- }).one();
16
+ [OrderState.COMPLETED, 'On going'],
17
+ [OrderState.DRAFT, 'Pending'],
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, 'Signature required'],
23
+ [OrderState.TO_SAVE_PAYMENT_METHOD, 'Payment method is missing'],
24
+ [OrderState.TO_SIGN, 'Signature required'],
25
+ ])('should display message from order state: %s', (state, expectedMessage) => {
26
+ const order = CredentialOrderFactory({ state }).one();
72
27
  render(<OrderStateLearnerMessage order={order} />, {
73
28
  wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
74
29
  });
75
- expect(
76
- screen.getByText(intl.formatMessage(messages.statusOnGoing), {
77
- exact: false,
78
- }),
79
- );
30
+ expect(screen.getByText(expectedMessage)).toBeInTheDocument();
80
31
  });
81
32
 
82
- it('should display message for validated order that have a generated certificate', () => {
33
+ it('should display message for completed order that have a generated certificate', () => {
83
34
  const order = CredentialOrderFactory({
84
- state: OrderState.VALIDATED,
85
- contract: ContractFactory({
86
- student_signed_on: new Date().toISOString(),
87
- organization_signed_on: new Date().toISOString(),
88
- }).one(),
35
+ state: OrderState.COMPLETED,
89
36
  certificate_id: 'FAKE_CERTIFICATE_ID',
90
37
  }).one();
91
38
  render(<OrderStateLearnerMessage order={order} />, {
92
39
  wrapper: ({ children }: PropsWithChildren) => <IntlWrapper>{children}</IntlWrapper>,
93
40
  });
94
41
  expect(
95
- screen.getByText(intl.formatMessage(messages.statusCompleted), {
42
+ screen.getByText(intl.formatMessage(messages.statusPassed), {
96
43
  exact: false,
97
44
  }),
98
45
  );
@@ -1,55 +1,70 @@
1
1
  import { defineMessages } from 'react-intl';
2
- import OrderStateMessage, { OrderStateMessageBaseProps } from '../OrderStateMessage';
2
+ import OrderStateMessage, { OrderStateMessageBaseProps, MessageKeys } from '../OrderStateMessage';
3
3
 
4
- export const messages = defineMessages({
4
+ export const messages = defineMessages<MessageKeys>({
5
5
  statusDraft: {
6
- id: 'components.DashboardItem.Order.OrderStateMessage.statusDraft',
6
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusDraft',
7
7
  description: 'Status shown on the dashboard order item when order is draft.',
8
- defaultMessage: 'Draft',
8
+ defaultMessage: 'Pending',
9
9
  },
10
- statusSubmitted: {
11
- id: 'components.DashboardItem.Order.OrderStateMessage.statusSubmitted',
12
- description: 'Status shown on the dashboard order item when order is submitted.',
13
- defaultMessage: 'Submitted',
10
+ statusAssigned: {
11
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusAssigned',
12
+ description: 'Status shown on the dashboard order item when order is assigned.',
13
+ defaultMessage: 'Pending',
14
14
  },
15
15
  statusPending: {
16
- id: 'components.DashboardItem.Order.OrderStateMessage.statusPending',
16
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.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.OrderStateMessage.statusOnGoing',
22
- description:
23
- 'Status shown on the dashboard order item when order is validated with no certificate',
20
+ statusPendingPayment: {
21
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusPendingPayment',
22
+ description: 'Status shown on the dashboard order item when order is pending for payment',
24
23
  defaultMessage: 'On going',
25
24
  },
26
25
  statusCompleted: {
27
- id: 'components.DashboardItem.Order.OrderStateMessage.statusCompleted',
28
- description:
29
- 'Status shown on the dashboard order item when order is validated with certificate',
30
- defaultMessage: 'Completed',
26
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusCompleted',
27
+ description: 'Status shown on the dashboard order item when order is completed',
28
+ defaultMessage: 'On going',
31
29
  },
32
30
  statusWaitingSignature: {
33
- id: 'components.DashboardItem.Order.OrderStateMessage.statusWaitingSignature',
31
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusWaitingSignature',
34
32
  description:
35
33
  "Status shown on the dashboard order item when order is validated with contract's learner signature missing.",
36
34
  defaultMessage: 'Signature required',
37
35
  },
38
36
  statusWaitingCounterSignature: {
39
- id: 'components.DashboardItem.Order.OrderStateMessage.statusWaitingCounterSignature',
37
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusWaitingCounterSignature',
40
38
  description:
41
39
  "Status shown on the dashboard order item when order is validated with contract's organization signature missing.",
42
40
  defaultMessage: 'On going',
43
41
  },
42
+ statusWaitingPaymentMethod: {
43
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusWaitingPaymentMethod',
44
+ description:
45
+ 'Status shown on the dashboard order item when order is in to_save_payment_method state.',
46
+ defaultMessage: 'Payment method is missing',
47
+ },
44
48
  statusCanceled: {
45
- id: 'components.DashboardItem.Order.OrderStateMessage.statusCanceled',
49
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusCanceled',
46
50
  description: 'Status shown on the dashboard order item when order is canceled',
47
51
  defaultMessage: 'Canceled',
48
52
  },
49
- statusOther: {
50
- id: 'components.DashboardItem.Order.OrderStateMessage.statusOther',
51
- description: 'Status shown on the dashboard order item when order status is unknown',
52
- defaultMessage: '{state}',
53
+ statusNoPayment: {
54
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusNoPayment',
55
+ description: 'Status shown on the dashboard order item when order is in no payment state',
56
+ defaultMessage: 'First direct debit has failed',
57
+ },
58
+ statusFailedPayment: {
59
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusFailedPayment',
60
+ description: 'Status shown on the dashboard order item when order is in failed payment state',
61
+ defaultMessage: 'Last direct debit has failed',
62
+ },
63
+ statusPassed: {
64
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusPassed',
65
+ description:
66
+ 'Status shown on the dashboard order item when order is completed and has a certificate',
67
+ defaultMessage: 'Successfully completed',
53
68
  },
54
69
  });
55
70
 
@@ -1,26 +1,33 @@
1
1
  import { FormattedMessage, MessageDescriptor } from 'react-intl';
2
2
  import { useEffect } from 'react';
3
- import {
4
- CertificateOrder,
5
- CredentialOrder,
6
- OrderState,
7
- ContractDefinition,
8
- NestedCourseOrder,
9
- } from 'types/Joanie';
3
+ import { CertificateOrder, CredentialOrder, OrderState, NestedCourseOrder } from 'types/Joanie';
10
4
  import { StringHelper } from 'utils/StringHelper';
11
5
  import { handle } from 'utils/errors/handle';
12
6
  import { OrderHelper, OrderStatus } from 'utils/OrderHelper';
13
7
 
14
8
  export interface OrderStateMessageBaseProps {
15
9
  order: CredentialOrder | CertificateOrder | NestedCourseOrder;
16
- contractDefinition?: ContractDefinition;
17
10
  }
18
11
 
12
+ export type MessageKeys =
13
+ | 'statusDraft'
14
+ | 'statusAssigned'
15
+ | 'statusPending'
16
+ | 'statusPendingPayment'
17
+ | 'statusCompleted'
18
+ | 'statusWaitingSignature'
19
+ | 'statusWaitingCounterSignature'
20
+ | 'statusWaitingPaymentMethod'
21
+ | 'statusCanceled'
22
+ | 'statusNoPayment'
23
+ | 'statusFailedPayment'
24
+ | 'statusPassed';
25
+
19
26
  interface OrderStateMessageProps extends OrderStateMessageBaseProps {
20
- messages: Record<string, MessageDescriptor>;
27
+ messages: Record<MessageKeys, MessageDescriptor>;
21
28
  }
22
29
 
23
- const OrderStateMessage = ({ order, contractDefinition, messages }: OrderStateMessageProps) => {
30
+ const OrderStateMessage = ({ order, messages }: OrderStateMessageProps) => {
24
31
  useEffect(() => {
25
32
  if (!Object.values(OrderState).includes(order.state)) {
26
33
  handle(new Error(`Unknown order state ${order.state}`));
@@ -28,24 +35,23 @@ const OrderStateMessage = ({ order, contractDefinition, messages }: OrderStateMe
28
35
  }, [order.state]);
29
36
 
30
37
  const orderStatusMessagesMap = {
38
+ [OrderStatus.ASSIGNED]: messages.statusAssigned,
39
+ [OrderStatus.CANCELED]: messages.statusCanceled,
31
40
  [OrderStatus.DRAFT]: messages.statusDraft,
32
- [OrderStatus.SUBMITTED]: messages.statusSubmitted,
41
+ [OrderStatus.COMPLETED]: messages.statusCompleted,
42
+ [OrderStatus.FAILED_PAYMENT]: messages.statusFailedPayment,
43
+ [OrderStatus.NO_PAYMENT]: messages.statusNoPayment,
44
+ [OrderStatus.PASSED]: messages.statusPassed,
33
45
  [OrderStatus.PENDING]: messages.statusPending,
34
- [OrderStatus.CANCELED]: messages.statusCanceled,
35
- [OrderStatus.WAITING_SIGNATURE]: messages.statusWaitingSignature,
46
+ [OrderStatus.PENDING_PAYMENT]: messages.statusPendingPayment,
36
47
  [OrderStatus.WAITING_COUNTER_SIGNATURE]: messages.statusWaitingCounterSignature,
37
- [OrderStatus.COMPLETED]: messages.statusCompleted,
38
- [OrderStatus.ON_GOING]: messages.statusOnGoing,
48
+ [OrderStatus.WAITING_PAYMENT_METHOD]: messages.statusWaitingPaymentMethod,
49
+ [OrderStatus.WAITING_SIGNATURE]: messages.statusWaitingSignature,
39
50
  };
40
- const status = OrderHelper.getState(order, contractDefinition);
51
+ const status = OrderHelper.getState(order);
41
52
 
42
53
  if (status === null) {
43
- return (
44
- <FormattedMessage
45
- {...messages.statusOther}
46
- values={{ state: StringHelper.capitalizeFirst(order.state) }}
47
- />
48
- );
54
+ return StringHelper.capitalizeFirst(order.state);
49
55
  }
50
56
 
51
57
  return <FormattedMessage {...orderStatusMessagesMap[status]} />;