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.
- package/js/api/joanie.ts +42 -17
- package/js/api/lms/dummy.ts +1 -12
- package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +16 -9
- package/js/components/ContractFrame/AbstractContractFrame.tsx +28 -23
- package/js/components/ContractFrame/LearnerContractFrame.tsx +2 -2
- package/js/components/ContractFrame/_styles.scss +6 -14
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.spec.tsx +15 -45
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.tsx +17 -24
- package/js/components/DownloadContractButton/index.spec.tsx +1 -1
- package/js/components/OpenEdxFullNameForm/index.spec.tsx +229 -0
- package/js/components/OpenEdxFullNameForm/index.tsx +7 -7
- package/js/components/PaymentInterfaces/LyraPopIn.tsx +2 -2
- package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
- package/js/components/PaymentInterfaces/__mocks__/index.tsx +1 -4
- package/js/components/PaymentInterfaces/types.ts +5 -2
- package/js/components/PaymentScheduleGrid/_styles.scss +13 -0
- package/js/components/PaymentScheduleGrid/index.tsx +50 -70
- package/js/components/PurchaseButton/index.spec.tsx +84 -37
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -1
- package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
- package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +80 -27
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +16 -20
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/_styles.scss +12 -0
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +160 -0
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +15 -29
- package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.scss +4 -5
- package/js/components/SaleTunnel/Sponsors/SaleTunnelSponsors.tsx +39 -11
- package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
- package/js/components/SaleTunnel/_styles.scss +16 -5
- package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
- package/js/components/SaleTunnel/index.credential.spec.tsx +14 -25
- package/js/components/SaleTunnel/index.full-process.spec.tsx +116 -48
- package/js/components/SaleTunnel/index.spec.tsx +334 -717
- package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +16 -11
- package/js/components/SignContractButton/index.spec.tsx +16 -20
- package/js/components/SignContractButton/index.tsx +3 -1
- package/js/hooks/useCreditCards/index.spec.tsx +70 -6
- package/js/hooks/useCreditCards/index.ts +49 -11
- package/js/hooks/useOrders/index.spec.tsx +322 -0
- package/js/hooks/{useOrders.ts → useOrders/index.ts} +40 -14
- package/js/hooks/usePaymentSchedule.tsx +23 -0
- package/js/hooks/useProductOrder/index.spec.tsx +77 -60
- package/js/hooks/useProductOrder/index.tsx +2 -2
- package/js/hooks/useResources/useResourcesRoot.ts +4 -3
- package/js/index.tsx +2 -0
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.spec.tsx +1 -1
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.tsx +4 -2
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +8 -5
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +8 -9
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.spec.tsx +1 -1
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +1 -6
- package/js/settings/settings.test.ts +11 -2
- package/js/types/Joanie.ts +77 -31
- package/js/utils/OrderHelper/index.ts +47 -38
- package/js/utils/test/factories/joanie.ts +66 -68
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +26 -32
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +11 -6
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +114 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +99 -12
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +3 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +6 -7
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +7 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +126 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +209 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +40 -25
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +28 -22
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +18 -73
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +32 -16
- package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +3 -11
- package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +25 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +7 -14
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.spec.tsx +7 -5
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.tsx +5 -7
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +242 -332
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +12 -13
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +10 -21
- package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +2 -2
- package/package.json +2 -1
- package/scss/components/_index.scss +4 -2
- package/js/components/PaymentButton/_styles.scss +0 -27
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -338
- package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
- /package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/_styles.scss +0 -0
package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx
ADDED
|
@@ -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
|
+
};
|
package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx
CHANGED
|
@@ -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('<
|
|
12
|
+
describe('<OrderStateLearnerMessage/>', () => {
|
|
17
13
|
it.each([
|
|
18
|
-
[OrderState.
|
|
19
|
-
[OrderState.SUBMITTED, 'Submitted'],
|
|
20
|
-
[OrderState.PENDING, 'Pending'],
|
|
14
|
+
[OrderState.ASSIGNED, 'Pending'],
|
|
21
15
|
[OrderState.CANCELED, 'Canceled'],
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
33
|
+
it('should display message for completed order that have a generated certificate', () => {
|
|
83
34
|
const order = CredentialOrderFactory({
|
|
84
|
-
state: OrderState.
|
|
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.
|
|
42
|
+
screen.getByText(intl.formatMessage(messages.statusPassed), {
|
|
96
43
|
exact: false,
|
|
97
44
|
}),
|
|
98
45
|
);
|
package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx
CHANGED
|
@@ -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.
|
|
6
|
+
id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusDraft',
|
|
7
7
|
description: 'Status shown on the dashboard order item when order is draft.',
|
|
8
|
-
defaultMessage: '
|
|
8
|
+
defaultMessage: 'Pending',
|
|
9
9
|
},
|
|
10
|
-
|
|
11
|
-
id: 'components.DashboardItem.Order.
|
|
12
|
-
description: 'Status shown on the dashboard order item when order is
|
|
13
|
-
defaultMessage: '
|
|
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.
|
|
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
|
-
|
|
21
|
-
id: 'components.DashboardItem.Order.
|
|
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.
|
|
28
|
-
description:
|
|
29
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
50
|
-
id: 'components.DashboardItem.Order.
|
|
51
|
-
description: 'Status shown on the dashboard order item when order
|
|
52
|
-
defaultMessage: '
|
|
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<
|
|
27
|
+
messages: Record<MessageKeys, MessageDescriptor>;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const OrderStateMessage = ({ order,
|
|
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.
|
|
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.
|
|
35
|
-
[OrderStatus.WAITING_SIGNATURE]: messages.statusWaitingSignature,
|
|
46
|
+
[OrderStatus.PENDING_PAYMENT]: messages.statusPendingPayment,
|
|
36
47
|
[OrderStatus.WAITING_COUNTER_SIGNATURE]: messages.statusWaitingCounterSignature,
|
|
37
|
-
[OrderStatus.
|
|
38
|
-
[OrderStatus.
|
|
48
|
+
[OrderStatus.WAITING_PAYMENT_METHOD]: messages.statusWaitingPaymentMethod,
|
|
49
|
+
[OrderStatus.WAITING_SIGNATURE]: messages.statusWaitingSignature,
|
|
39
50
|
};
|
|
40
|
-
const status = OrderHelper.getState(order
|
|
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]} />;
|