richie-education 3.2.1-dev9 → 3.2.2-dev26
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/i18n/locales/ar-SA.json +29 -1
- package/i18n/locales/es-ES.json +29 -1
- package/i18n/locales/fa-IR.json +29 -1
- package/i18n/locales/fr-CA.json +29 -1
- package/i18n/locales/fr-FR.json +29 -1
- package/i18n/locales/ko-KR.json +29 -1
- package/i18n/locales/pt-PT.json +29 -1
- package/i18n/locales/ru-RU.json +29 -1
- package/i18n/locales/vi-VN.json +29 -1
- package/js/api/joanie.ts +144 -0
- package/js/components/PaymentInterfaces/types.ts +7 -0
- package/js/components/PaymentScheduleGrid/index.tsx +4 -2
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +9 -2
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +33 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +253 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +314 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +528 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +47 -261
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +25 -11
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +54 -6
- package/js/components/SaleTunnel/_styles.scss +55 -0
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +356 -0
- package/js/components/SaleTunnel/{index.full-process.spec.tsx → index.full-process-b2c.spec.tsx} +4 -1
- package/js/components/SaleTunnel/index.spec.tsx +130 -1
- package/js/hooks/useBatchOrder/index.tsx +36 -0
- package/js/hooks/useContractArchive/index.ts +2 -0
- package/js/hooks/useOfferingOrganizations/index.tsx +38 -0
- package/js/hooks/useOrganizationAgreements.tsx/index.tsx +66 -0
- package/js/hooks/useOrganizationQuotes/index.tsx +56 -0
- package/js/hooks/usePaymentPlan.tsx +2 -1
- package/js/hooks/useTeacherPendingAgreementsCount/index.ts +34 -0
- package/js/pages/DashboardBatchOrderLayout/_styles.scss +5 -0
- package/js/pages/DashboardBatchOrderLayout/index.spec.tsx +78 -0
- package/js/pages/DashboardBatchOrderLayout/index.tsx +45 -0
- package/js/pages/DashboardBatchOrders/index.spec.tsx +237 -0
- package/js/pages/DashboardBatchOrders/index.tsx +84 -0
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardCourseContractsLayout/index.tsx +0 -1
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +3 -1
- package/js/pages/TeacherDashboardOrganizationAgreements/AgreementActionsBar.tsx +49 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/BulkAgreementContractButton.tsx +79 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/OrganizationAgreementFrame.tsx +71 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/SignOrganizationAgreementButton.tsx +60 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useAgreementsAbilities.tsx +8 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useHasAgreementToDownload.tsx +27 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useTeacherAgreementsToSign.tsx +32 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/index.spec.tsx +433 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/index.tsx +130 -0
- package/js/pages/TeacherDashboardOrganizationAgreementsLayout/index.tsx +25 -0
- package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +9 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/_styles.scss +40 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +194 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +144 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +521 -0
- package/js/pages/TeacherDashboardOrganizationQuotesLayout/index.tsx +26 -0
- package/js/translations/ar-SA.json +1 -1
- package/js/translations/es-ES.json +1 -1
- package/js/translations/fa-IR.json +1 -1
- package/js/translations/fr-CA.json +1 -1
- package/js/translations/fr-FR.json +1 -1
- package/js/translations/ko-KR.json +1 -1
- package/js/translations/pt-PT.json +1 -1
- package/js/translations/ru-RU.json +1 -1
- package/js/translations/vi-VN.json +1 -1
- package/js/types/Joanie.ts +216 -1
- package/js/utils/AbilitiesHelper/agreementAbilities.ts +14 -0
- package/js/utils/AbilitiesHelper/index.ts +7 -0
- package/js/utils/AbilitiesHelper/types.ts +12 -3
- package/js/utils/ObjectHelper/index.ts +20 -0
- package/js/utils/OrderHelper/index.ts +10 -0
- package/js/utils/errors/HttpError.ts +1 -0
- package/js/utils/test/factories/joanie.ts +156 -1
- package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/_styles.scss +14 -0
- package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/index.tsx +32 -0
- package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +18 -0
- package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +25 -2
- package/js/widgets/Dashboard/components/DashboardCard/index.tsx +4 -2
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +88 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/index.tsx +216 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/DashboardBatchOrderSubItems.tsx +316 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.spec.tsx +27 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.tsx +175 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +5 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +4 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +5 -0
- package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +43 -0
- package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.spec.tsx +214 -0
- package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.tsx +47 -0
- package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +1 -0
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +21 -3
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +9 -0
- package/js/widgets/Dashboard/utils/learnerRoutes.tsx +30 -0
- package/js/widgets/Dashboard/utils/learnerRoutesPaths.tsx +12 -0
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +12 -0
- package/js/widgets/Dashboard/utils/teacherRoutes.tsx +17 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +8 -2
- package/package.json +4 -1
- package/scss/colors/_theme.scss +1 -1
- package/scss/components/_index.scss +1 -0
|
@@ -1,282 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { defineMessages, FormattedMessage,
|
|
3
|
-
import {
|
|
4
|
-
import { AddressSelector } from 'components/SaleTunnel/AddressSelector';
|
|
5
|
-
import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
3
|
+
import { Select } from '@openfun/cunningham-react';
|
|
6
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import useOpenEdxProfile from 'hooks/useOpenEdxProfile';
|
|
10
|
-
import WithdrawRightCheckbox from 'components/SaleTunnel/WithdrawRightCheckbox';
|
|
11
|
-
import { PaymentSchedule, ProductType } from 'types/Joanie';
|
|
12
|
-
import { usePaymentPlan } from 'hooks/usePaymentPlan';
|
|
5
|
+
import { SaleTunnelInformationSingular } from 'components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular';
|
|
6
|
+
import { SaleTunnelInformationGroup } from 'components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup';
|
|
13
7
|
|
|
14
8
|
const messages = defineMessages({
|
|
15
|
-
|
|
16
|
-
id: 'components.SaleTunnel.Information.
|
|
17
|
-
description: 'Title for
|
|
18
|
-
defaultMessage: '
|
|
9
|
+
purchaseTypeTitle: {
|
|
10
|
+
id: 'components.SaleTunnel.Information.purchaseTypeTitle',
|
|
11
|
+
description: 'Title for purchase type',
|
|
12
|
+
defaultMessage: 'Select purchase type',
|
|
19
13
|
},
|
|
20
|
-
|
|
21
|
-
id: 'components.SaleTunnel.Information.
|
|
22
|
-
description: '
|
|
23
|
-
defaultMessage: '
|
|
14
|
+
purchaseTypeSelect: {
|
|
15
|
+
id: 'components.SaleTunnel.Information.purchaseTypeSelect',
|
|
16
|
+
description: 'Label for purchase type select',
|
|
17
|
+
defaultMessage: 'Purchase type',
|
|
24
18
|
},
|
|
25
|
-
|
|
26
|
-
id: 'components.SaleTunnel.Information.
|
|
27
|
-
description: 'Label for
|
|
28
|
-
defaultMessage: '
|
|
19
|
+
purchaseTypeOptionSingle: {
|
|
20
|
+
id: 'components.SaleTunnel.Information.purchaseTypeOptionSingle',
|
|
21
|
+
description: 'Label for B2C option',
|
|
22
|
+
defaultMessage: 'Single purchase (B2C)',
|
|
29
23
|
},
|
|
30
|
-
|
|
31
|
-
id: 'components.SaleTunnel.Information.
|
|
32
|
-
description: '
|
|
33
|
-
defaultMessage: '
|
|
34
|
-
},
|
|
35
|
-
totalInfo: {
|
|
36
|
-
id: 'components.SaleTunnel.Information.total.info',
|
|
37
|
-
description: 'Information about the total amount',
|
|
38
|
-
defaultMessage: 'You will then pay on the secured platform of our payment provider.',
|
|
39
|
-
},
|
|
40
|
-
totalLabel: {
|
|
41
|
-
id: 'components.SaleTunnel.Information.total.label',
|
|
42
|
-
description: 'Label for the total amount',
|
|
43
|
-
defaultMessage: 'Total',
|
|
44
|
-
},
|
|
45
|
-
emailLabel: {
|
|
46
|
-
id: 'components.SaleTunnel.Information.email.label',
|
|
47
|
-
description: 'Label for the email',
|
|
48
|
-
defaultMessage: 'Account email',
|
|
49
|
-
},
|
|
50
|
-
emailInfo: {
|
|
51
|
-
id: 'components.SaleTunnel.Information.email.info',
|
|
52
|
-
description: 'Info for the email',
|
|
53
|
-
defaultMessage:
|
|
54
|
-
'This email will be used to send you confirmation mails, it is the one you created your account with.',
|
|
55
|
-
},
|
|
56
|
-
voucherTitle: {
|
|
57
|
-
id: 'components.SaleTunnel.Information.voucher.title',
|
|
58
|
-
description: 'Title for the voucher',
|
|
59
|
-
defaultMessage: 'Voucher code',
|
|
60
|
-
},
|
|
61
|
-
voucherInfo: {
|
|
62
|
-
id: 'components.SaleTunnel.Information.voucher.info',
|
|
63
|
-
description: 'Info for the voucher',
|
|
64
|
-
defaultMessage: 'If you have a voucher code, please enter it in the field below.',
|
|
65
|
-
},
|
|
66
|
-
voucherValidate: {
|
|
67
|
-
id: 'components.SaleTunnel.Information.voucher.validate',
|
|
68
|
-
description: 'Validate text for the voucher',
|
|
69
|
-
defaultMessage: 'Validate',
|
|
70
|
-
},
|
|
71
|
-
voucherDelete: {
|
|
72
|
-
id: 'components.SaleTunnel.Information.voucher.delete',
|
|
73
|
-
description: 'Delete text for the voucher',
|
|
74
|
-
defaultMessage: 'Delete this voucher',
|
|
75
|
-
},
|
|
76
|
-
voucherError: {
|
|
77
|
-
id: 'components.SaleTunnel.Information.voucher.error',
|
|
78
|
-
description: 'Error when voucher is invalid',
|
|
79
|
-
defaultMessage: 'The submitted voucher code is not valid.',
|
|
80
|
-
},
|
|
81
|
-
discount: {
|
|
82
|
-
id: 'components.SaleTunnel.Information.voucher.discount',
|
|
83
|
-
description: 'Discount description',
|
|
84
|
-
defaultMessage: 'Discount applied',
|
|
24
|
+
purchaseTypeOptionGroup: {
|
|
25
|
+
id: 'components.SaleTunnel.Information.purchaseTypeOptionGroup',
|
|
26
|
+
description: 'Label for B2C option',
|
|
27
|
+
defaultMessage: 'Group purchase (B2B)',
|
|
85
28
|
},
|
|
86
29
|
});
|
|
87
30
|
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
course_code: props.course?.code ?? props.enrollment!.course_run.course.code,
|
|
93
|
-
product_id: props.product.id,
|
|
94
|
-
...(voucherCode ? { voucher_code: voucherCode } : {}),
|
|
95
|
-
});
|
|
96
|
-
const schedule = query.data?.payment_schedule ?? props.paymentPlan?.payment_schedule;
|
|
97
|
-
const price = query.data?.price ?? props.paymentPlan?.price;
|
|
98
|
-
const discountedPrice = query.data?.discounted_price ?? props.paymentPlan?.discounted_price;
|
|
99
|
-
const discount = query.data?.discount ?? props.paymentPlan?.discount;
|
|
100
|
-
|
|
101
|
-
const showPaymentSchedule =
|
|
102
|
-
product.type === ProductType.CREDENTIAL &&
|
|
103
|
-
schedule &&
|
|
104
|
-
(discountedPrice != null ? discountedPrice > 0 : price != null && price > 0);
|
|
31
|
+
export enum FormType {
|
|
32
|
+
GROUP = 'group',
|
|
33
|
+
SINGULAR = 'singular',
|
|
34
|
+
}
|
|
105
35
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
36
|
+
export const SaleTunnelInformation = () => {
|
|
37
|
+
const intl = useIntl();
|
|
38
|
+
const { setBatchOrder, setSchedule } = useSaleTunnelContext();
|
|
39
|
+
const options = [
|
|
40
|
+
{ label: intl.formatMessage(messages.purchaseTypeOptionSingle), value: FormType.SINGULAR },
|
|
41
|
+
{ label: intl.formatMessage(messages.purchaseTypeOptionGroup), value: FormType.GROUP },
|
|
42
|
+
];
|
|
43
|
+
const [purchaseType, setPurchaseType] = useState(FormType.SINGULAR);
|
|
112
44
|
|
|
113
45
|
return (
|
|
114
46
|
<div className="sale-tunnel__main__column sale-tunnel__information">
|
|
115
47
|
<div>
|
|
116
48
|
<h3 className="block-title mb-t">
|
|
117
|
-
<FormattedMessage {...messages.
|
|
49
|
+
<FormattedMessage {...messages.purchaseTypeTitle} />
|
|
118
50
|
</h3>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<Voucher
|
|
131
|
-
discount={discount}
|
|
132
|
-
voucherError={voucherError}
|
|
133
|
-
setVoucherError={setVoucherError}
|
|
51
|
+
<Select
|
|
52
|
+
label={intl.formatMessage(messages.purchaseTypeSelect)}
|
|
53
|
+
options={options}
|
|
54
|
+
fullWidth
|
|
55
|
+
value={purchaseType}
|
|
56
|
+
clearable={false}
|
|
57
|
+
onChange={(e) => {
|
|
58
|
+
setPurchaseType(e.target.value as FormType);
|
|
59
|
+
setBatchOrder(undefined);
|
|
60
|
+
setSchedule(undefined);
|
|
61
|
+
}}
|
|
134
62
|
/>
|
|
135
|
-
<Total price={price} discountedPrice={discountedPrice} />
|
|
136
|
-
<WithdrawRightCheckbox />
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const Email = () => {
|
|
143
|
-
const { user } = useSession();
|
|
144
|
-
const { data: openEdxProfileData } = useOpenEdxProfile({
|
|
145
|
-
username: user!.username,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
<div className="sale-tunnel__email">
|
|
150
|
-
<div className="sale-tunnel__email__top">
|
|
151
|
-
<h4>
|
|
152
|
-
<FormattedMessage {...messages.emailLabel} />
|
|
153
|
-
</h4>
|
|
154
|
-
<div className="fw-bold">{openEdxProfileData?.email}</div>
|
|
155
|
-
</div>
|
|
156
|
-
<div className="sale-tunnel__email__description">
|
|
157
|
-
<FormattedMessage {...messages.emailInfo} />
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const Total = ({ price, discountedPrice }: { price?: number; discountedPrice?: number }) => {
|
|
164
|
-
const { product } = useSaleTunnelContext();
|
|
165
|
-
const totalPrice = price || product.price;
|
|
166
|
-
return (
|
|
167
|
-
<div className="sale-tunnel__total">
|
|
168
|
-
<div className="sale-tunnel__total__amount mt-t" data-testid="sale-tunnel__total__amount">
|
|
169
|
-
<div className="block-title">
|
|
170
|
-
<FormattedMessage {...messages.totalLabel} />
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div className="block-title">
|
|
174
|
-
{discountedPrice !== undefined ? (
|
|
175
|
-
<>
|
|
176
|
-
<span className="price--striked">
|
|
177
|
-
<FormattedNumber
|
|
178
|
-
value={totalPrice}
|
|
179
|
-
style="currency"
|
|
180
|
-
currency={product.price_currency}
|
|
181
|
-
/>
|
|
182
|
-
</span>
|
|
183
|
-
<FormattedNumber
|
|
184
|
-
value={discountedPrice}
|
|
185
|
-
style="currency"
|
|
186
|
-
currency={product.price_currency}
|
|
187
|
-
/>
|
|
188
|
-
</>
|
|
189
|
-
) : (
|
|
190
|
-
<FormattedNumber
|
|
191
|
-
value={totalPrice}
|
|
192
|
-
style="currency"
|
|
193
|
-
currency={product.price_currency}
|
|
194
|
-
/>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const Voucher = ({
|
|
203
|
-
discount,
|
|
204
|
-
voucherError,
|
|
205
|
-
setVoucherError,
|
|
206
|
-
}: {
|
|
207
|
-
discount?: string;
|
|
208
|
-
voucherError: boolean;
|
|
209
|
-
setVoucherError: (value: boolean) => void;
|
|
210
|
-
}) => {
|
|
211
|
-
const intl = useIntl();
|
|
212
|
-
const { voucherCode, setVoucherCode } = useSaleTunnelContext();
|
|
213
|
-
const [voucher, setVoucher] = useState('');
|
|
214
|
-
const handleVoucher = (e: ChangeEvent<HTMLInputElement>) => setVoucher(e.target.value);
|
|
215
|
-
const submitVoucher = () => {
|
|
216
|
-
setVoucherError(false);
|
|
217
|
-
setVoucherCode(voucher);
|
|
218
|
-
setVoucher('');
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<div className="sale-tunnel__voucher">
|
|
223
|
-
<div className="description">
|
|
224
|
-
<h4 className="block-title mb-t">
|
|
225
|
-
<FormattedMessage {...messages.voucherTitle} />
|
|
226
|
-
</h4>
|
|
227
|
-
<span className="mb-t">
|
|
228
|
-
<FormattedMessage {...messages.voucherInfo} />
|
|
229
|
-
</span>
|
|
230
|
-
</div>
|
|
231
|
-
<div className="form">
|
|
232
|
-
<Input
|
|
233
|
-
className="form-field mt-s"
|
|
234
|
-
value={voucher}
|
|
235
|
-
onChange={handleVoucher}
|
|
236
|
-
label={intl.formatMessage(messages.voucherTitle)}
|
|
237
|
-
disabled={!!voucherCode}
|
|
238
|
-
/>
|
|
239
|
-
<Button size="small" color="primary" onClick={submitVoucher} disabled={!!voucherCode}>
|
|
240
|
-
<FormattedMessage {...messages.voucherValidate} />
|
|
241
|
-
</Button>
|
|
242
|
-
</div>
|
|
243
|
-
{voucherCode && (
|
|
244
|
-
<div className="voucher-tag">
|
|
245
|
-
<span>{voucherCode}</span>
|
|
246
|
-
<button
|
|
247
|
-
onClick={() => setVoucherCode('')}
|
|
248
|
-
title={intl.formatMessage(messages.voucherDelete)}
|
|
249
|
-
>
|
|
250
|
-
<span className="material-icons">close</span>
|
|
251
|
-
</button>
|
|
252
|
-
</div>
|
|
253
|
-
)}
|
|
254
|
-
{discount && (
|
|
255
|
-
<div className="voucher-discount">
|
|
256
|
-
<span>
|
|
257
|
-
<FormattedMessage {...messages.discount} />
|
|
258
|
-
</span>
|
|
259
|
-
<span>{discount}</span>
|
|
260
|
-
</div>
|
|
261
|
-
)}
|
|
262
|
-
{voucherError && (
|
|
263
|
-
<Alert type={VariantType.ERROR} className="mt-s">
|
|
264
|
-
<FormattedMessage {...messages.voucherError} />
|
|
265
|
-
</Alert>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const PaymentScheduleBlock = ({ schedule }: { schedule: PaymentSchedule }) => {
|
|
272
|
-
return (
|
|
273
|
-
<div className="payment-schedule">
|
|
274
|
-
<h4 className="block-title mb-t">
|
|
275
|
-
<FormattedMessage {...messages.paymentSchedule} />
|
|
276
|
-
</h4>
|
|
277
|
-
<div className="mt-t">
|
|
278
|
-
<PaymentScheduleGrid schedule={schedule} />
|
|
279
63
|
</div>
|
|
64
|
+
{purchaseType === FormType.SINGULAR && <SaleTunnelInformationSingular />}
|
|
65
|
+
{purchaseType === FormType.GROUP && <SaleTunnelInformationGroup />}
|
|
280
66
|
</div>
|
|
281
67
|
);
|
|
282
68
|
};
|
|
@@ -33,7 +33,7 @@ const messages = defineMessages({
|
|
|
33
33
|
|
|
34
34
|
export const SaleTunnelSuccess = ({ closeModal }: { closeModal: () => void }) => {
|
|
35
35
|
const intl = useIntl();
|
|
36
|
-
const { order, product } = useSaleTunnelContext();
|
|
36
|
+
const { order, product, batchOrder, schedule } = useSaleTunnelContext();
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
39
|
<section className="sale-tunnel-step" data-testid="generic-sale-tunnel-success-step">
|
|
@@ -46,24 +46,38 @@ export const SaleTunnelSuccess = ({ closeModal }: { closeModal: () => void }) =>
|
|
|
46
46
|
<p className="sale-tunnel-step__content">
|
|
47
47
|
<FormattedMessage {...messages.successMessage} />
|
|
48
48
|
<br />
|
|
49
|
-
<FormattedMessage {...messages.successDetailMessage} />
|
|
49
|
+
{schedule && <FormattedMessage {...messages.successDetailMessage} />}
|
|
50
50
|
</p>
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
{order && (
|
|
52
|
+
<footer className="sale-tunnel-step__footer">
|
|
53
|
+
{product.type === ProductType.CREDENTIAL ? (
|
|
54
|
+
<Button
|
|
55
|
+
href={
|
|
56
|
+
getDashboardBasename(intl.locale) +
|
|
57
|
+
generatePath(LearnerDashboardPaths.ORDER, { orderId: order!.id })
|
|
58
|
+
}
|
|
59
|
+
>
|
|
60
|
+
<FormattedMessage {...messages.cta} />
|
|
61
|
+
</Button>
|
|
62
|
+
) : (
|
|
63
|
+
<Button onClick={closeModal}>
|
|
64
|
+
<FormattedMessage {...messages.cta} />
|
|
65
|
+
</Button>
|
|
66
|
+
)}
|
|
67
|
+
</footer>
|
|
68
|
+
)}
|
|
69
|
+
{batchOrder?.id && (
|
|
70
|
+
<footer className="sale-tunnel-step__footer">
|
|
53
71
|
<Button
|
|
54
72
|
href={
|
|
55
73
|
getDashboardBasename(intl.locale) +
|
|
56
|
-
generatePath(LearnerDashboardPaths.
|
|
74
|
+
generatePath(LearnerDashboardPaths.BATCH_ORDER, { batchOrderId: batchOrder.id })
|
|
57
75
|
}
|
|
58
76
|
>
|
|
59
77
|
<FormattedMessage {...messages.cta} />
|
|
60
78
|
</Button>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<FormattedMessage {...messages.cta} />
|
|
64
|
-
</Button>
|
|
65
|
-
)}
|
|
66
|
-
</footer>
|
|
79
|
+
</footer>
|
|
80
|
+
)}
|
|
67
81
|
</section>
|
|
68
82
|
);
|
|
69
83
|
};
|
|
@@ -2,7 +2,9 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
2
2
|
import { Alert, Button, VariantType } from '@openfun/cunningham-react';
|
|
3
3
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
4
4
|
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
5
|
+
import { validationSchema } from 'components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup';
|
|
5
6
|
import { useOrders } from 'hooks/useOrders';
|
|
7
|
+
import { useBatchOrder } from 'hooks/useBatchOrder';
|
|
6
8
|
import { OrderCreationPayload } from 'types/Joanie';
|
|
7
9
|
import { useMatchMediaLg } from 'hooks/useMatchMedia';
|
|
8
10
|
import { SubscriptionErrorMessageId } from 'components/PaymentInterfaces/types';
|
|
@@ -60,6 +62,11 @@ const messages = defineMessages({
|
|
|
60
62
|
description: 'Label for screen reader when an order creation is in progress.',
|
|
61
63
|
id: 'components.SubscriptionButton.orderCreationInProgress',
|
|
62
64
|
},
|
|
65
|
+
batchOrderFormInvalid: {
|
|
66
|
+
id: 'components.SubscriptionButton.batchOrderFormInvalid',
|
|
67
|
+
defaultMessage: 'Some required fields are missing in the form.',
|
|
68
|
+
description: 'Some required fields are missing in the form.',
|
|
69
|
+
},
|
|
63
70
|
});
|
|
64
71
|
|
|
65
72
|
enum ComponentStates {
|
|
@@ -82,16 +89,23 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
82
89
|
order,
|
|
83
90
|
creditCard,
|
|
84
91
|
billingAddress,
|
|
92
|
+
batchOrder,
|
|
93
|
+
setBatchOrder,
|
|
94
|
+
batchOrderFormMethods,
|
|
95
|
+
validateBatchOrder,
|
|
85
96
|
hasWaivedWithdrawalRight,
|
|
86
97
|
product,
|
|
87
98
|
nextStep,
|
|
88
99
|
runSubmitCallbacks,
|
|
89
100
|
props: saleTunnelProps,
|
|
90
101
|
voucherCode,
|
|
102
|
+
needsPayment,
|
|
91
103
|
} = useSaleTunnelContext();
|
|
92
104
|
const { methods: orderMethods } = useOrders(undefined, { enabled: false });
|
|
105
|
+
const { methods: batchOrderMethods } = useBatchOrder();
|
|
93
106
|
const [state, setState] = useState<ComponentStates>(ComponentStates.IDLE);
|
|
94
107
|
const [error, setError] = useState<SubscriptionErrorMessageId | string>();
|
|
108
|
+
const [isBatchOrderValid, setIsBatchOrderValid] = useState(false);
|
|
95
109
|
const isMobile = useMatchMediaLg();
|
|
96
110
|
|
|
97
111
|
const handleError = (
|
|
@@ -112,12 +126,12 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
112
126
|
return;
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
if (!billingAddress) {
|
|
129
|
+
if (!billingAddress && needsPayment) {
|
|
116
130
|
handleError(SubscriptionErrorMessageId.ERROR_ADDRESS);
|
|
117
131
|
return;
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
if (!saleTunnelProps.isWithdrawable && !hasWaivedWithdrawalRight) {
|
|
134
|
+
if (!saleTunnelProps.isWithdrawable && !hasWaivedWithdrawalRight && needsPayment) {
|
|
121
135
|
handleError(SubscriptionErrorMessageId.ERROR_WITHDRAWAL_RIGHT);
|
|
122
136
|
return;
|
|
123
137
|
}
|
|
@@ -142,15 +156,43 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
142
156
|
});
|
|
143
157
|
};
|
|
144
158
|
|
|
159
|
+
const createBatchOrder = async () => {
|
|
160
|
+
setState(ComponentStates.LOADING);
|
|
161
|
+
try {
|
|
162
|
+
await runSubmitCallbacks();
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
164
|
+
} catch (_error) {
|
|
165
|
+
setState(ComponentStates.IDLE);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!batchOrder) return;
|
|
169
|
+
const isFormValid = await batchOrderFormMethods?.trigger();
|
|
170
|
+
if (!isFormValid) {
|
|
171
|
+
handleError(SubscriptionErrorMessageId.ERROR_BATCH_ORDER_FORM_INVALID);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
batchOrderMethods.create(batchOrder, {
|
|
175
|
+
onError: async () => {
|
|
176
|
+
handleError();
|
|
177
|
+
},
|
|
178
|
+
onSuccess: async (createdBatchOrder: any) => {
|
|
179
|
+
if (createdBatchOrder.id) {
|
|
180
|
+
setBatchOrder(createdBatchOrder);
|
|
181
|
+
validateBatchOrder();
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
145
187
|
const walkthroughMessages = useMemo(() => {
|
|
146
188
|
if (product.contract_definition && product.price > 0) {
|
|
147
189
|
return messages.walkthroughToSignAndSavePayment;
|
|
148
190
|
} else if (product.contract_definition && product.price === 0) {
|
|
149
191
|
return messages.walkthroughToSign;
|
|
150
|
-
} else if (!product.contract_definition && product.price > 0) {
|
|
192
|
+
} else if (!product.contract_definition && product.price > 0 && needsPayment) {
|
|
151
193
|
return messages.walkthroughToSavePayment;
|
|
152
194
|
}
|
|
153
|
-
}, [product, creditCard]);
|
|
195
|
+
}, [product, creditCard, needsPayment]);
|
|
154
196
|
|
|
155
197
|
useEffect(() => {
|
|
156
198
|
if (order) nextStep();
|
|
@@ -165,6 +207,12 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
165
207
|
}
|
|
166
208
|
}, [state]);
|
|
167
209
|
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (batchOrder) {
|
|
212
|
+
validationSchema.isValid(batchOrder).then((isValid) => setIsBatchOrderValid(isValid));
|
|
213
|
+
}
|
|
214
|
+
}, [batchOrder]);
|
|
215
|
+
|
|
168
216
|
return (
|
|
169
217
|
<>
|
|
170
218
|
<div style={{ maxWidth: '680px' }} className="mb-s" data-testid="walkthrough-banner">
|
|
@@ -178,9 +226,9 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
178
226
|
)}
|
|
179
227
|
</div>
|
|
180
228
|
<Button
|
|
181
|
-
onClick={createOrder}
|
|
229
|
+
onClick={batchOrder ? createBatchOrder : createOrder}
|
|
182
230
|
fullWidth={isMobile}
|
|
183
|
-
disabled={state === ComponentStates.LOADING}
|
|
231
|
+
disabled={state === ComponentStates.LOADING || (batchOrder && !isBatchOrderValid)}
|
|
184
232
|
{...(state === ComponentStates.ERROR && {
|
|
185
233
|
'aria-describedby': 'sale-tunnel-payment-error',
|
|
186
234
|
})}
|
|
@@ -130,6 +130,61 @@
|
|
|
130
130
|
font-size: var(--c--theme--font--sizes--l);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
.stepper {
|
|
135
|
+
flex-wrap: wrap;
|
|
136
|
+
gap: 0.2rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.navigationButton {
|
|
140
|
+
display: flex;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
gap: 2rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.step-content {
|
|
146
|
+
.step {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
align-items: center;
|
|
150
|
+
margin: 1rem 0;
|
|
151
|
+
gap: 0.5rem;
|
|
152
|
+
.field {
|
|
153
|
+
width: 90%;
|
|
154
|
+
}
|
|
155
|
+
.city-fields {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: row;
|
|
158
|
+
justify-content: space-between;
|
|
159
|
+
gap: 0.25rem;
|
|
160
|
+
width: 90%;
|
|
161
|
+
}
|
|
162
|
+
.payment-block {
|
|
163
|
+
& > div {
|
|
164
|
+
margin: 1rem 0;
|
|
165
|
+
flex-direction: row;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
.organism-block {
|
|
169
|
+
width: 100%;
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-direction: column;
|
|
172
|
+
gap: 0.25rem;
|
|
173
|
+
margin-bottom: 1rem;
|
|
174
|
+
.opco-order {
|
|
175
|
+
display: flex;
|
|
176
|
+
flex-direction: row;
|
|
177
|
+
gap: 0.25rem;
|
|
178
|
+
& > div {
|
|
179
|
+
width: 100%;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
.recommandation {
|
|
184
|
+
width: 100%;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
133
188
|
}
|
|
134
189
|
|
|
135
190
|
.description {
|