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
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { ChangeEvent, useEffect, useState } from 'react';
|
|
2
|
+
import { defineMessages, FormattedMessage, FormattedNumber, useIntl } from 'react-intl';
|
|
3
|
+
import { Alert, Button, Input, VariantType } from '@openfun/cunningham-react';
|
|
4
|
+
import { AddressSelector } from 'components/SaleTunnel/AddressSelector';
|
|
5
|
+
import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
|
|
6
|
+
import { useSaleTunnelContext } from 'components/SaleTunnel/GenericSaleTunnel';
|
|
7
|
+
import OpenEdxFullNameForm from 'components/OpenEdxFullNameForm';
|
|
8
|
+
import { useSession } from 'contexts/SessionContext';
|
|
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';
|
|
13
|
+
import { HttpError } from 'utils/errors/HttpError';
|
|
14
|
+
|
|
15
|
+
const messages = defineMessages({
|
|
16
|
+
title: {
|
|
17
|
+
id: 'components.SaleTunnel.Information.title',
|
|
18
|
+
description: 'Title for the information section',
|
|
19
|
+
defaultMessage: 'Information',
|
|
20
|
+
},
|
|
21
|
+
description: {
|
|
22
|
+
id: 'components.SaleTunnel.Information.description',
|
|
23
|
+
description: 'Description of the information section',
|
|
24
|
+
defaultMessage: 'Those information will be used for billing',
|
|
25
|
+
},
|
|
26
|
+
paymentSchedule: {
|
|
27
|
+
id: 'components.SaleTunnel.Information.paymentSchedule',
|
|
28
|
+
description: 'Title for the payment schedule section',
|
|
29
|
+
defaultMessage: 'Payment schedule',
|
|
30
|
+
},
|
|
31
|
+
totalInfo: {
|
|
32
|
+
id: 'components.SaleTunnel.Information.total.info',
|
|
33
|
+
description: 'Information about the total amount',
|
|
34
|
+
defaultMessage: 'You will then pay on the secured platform of our payment provider.',
|
|
35
|
+
},
|
|
36
|
+
totalLabel: {
|
|
37
|
+
id: 'components.SaleTunnel.Information.total.label',
|
|
38
|
+
description: 'Label for the total amount',
|
|
39
|
+
defaultMessage: 'Total',
|
|
40
|
+
},
|
|
41
|
+
emailLabel: {
|
|
42
|
+
id: 'components.SaleTunnel.Information.email.label',
|
|
43
|
+
description: 'Label for the email',
|
|
44
|
+
defaultMessage: 'Account email',
|
|
45
|
+
},
|
|
46
|
+
emailInfo: {
|
|
47
|
+
id: 'components.SaleTunnel.Information.email.info',
|
|
48
|
+
description: 'Info for the email',
|
|
49
|
+
defaultMessage:
|
|
50
|
+
'This email will be used to send you confirmation mails, it is the one you created your account with.',
|
|
51
|
+
},
|
|
52
|
+
voucherTitle: {
|
|
53
|
+
id: 'components.SaleTunnel.Information.voucher.title',
|
|
54
|
+
description: 'Title for the voucher',
|
|
55
|
+
defaultMessage: 'Voucher code',
|
|
56
|
+
},
|
|
57
|
+
voucherInfo: {
|
|
58
|
+
id: 'components.SaleTunnel.Information.voucher.info',
|
|
59
|
+
description: 'Info for the voucher',
|
|
60
|
+
defaultMessage: 'If you have a voucher code, please enter it in the field below.',
|
|
61
|
+
},
|
|
62
|
+
voucherValidate: {
|
|
63
|
+
id: 'components.SaleTunnel.Information.voucher.validate',
|
|
64
|
+
description: 'Validate text for the voucher',
|
|
65
|
+
defaultMessage: 'Validate',
|
|
66
|
+
},
|
|
67
|
+
voucherDelete: {
|
|
68
|
+
id: 'components.SaleTunnel.Information.voucher.delete',
|
|
69
|
+
description: 'Delete text for the voucher',
|
|
70
|
+
defaultMessage: 'Delete this voucher',
|
|
71
|
+
},
|
|
72
|
+
voucherErrorInvalid: {
|
|
73
|
+
id: 'components.SaleTunnel.Information.voucher.errorInvalid',
|
|
74
|
+
description: 'Error when voucher is invalid',
|
|
75
|
+
defaultMessage: 'The submitted voucher code is not valid.',
|
|
76
|
+
},
|
|
77
|
+
voucherErrorTooManyRequests: {
|
|
78
|
+
id: 'components.SaleTunnel.Information.voucher.errorTooManyRequests',
|
|
79
|
+
description: 'Error when user has tried too many vouchers',
|
|
80
|
+
defaultMessage: 'Too many attempts. Please try again later.',
|
|
81
|
+
},
|
|
82
|
+
discount: {
|
|
83
|
+
id: 'components.SaleTunnel.Information.voucher.discount',
|
|
84
|
+
description: 'Discount description',
|
|
85
|
+
defaultMessage: 'Discount applied',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const SaleTunnelInformationSingular = () => {
|
|
90
|
+
const {
|
|
91
|
+
props,
|
|
92
|
+
product,
|
|
93
|
+
voucherCode,
|
|
94
|
+
setVoucherCode,
|
|
95
|
+
setSchedule,
|
|
96
|
+
needsPayment,
|
|
97
|
+
setNeedsPayment,
|
|
98
|
+
} = useSaleTunnelContext();
|
|
99
|
+
const [voucherError, setVoucherError] = useState<HttpError | null>(null);
|
|
100
|
+
const query = usePaymentPlan({
|
|
101
|
+
course_code: props.course?.code ?? props.enrollment!.course_run.course.code,
|
|
102
|
+
product_id: props.product.id,
|
|
103
|
+
...(voucherCode ? { voucher_code: voucherCode } : {}),
|
|
104
|
+
});
|
|
105
|
+
const schedule = query.data?.payment_schedule ?? props.paymentPlan?.payment_schedule;
|
|
106
|
+
const price = query.data?.price ?? props.paymentPlan?.price;
|
|
107
|
+
const discountedPrice = query.data?.discounted_price ?? props.paymentPlan?.discounted_price;
|
|
108
|
+
const discount = query.data?.discount ?? props.paymentPlan?.discount;
|
|
109
|
+
const fromBatchOrder = query.data?.from_batch_order ?? props.paymentPlan?.from_batch_order;
|
|
110
|
+
|
|
111
|
+
const showPaymentSchedule =
|
|
112
|
+
product.type === ProductType.CREDENTIAL &&
|
|
113
|
+
schedule &&
|
|
114
|
+
(discountedPrice != null ? discountedPrice > 0 : price != null && price > 0);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (schedule) {
|
|
118
|
+
setSchedule(schedule);
|
|
119
|
+
}
|
|
120
|
+
}, [schedule, setSchedule]);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (query.error && voucherCode) {
|
|
124
|
+
setVoucherCode('');
|
|
125
|
+
setVoucherError(query.error);
|
|
126
|
+
}
|
|
127
|
+
}, [query.error, voucherCode, setVoucherCode]);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setNeedsPayment(!fromBatchOrder);
|
|
131
|
+
}, [fromBatchOrder, setNeedsPayment]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
<div>
|
|
136
|
+
<Voucher
|
|
137
|
+
discount={discount}
|
|
138
|
+
voucherError={voucherError}
|
|
139
|
+
setVoucherError={setVoucherError}
|
|
140
|
+
/>
|
|
141
|
+
<Total price={price} discountedPrice={discountedPrice} />
|
|
142
|
+
</div>
|
|
143
|
+
{needsPayment && (
|
|
144
|
+
<div>
|
|
145
|
+
<h3 className="block-title mb-t">
|
|
146
|
+
<FormattedMessage {...messages.title} />
|
|
147
|
+
</h3>
|
|
148
|
+
<div className="description mb-s">
|
|
149
|
+
<FormattedMessage {...messages.description} />
|
|
150
|
+
</div>
|
|
151
|
+
<OpenEdxFullNameForm />
|
|
152
|
+
<AddressSelector />
|
|
153
|
+
<div className="mt-s">
|
|
154
|
+
<Email />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
<div>
|
|
159
|
+
{showPaymentSchedule && <PaymentScheduleBlock schedule={schedule} />}
|
|
160
|
+
{needsPayment && <WithdrawRightCheckbox />}
|
|
161
|
+
</div>
|
|
162
|
+
</>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const Email = () => {
|
|
167
|
+
const { user } = useSession();
|
|
168
|
+
const { data: openEdxProfileData } = useOpenEdxProfile({
|
|
169
|
+
username: user!.username,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className="sale-tunnel__email">
|
|
174
|
+
<div className="sale-tunnel__email__top">
|
|
175
|
+
<h4>
|
|
176
|
+
<FormattedMessage {...messages.emailLabel} />
|
|
177
|
+
</h4>
|
|
178
|
+
<div className="fw-bold">{openEdxProfileData?.email}</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="sale-tunnel__email__description">
|
|
181
|
+
<FormattedMessage {...messages.emailInfo} />
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const Total = ({ price, discountedPrice }: { price?: number; discountedPrice?: number }) => {
|
|
188
|
+
const { product } = useSaleTunnelContext();
|
|
189
|
+
const totalPrice = price || product.price;
|
|
190
|
+
return (
|
|
191
|
+
<div className="sale-tunnel__total">
|
|
192
|
+
<div className="sale-tunnel__total__amount mt-t" data-testid="sale-tunnel__total__amount">
|
|
193
|
+
<div className="block-title">
|
|
194
|
+
<FormattedMessage {...messages.totalLabel} />
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div className="block-title">
|
|
198
|
+
{discountedPrice !== undefined ? (
|
|
199
|
+
<>
|
|
200
|
+
<span className="price--striked">
|
|
201
|
+
<FormattedNumber
|
|
202
|
+
value={totalPrice}
|
|
203
|
+
style="currency"
|
|
204
|
+
currency={product.price_currency}
|
|
205
|
+
/>
|
|
206
|
+
</span>
|
|
207
|
+
<FormattedNumber
|
|
208
|
+
value={discountedPrice}
|
|
209
|
+
style="currency"
|
|
210
|
+
currency={product.price_currency}
|
|
211
|
+
/>
|
|
212
|
+
</>
|
|
213
|
+
) : (
|
|
214
|
+
<FormattedNumber
|
|
215
|
+
value={totalPrice}
|
|
216
|
+
style="currency"
|
|
217
|
+
currency={product.price_currency}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const Voucher = ({
|
|
227
|
+
discount,
|
|
228
|
+
voucherError,
|
|
229
|
+
setVoucherError,
|
|
230
|
+
}: {
|
|
231
|
+
discount?: string;
|
|
232
|
+
voucherError: HttpError | null;
|
|
233
|
+
setVoucherError: (value: HttpError | null) => void;
|
|
234
|
+
}) => {
|
|
235
|
+
const intl = useIntl();
|
|
236
|
+
const { voucherCode, setVoucherCode } = useSaleTunnelContext();
|
|
237
|
+
const [voucher, setVoucher] = useState('');
|
|
238
|
+
const handleVoucher = (e: ChangeEvent<HTMLInputElement>) => setVoucher(e.target.value);
|
|
239
|
+
const submitVoucher = () => {
|
|
240
|
+
setVoucherError(null);
|
|
241
|
+
setVoucherCode(voucher);
|
|
242
|
+
setVoucher('');
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div className="sale-tunnel__voucher">
|
|
247
|
+
<div className="description">
|
|
248
|
+
<h4 className="block-title mb-t">
|
|
249
|
+
<FormattedMessage {...messages.voucherTitle} />
|
|
250
|
+
</h4>
|
|
251
|
+
{!voucherCode && (
|
|
252
|
+
<span className="mb-t">
|
|
253
|
+
<FormattedMessage {...messages.voucherInfo} />
|
|
254
|
+
</span>
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
257
|
+
{!voucherCode && (
|
|
258
|
+
<div className="form">
|
|
259
|
+
<Input
|
|
260
|
+
className="form-field mt-s"
|
|
261
|
+
value={voucher}
|
|
262
|
+
onChange={handleVoucher}
|
|
263
|
+
label={intl.formatMessage(messages.voucherTitle)}
|
|
264
|
+
disabled={!!voucherCode}
|
|
265
|
+
/>
|
|
266
|
+
<Button size="small" color="primary" onClick={submitVoucher} disabled={!!voucherCode}>
|
|
267
|
+
<FormattedMessage {...messages.voucherValidate} />
|
|
268
|
+
</Button>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
{voucherCode && (
|
|
272
|
+
<div className="voucher-tag">
|
|
273
|
+
<span>{voucherCode}</span>
|
|
274
|
+
<button
|
|
275
|
+
onClick={() => setVoucherCode('')}
|
|
276
|
+
title={intl.formatMessage(messages.voucherDelete)}
|
|
277
|
+
>
|
|
278
|
+
<span className="material-icons">close</span>
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
{discount && (
|
|
283
|
+
<div className="voucher-discount">
|
|
284
|
+
<span>
|
|
285
|
+
<FormattedMessage {...messages.discount} />
|
|
286
|
+
</span>
|
|
287
|
+
<span>{discount}</span>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
{voucherError && (
|
|
291
|
+
<Alert type={VariantType.ERROR} className="mt-s">
|
|
292
|
+
{voucherError.code === 429 ? (
|
|
293
|
+
<FormattedMessage {...messages.voucherErrorTooManyRequests} />
|
|
294
|
+
) : (
|
|
295
|
+
<FormattedMessage {...messages.voucherErrorInvalid} />
|
|
296
|
+
)}
|
|
297
|
+
</Alert>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const PaymentScheduleBlock = ({ schedule }: { schedule: PaymentSchedule }) => {
|
|
304
|
+
return (
|
|
305
|
+
<div className="payment-schedule">
|
|
306
|
+
<h4 className="block-title mb-t">
|
|
307
|
+
<FormattedMessage {...messages.paymentSchedule} />
|
|
308
|
+
</h4>
|
|
309
|
+
<div className="mt-t">
|
|
310
|
+
<PaymentScheduleGrid schedule={schedule} />
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
};
|