richie-education 2.28.2-dev39 → 2.28.2-dev53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/api/joanie.ts +12 -16
- 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/CreditCardSelector/index.spec.tsx +7 -7
- package/js/components/CreditCardSelector/index.tsx +2 -2
- 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/PurchaseButton/index.spec.tsx +69 -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 +75 -41
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +0 -30
- 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.tsx +5 -0
- package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +7 -0
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +201 -0
- package/js/components/SaleTunnel/_styles.scss +10 -1
- package/js/components/SaleTunnel/hooks/useTerms.tsx +0 -77
- package/js/components/SaleTunnel/index.credential.spec.tsx +12 -21
- package/js/components/SaleTunnel/index.full-process.spec.tsx +110 -48
- package/js/components/SaleTunnel/index.spec.tsx +330 -779
- 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/useProductOrder/index.spec.tsx +77 -60
- package/js/hooks/useProductOrder/index.tsx +2 -2
- package/js/hooks/useResources/useResourcesRoot.ts +1 -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 +49 -34
- package/js/utils/OrderHelper/index.ts +38 -42
- package/js/utils/test/factories/joanie.ts +36 -51
- 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 +7 -6
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +9 -10
- 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/index.tsx +28 -8
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +2 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +18 -71
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +34 -35
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +27 -24
- 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 +1 -1
- package/scss/components/_index.scss +2 -1
- package/js/components/PaymentButton/_styles.scss +0 -27
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -333
- package/js/components/SaleTunnel/SaleTunnelNotValidated/index.tsx +0 -70
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +0 -41
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Modal, ModalSize } from '@openfun/cunningham-react';
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
useCallback,
|
|
10
|
+
} from 'react';
|
|
4
11
|
import { SaleTunnelSponsors } from 'components/SaleTunnel/Sponsors/SaleTunnelSponsors';
|
|
5
12
|
import { SaleTunnelProps } from 'components/SaleTunnel/index';
|
|
6
|
-
import { Address, CreditCard, Order, Product } from 'types/Joanie';
|
|
13
|
+
import { Address, CreditCard, Order, OrderState, Product } from 'types/Joanie';
|
|
7
14
|
import useProductOrder from 'hooks/useProductOrder';
|
|
8
15
|
import { SaleTunnelSuccess } from 'components/SaleTunnel/SaleTunnelSuccess';
|
|
9
16
|
import WebAnalyticsAPIHandler from 'api/web-analytics';
|
|
@@ -11,16 +18,8 @@ import { CourseProductEvent } from 'types/web-analytics';
|
|
|
11
18
|
import { useOmniscientOrders, useOrders } from 'hooks/useOrders';
|
|
12
19
|
import { SaleTunnelInformation } from 'components/SaleTunnel/SaleTunnelInformation';
|
|
13
20
|
import { useEnrollments } from 'hooks/useEnrollments';
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
const messages = defineMessages({
|
|
17
|
-
walkthrough: {
|
|
18
|
-
id: 'components.SaleTunnel.GenericSaleTunnel.walkthrough',
|
|
19
|
-
defaultMessage:
|
|
20
|
-
'To enroll in the training, you will first be invited to sign the training agreement and then to define a payment method. You will not be charged during this step; the first payment will take place on the date mentioned in the payment schedule above.',
|
|
21
|
-
description: 'Message explaining the credential sale tunnel process',
|
|
22
|
-
},
|
|
23
|
-
});
|
|
21
|
+
import SaleTunnelSavePaymentMethod from 'components/SaleTunnel/SaleTunnelSavePaymentMethod';
|
|
22
|
+
import { LearnerContractFrame } from 'components/ContractFrame';
|
|
24
23
|
|
|
25
24
|
export interface SaleTunnelContextType {
|
|
26
25
|
props: SaleTunnelProps;
|
|
@@ -29,7 +28,7 @@ export interface SaleTunnelContextType {
|
|
|
29
28
|
webAnalyticsEventKey: string;
|
|
30
29
|
|
|
31
30
|
// internal
|
|
32
|
-
onPaymentSuccess: (
|
|
31
|
+
onPaymentSuccess: () => void;
|
|
33
32
|
step: SaleTunnelStep;
|
|
34
33
|
|
|
35
34
|
// meta
|
|
@@ -40,6 +39,7 @@ export interface SaleTunnelContextType {
|
|
|
40
39
|
registerSubmitCallback: (key: string, callback: () => Promise<void>) => void;
|
|
41
40
|
unregisterSubmitCallback: (key: string) => void;
|
|
42
41
|
runSubmitCallbacks: () => Promise<void>;
|
|
42
|
+
nextStep: () => void;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const SaleTunnelContext = createContext<SaleTunnelContextType>({} as any);
|
|
@@ -55,9 +55,10 @@ export const useSaleTunnelContext = () => {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
export enum SaleTunnelStep {
|
|
58
|
-
|
|
58
|
+
IDLE,
|
|
59
|
+
SIGN,
|
|
60
|
+
SAVE_PAYMENT,
|
|
59
61
|
SUCCESS,
|
|
60
|
-
NOT_VALIDATED,
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
interface GenericSaleTunnelProps extends SaleTunnelProps {
|
|
@@ -86,11 +87,35 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
86
87
|
} = useEnrollments(undefined, { enabled: false });
|
|
87
88
|
const [billingAddress, setBillingAddress] = useState<Address>();
|
|
88
89
|
const [creditCard, setCreditCard] = useState<CreditCard>();
|
|
89
|
-
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.
|
|
90
|
+
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.IDLE);
|
|
90
91
|
const [submitCallbacks, setSubmitCallbacks] = useState<Map<string, () => Promise<void>>>(
|
|
91
92
|
new Map(),
|
|
92
93
|
);
|
|
93
94
|
|
|
95
|
+
const nextStep = useCallback(() => {
|
|
96
|
+
if (order)
|
|
97
|
+
switch (step) {
|
|
98
|
+
case SaleTunnelStep.IDLE:
|
|
99
|
+
if ([OrderState.TO_SIGN, OrderState.SIGNING].includes(order.state)) {
|
|
100
|
+
setStep(SaleTunnelStep.SIGN);
|
|
101
|
+
} else if (order.state === OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
102
|
+
setStep(SaleTunnelStep.SAVE_PAYMENT);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case SaleTunnelStep.SIGN:
|
|
106
|
+
if (order.state === OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
107
|
+
setStep(SaleTunnelStep.SAVE_PAYMENT);
|
|
108
|
+
}
|
|
109
|
+
if (order.state === OrderState.COMPLETED) {
|
|
110
|
+
setStep(SaleTunnelStep.SUCCESS);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case SaleTunnelStep.SAVE_PAYMENT:
|
|
114
|
+
setStep(SaleTunnelStep.SUCCESS);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}, [order, step]);
|
|
118
|
+
|
|
94
119
|
const context: SaleTunnelContextType = useMemo(
|
|
95
120
|
() => ({
|
|
96
121
|
webAnalyticsEventKey: props.eventKey,
|
|
@@ -101,12 +126,9 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
101
126
|
setBillingAddress,
|
|
102
127
|
creditCard,
|
|
103
128
|
setCreditCard,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} else {
|
|
108
|
-
setStep(SaleTunnelStep.NOT_VALIDATED);
|
|
109
|
-
}
|
|
129
|
+
nextStep,
|
|
130
|
+
onPaymentSuccess: () => {
|
|
131
|
+
nextStep();
|
|
110
132
|
WebAnalyticsAPIHandler()?.sendCourseProductEvent(
|
|
111
133
|
CourseProductEvent.PAYMENT_SUCCEED,
|
|
112
134
|
props.eventKey,
|
|
@@ -154,13 +176,16 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
154
176
|
|
|
155
177
|
export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
|
|
156
178
|
const { step } = useSaleTunnelContext();
|
|
179
|
+
|
|
157
180
|
switch (step) {
|
|
158
|
-
case SaleTunnelStep.
|
|
159
|
-
return <
|
|
181
|
+
case SaleTunnelStep.IDLE:
|
|
182
|
+
return <GenericSaleTunnelInitialStep {...props} />;
|
|
183
|
+
case SaleTunnelStep.SIGN:
|
|
184
|
+
return <GenericSaleTunnelSignStep {...props} />;
|
|
185
|
+
case SaleTunnelStep.SAVE_PAYMENT:
|
|
186
|
+
return <GenericSaleTunnelSavePaymentMethodStep {...props} />;
|
|
160
187
|
case SaleTunnelStep.SUCCESS:
|
|
161
188
|
return <GenericSaleTunnelSuccessStep {...props} />;
|
|
162
|
-
case SaleTunnelStep.NOT_VALIDATED:
|
|
163
|
-
return <GenericSaleTunnelNotValidatedStep {...props} />;
|
|
164
189
|
}
|
|
165
190
|
throw new Error('Invalid step: ' + step);
|
|
166
191
|
};
|
|
@@ -169,7 +194,7 @@ export const GenericSaleTunnelInner = (props: GenericSaleTunnelProps) => {
|
|
|
169
194
|
* Steps.
|
|
170
195
|
*/
|
|
171
196
|
|
|
172
|
-
export const
|
|
197
|
+
export const GenericSaleTunnelInitialStep = (props: GenericSaleTunnelProps) => {
|
|
173
198
|
const { webAnalyticsEventKey } = useSaleTunnelContext();
|
|
174
199
|
|
|
175
200
|
useEffect(() => {
|
|
@@ -184,7 +209,7 @@ export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
|
|
|
184
209
|
}, []);
|
|
185
210
|
|
|
186
211
|
return (
|
|
187
|
-
<Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title}
|
|
212
|
+
<Modal {...props} size={ModalSize.EXTRA_LARGE} title={props.product.title}>
|
|
188
213
|
<div className="sale-tunnel" data-testid="generic-sale-tunnel-payment-step">
|
|
189
214
|
<div className="sale-tunnel__main">
|
|
190
215
|
<div className="sale-tunnel__main__column sale-tunnel__main__left ">
|
|
@@ -198,14 +223,7 @@ export const GenericSaleTunnelPaymentStep = (props: GenericSaleTunnelProps) => {
|
|
|
198
223
|
<SaleTunnelInformation />
|
|
199
224
|
</div>
|
|
200
225
|
</div>
|
|
201
|
-
<div className="sale-tunnel__footer">
|
|
202
|
-
<div style={{ maxWidth: '680px' }} className="mb-s" data-testid="walkthrough-banner">
|
|
203
|
-
<Alert type={VariantType.INFO}>
|
|
204
|
-
<FormattedMessage {...messages.walkthrough} />
|
|
205
|
-
</Alert>
|
|
206
|
-
</div>
|
|
207
|
-
{props.paymentNode}
|
|
208
|
-
</div>
|
|
226
|
+
<div className="sale-tunnel__footer">{props.paymentNode}</div>
|
|
209
227
|
</div>
|
|
210
228
|
</Modal>
|
|
211
229
|
);
|
|
@@ -219,10 +237,26 @@ export const GenericSaleTunnelSuccessStep = (props: SaleTunnelProps) => {
|
|
|
219
237
|
);
|
|
220
238
|
};
|
|
221
239
|
|
|
222
|
-
export const
|
|
240
|
+
export const GenericSaleTunnelSavePaymentMethodStep = (props: SaleTunnelProps) => {
|
|
223
241
|
return (
|
|
224
|
-
<Modal {...props} size={ModalSize.
|
|
225
|
-
<
|
|
242
|
+
<Modal {...props} size={ModalSize.LARGE}>
|
|
243
|
+
<SaleTunnelSavePaymentMethod />
|
|
226
244
|
</Modal>
|
|
227
245
|
);
|
|
228
246
|
};
|
|
247
|
+
|
|
248
|
+
export const GenericSaleTunnelSignStep = ({ isOpen, onClose }: SaleTunnelProps) => {
|
|
249
|
+
const { order, nextStep } = useSaleTunnelContext();
|
|
250
|
+
|
|
251
|
+
const handleDone = useCallback(nextStep, [order]);
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (![OrderState.TO_SIGN, OrderState.SIGNING].includes(order!.state)) {
|
|
255
|
+
nextStep();
|
|
256
|
+
}
|
|
257
|
+
}, [order]);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<LearnerContractFrame order={order!} isOpen={isOpen} onClose={onClose} onDone={handleDone} />
|
|
261
|
+
);
|
|
262
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineMessages, FormattedMessage, FormattedNumber } from 'react-intl';
|
|
2
2
|
import { AddressSelector } from 'components/SaleTunnel/AddressSelector';
|
|
3
|
-
import { CreditCardSelector } from 'components/CreditCardSelector';
|
|
4
3
|
import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
|
|
5
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
6
5
|
import OpenEdxFullNameForm from 'components/OpenEdxFullNameForm';
|
|
@@ -25,16 +24,6 @@ const messages = defineMessages({
|
|
|
25
24
|
description: 'Label for the full name input',
|
|
26
25
|
defaultMessage: 'Full name',
|
|
27
26
|
},
|
|
28
|
-
paymentMethodTitle: {
|
|
29
|
-
id: 'components.SaleTunnel.CreditCardSelector.title',
|
|
30
|
-
description: 'Title for the credit card section',
|
|
31
|
-
defaultMessage: 'Payment method',
|
|
32
|
-
},
|
|
33
|
-
paymentMethodDescription: {
|
|
34
|
-
id: 'components.SaleTunnel.CreditCardSelector.description',
|
|
35
|
-
description: 'Description for the credit card section',
|
|
36
|
-
defaultMessage: 'Choose your payment method or add a new one during the payment.',
|
|
37
|
-
},
|
|
38
27
|
totalInfo: {
|
|
39
28
|
id: 'components.SaleTunnel.Information.total.info',
|
|
40
29
|
description: 'Information about the total amount',
|
|
@@ -74,9 +63,6 @@ export const SaleTunnelInformation = () => {
|
|
|
74
63
|
<Email />
|
|
75
64
|
</div>
|
|
76
65
|
</div>
|
|
77
|
-
<div>
|
|
78
|
-
<CreditCardSelectorWrapper />
|
|
79
|
-
</div>
|
|
80
66
|
<div>
|
|
81
67
|
<PaymentScheduleBlock />
|
|
82
68
|
<Total />
|
|
@@ -84,22 +70,6 @@ export const SaleTunnelInformation = () => {
|
|
|
84
70
|
</div>
|
|
85
71
|
);
|
|
86
72
|
};
|
|
87
|
-
|
|
88
|
-
const CreditCardSelectorWrapper = () => {
|
|
89
|
-
const { creditCard, setCreditCard } = useSaleTunnelContext();
|
|
90
|
-
return (
|
|
91
|
-
<>
|
|
92
|
-
<h4 className="block-title mb-t">
|
|
93
|
-
<FormattedMessage {...messages.paymentMethodTitle} />
|
|
94
|
-
</h4>
|
|
95
|
-
<div className="description mb-s">
|
|
96
|
-
<FormattedMessage {...messages.paymentMethodDescription} />
|
|
97
|
-
</div>
|
|
98
|
-
<CreditCardSelector creditCard={creditCard} setCreditCard={setCreditCard} />
|
|
99
|
-
</>
|
|
100
|
-
);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
73
|
const Email = () => {
|
|
104
74
|
const { user } = useSession();
|
|
105
75
|
const { data: openEdxProfileData } = useOpenEdxProfile({
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
2
|
+
import { Button } from '@openfun/cunningham-react';
|
|
3
|
+
import { useEffect, useState, useRef } from 'react';
|
|
4
|
+
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
5
|
+
import { CreditCard, OrderState } from 'types/Joanie';
|
|
6
|
+
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
7
|
+
import { Payment, PaymentErrorMessageId } from 'components/PaymentInterfaces/types';
|
|
8
|
+
import { useCreditCards } from 'hooks/useCreditCards';
|
|
9
|
+
import { Spinner } from 'components/Spinner';
|
|
10
|
+
import PaymentInterfaces from 'components/PaymentInterfaces';
|
|
11
|
+
import { useOrders } from 'hooks/useOrders';
|
|
12
|
+
import { CreditCardSelector } from 'components/CreditCardSelector';
|
|
13
|
+
import { useJoanieApi } from 'contexts/JoanieApiContext';
|
|
14
|
+
|
|
15
|
+
const messages = defineMessages({
|
|
16
|
+
title: {
|
|
17
|
+
id: 'components.SaleTunnelSavePaymentMethod.title',
|
|
18
|
+
defaultMessage: 'Define a payment method',
|
|
19
|
+
description: 'Content title',
|
|
20
|
+
},
|
|
21
|
+
description: {
|
|
22
|
+
defaultMessage:
|
|
23
|
+
'This is the last step to validate your subscription, you must define a payment method. This one will be used to debit installments. You will not be charged during this step. Pick an existing payment method or add a new one.',
|
|
24
|
+
description: "Text to explain what the user has to do in the 'save payment method' step",
|
|
25
|
+
id: 'components.SaleTunnelSavePaymentMethod.description',
|
|
26
|
+
},
|
|
27
|
+
cta: {
|
|
28
|
+
defaultMessage: 'Define',
|
|
29
|
+
description: 'Label to the call to action to close sale tunnel',
|
|
30
|
+
id: 'components.SaleTunnelSavePaymentMethod.cta',
|
|
31
|
+
},
|
|
32
|
+
errorAbort: {
|
|
33
|
+
defaultMessage: 'You have aborted the payment.',
|
|
34
|
+
description: 'Error message shown when user aborts the payment.',
|
|
35
|
+
id: 'components.PaymentButton.errorAbort',
|
|
36
|
+
},
|
|
37
|
+
errorDefault: {
|
|
38
|
+
defaultMessage: 'An error occurred during payment. Please retry later.',
|
|
39
|
+
description: 'Error message shown when payment creation request failed.',
|
|
40
|
+
id: 'components.PaymentButton.errorDefault',
|
|
41
|
+
},
|
|
42
|
+
tokenizingPayment: {
|
|
43
|
+
defaultMessage: 'Payment method definition in progress.',
|
|
44
|
+
description: 'Label for screen reader when a credit card is being tokenized.',
|
|
45
|
+
id: 'components.PaymentButton.tokenizingPayment',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const SaleTunnelSavePaymentMethod = () => {
|
|
50
|
+
const initialCreditCards = useRef<CreditCard[]>();
|
|
51
|
+
const pollingTimeoutRef = useRef<NodeJS.Timeout>();
|
|
52
|
+
const JoanieApi = useJoanieApi();
|
|
53
|
+
const [payment, setPayment] = useState<Payment>();
|
|
54
|
+
const [error, setError] = useState<string>();
|
|
55
|
+
const creditCards = useCreditCards();
|
|
56
|
+
const orders = useOrders(undefined, { enabled: false });
|
|
57
|
+
const { order, nextStep, creditCard, setCreditCard } = useSaleTunnelContext();
|
|
58
|
+
|
|
59
|
+
const setPaymentMethod = async (creditCardId: string) => {
|
|
60
|
+
orders.methods.set_payment_method(
|
|
61
|
+
{ id: order!.id, credit_card_id: creditCardId },
|
|
62
|
+
{ onError: () => handleError() },
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const tokenizePaymentMethod = async () => {
|
|
67
|
+
const data = await creditCards.methods.tokenize();
|
|
68
|
+
setPayment(data);
|
|
69
|
+
setError(undefined);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const waitForNewCreditCard = async () => {
|
|
73
|
+
const { results } = await JoanieApi.user.creditCards.get();
|
|
74
|
+
const initialIds = initialCreditCards.current!.map((cc) => cc.id);
|
|
75
|
+
const newCard = results.find((cc) => !initialIds.includes(cc.id));
|
|
76
|
+
|
|
77
|
+
if (!newCard) {
|
|
78
|
+
pollingTimeoutRef.current = setTimeout(waitForNewCreditCard, 1000);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setCreditCard(newCard);
|
|
83
|
+
await setPaymentMethod(newCard.id);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleError = (message: string = PaymentErrorMessageId.ERROR_DEFAULT) => {
|
|
87
|
+
setError(message);
|
|
88
|
+
setPayment(undefined);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!payment) {
|
|
93
|
+
initialCreditCards.current = creditCards.items;
|
|
94
|
+
}
|
|
95
|
+
}, [creditCards]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (order?.state !== OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
99
|
+
nextStep();
|
|
100
|
+
}
|
|
101
|
+
}, [order]);
|
|
102
|
+
|
|
103
|
+
useEffect(
|
|
104
|
+
() => () => {
|
|
105
|
+
clearTimeout(pollingTimeoutRef.current);
|
|
106
|
+
},
|
|
107
|
+
[],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<section
|
|
112
|
+
className="sale-tunnel-step sale-tunnel-step--save-payment-method"
|
|
113
|
+
data-testid="generic-sale-tunnel-save-payment-method-step"
|
|
114
|
+
>
|
|
115
|
+
<header className="sale-tunnel-step__header">
|
|
116
|
+
<Icon name={IconTypeEnum.CREDIT_CARD} />
|
|
117
|
+
<h3 className="sale-tunnel-step__title">
|
|
118
|
+
<FormattedMessage {...messages.title} />
|
|
119
|
+
</h3>
|
|
120
|
+
</header>
|
|
121
|
+
<div className="sale-tunnel-step__content">
|
|
122
|
+
<p className="mb-s">
|
|
123
|
+
<FormattedMessage {...messages.description} />
|
|
124
|
+
</p>
|
|
125
|
+
<CreditCardSelector creditCard={creditCard} setCreditCard={setCreditCard} />
|
|
126
|
+
</div>
|
|
127
|
+
<footer className="sale-tunnel-step__footer">
|
|
128
|
+
<Button
|
|
129
|
+
size="medium"
|
|
130
|
+
disabled={!!payment || orders.states.settingPaymentMethod}
|
|
131
|
+
onClick={creditCard ? () => setPaymentMethod(creditCard.id) : tokenizePaymentMethod}
|
|
132
|
+
>
|
|
133
|
+
{payment || orders.states.settingPaymentMethod ? (
|
|
134
|
+
<Spinner size="small">
|
|
135
|
+
<span id="tokenizing-payment">
|
|
136
|
+
<FormattedMessage {...messages.tokenizingPayment} />
|
|
137
|
+
</span>
|
|
138
|
+
</Spinner>
|
|
139
|
+
) : (
|
|
140
|
+
<FormattedMessage {...messages.cta} />
|
|
141
|
+
)}
|
|
142
|
+
</Button>
|
|
143
|
+
{error && (
|
|
144
|
+
<p className="payment-button__error">
|
|
145
|
+
{messages.hasOwnProperty(error) ? (
|
|
146
|
+
<FormattedMessage {...messages[error as PaymentErrorMessageId]} />
|
|
147
|
+
) : (
|
|
148
|
+
error
|
|
149
|
+
)}
|
|
150
|
+
</p>
|
|
151
|
+
)}
|
|
152
|
+
{payment && (
|
|
153
|
+
<PaymentInterfaces {...payment} onSuccess={waitForNewCreditCard} onError={handleError} />
|
|
154
|
+
)}
|
|
155
|
+
</footer>
|
|
156
|
+
</section>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default SaleTunnelSavePaymentMethod;
|
|
@@ -5,72 +5,58 @@ import { SuccessIcon } from 'components/SuccessIcon';
|
|
|
5
5
|
import { getDashboardBasename } from 'widgets/Dashboard/hooks/useDashboardRouter/getDashboardBasename';
|
|
6
6
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
7
7
|
import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
|
|
8
|
+
import { ProductType } from 'types/Joanie';
|
|
8
9
|
|
|
9
10
|
const messages = defineMessages({
|
|
10
11
|
congratulations: {
|
|
11
|
-
defaultMessage: '
|
|
12
|
+
defaultMessage: 'Subscription confirmed!',
|
|
12
13
|
description: 'Text displayed to thank user for his order',
|
|
13
14
|
id: 'components.SaleTunnelSuccess.congratulations',
|
|
14
15
|
},
|
|
15
16
|
successMessage: {
|
|
16
|
-
defaultMessage: 'Your order has been successfully
|
|
17
|
+
defaultMessage: 'Your order has been successfully registered.',
|
|
17
18
|
description: 'Message to confirm that order has been created',
|
|
18
19
|
id: 'components.SaleTunnelSuccess.successMessage',
|
|
19
20
|
},
|
|
20
21
|
successDetailMessage: {
|
|
21
|
-
defaultMessage: 'You will receive your invoice by email in a few moments.',
|
|
22
|
-
description: "Text to remind that order's invoice will be send by email soon",
|
|
23
|
-
id: 'components.SaleTunnelSuccess.successDetailMessage',
|
|
24
|
-
},
|
|
25
|
-
successDetailSignatureMessage: {
|
|
26
22
|
defaultMessage:
|
|
27
|
-
'
|
|
28
|
-
description: 'Text to
|
|
29
|
-
id: 'components.SaleTunnelSuccess.
|
|
23
|
+
'You will be able to start your training once the first installment will be paid.',
|
|
24
|
+
description: 'Text to explain when the user will be able to start its training.',
|
|
25
|
+
id: 'components.SaleTunnelSuccess.successDetailMessage',
|
|
30
26
|
},
|
|
31
27
|
cta: {
|
|
32
|
-
defaultMessage: '
|
|
28
|
+
defaultMessage: 'Close',
|
|
33
29
|
description: 'Label to the call to action to close sale tunnel',
|
|
34
30
|
id: 'components.SaleTunnelSuccess.cta',
|
|
35
31
|
},
|
|
36
|
-
ctaSignature: {
|
|
37
|
-
defaultMessage: 'Sign the training contract',
|
|
38
|
-
description: 'Label to the call to action to close sale tunnel if there is a pending signature',
|
|
39
|
-
id: 'components.SaleTunnelSuccess.ctaSignature',
|
|
40
|
-
},
|
|
41
32
|
});
|
|
42
33
|
|
|
43
34
|
export const SaleTunnelSuccess = ({ closeModal }: { closeModal: () => void }) => {
|
|
44
35
|
const intl = useIntl();
|
|
45
36
|
const { order, product } = useSaleTunnelContext();
|
|
37
|
+
|
|
46
38
|
return (
|
|
47
|
-
<section className="sale-tunnel-
|
|
48
|
-
<header className="sale-tunnel-
|
|
39
|
+
<section className="sale-tunnel-step" data-testid="generic-sale-tunnel-success-step">
|
|
40
|
+
<header className="sale-tunnel-step__header">
|
|
49
41
|
<SuccessIcon />
|
|
50
|
-
<h3 className="sale-tunnel-
|
|
42
|
+
<h3 className="sale-tunnel-step__title">
|
|
51
43
|
<FormattedMessage {...messages.congratulations} />
|
|
52
44
|
</h3>
|
|
53
45
|
</header>
|
|
54
|
-
<p className="sale-tunnel-
|
|
46
|
+
<p className="sale-tunnel-step__content">
|
|
55
47
|
<FormattedMessage {...messages.successMessage} />
|
|
56
48
|
<br />
|
|
57
49
|
<FormattedMessage {...messages.successDetailMessage} />
|
|
58
|
-
{product.contract_definition && (
|
|
59
|
-
<>
|
|
60
|
-
<br />
|
|
61
|
-
<FormattedMessage {...messages.successDetailSignatureMessage} />
|
|
62
|
-
</>
|
|
63
|
-
)}
|
|
64
50
|
</p>
|
|
65
|
-
<footer className="sale-tunnel-
|
|
66
|
-
{product.
|
|
51
|
+
<footer className="sale-tunnel-step__footer">
|
|
52
|
+
{product.type === ProductType.CREDENTIAL ? (
|
|
67
53
|
<Button
|
|
68
54
|
href={
|
|
69
55
|
getDashboardBasename(intl.locale) +
|
|
70
56
|
generatePath(LearnerDashboardPaths.ORDER, { orderId: order!.id })
|
|
71
57
|
}
|
|
72
58
|
>
|
|
73
|
-
<FormattedMessage {...messages.
|
|
59
|
+
<FormattedMessage {...messages.cta} />
|
|
74
60
|
</Button>
|
|
75
61
|
) : (
|
|
76
62
|
<Button onClick={closeModal}>
|