richie-education 2.29.2 → 2.29.3-dev48
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 +193 -125
- package/i18n/locales/es-ES.json +193 -125
- package/i18n/locales/fa-IR.json +193 -125
- package/i18n/locales/fr-CA.json +193 -125
- package/i18n/locales/fr-FR.json +193 -125
- package/i18n/locales/ko-KR.json +193 -125
- package/i18n/locales/pt-PT.json +193 -125
- package/i18n/locales/ru-RU.json +193 -125
- package/i18n/locales/vi-VN.json +193 -125
- 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/ContractStatus/index.spec.tsx +1 -1
- package/js/components/ContractStatus/index.tsx +1 -1
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.spec.tsx +15 -45
- package/js/components/{SaleTunnel/CreditCardSelector → CreditCardSelector}/index.tsx +19 -26
- package/js/components/DownloadContractButton/index.spec.tsx +1 -1
- package/js/components/Form/utils.ts +2 -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/PurchaseButton/index.tsx +9 -27
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -2
- package/js/components/SaleTunnel/CertificateSaleTunnel/index.tsx +2 -2
- package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +6 -10
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +105 -48
- 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 +157 -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 +202 -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 +114 -48
- package/js/components/SaleTunnel/index.spec.tsx +333 -717
- package/js/components/SaleTunnel/index.tsx +2 -1
- 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/DashboardOrderLayout/index.spec.tsx +10 -1
- 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.prod.ts +0 -2
- package/js/settings/settings.test.ts +16 -2
- 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 +81 -32
- package/js/utils/OrderHelper/index.ts +46 -38
- package/js/utils/PaymentScheduleHelper/index.spec.tsx +78 -0
- package/js/utils/PaymentScheduleHelper/index.ts +30 -0
- package/js/utils/ProductHelper/index.spec.ts +322 -166
- package/js/utils/ProductHelper/index.ts +32 -0
- package/js/utils/test/factories/joanie.ts +72 -69
- package/js/widgets/Dashboard/components/DashboardItem/Contract/index.spec.tsx +2 -2
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -18
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.spec.tsx +23 -0
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +23 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +145 -34
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +175 -37
- package/js/widgets/Dashboard/components/DashboardItem/Order/CertificateItem/index.tsx +51 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/ContractItem/index.tsx +52 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemNotResumable.spec.tsx +109 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +219 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +111 -245
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +61 -8
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +17 -7
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemSavePaymentMethod.spec.tsx +116 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +175 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +7 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +127 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +216 -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/DashboardItem/Order/OrganizationBlock/index.tsx +150 -0
- package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +32 -14
- 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 +45 -44
- package/scss/components/_index.scss +4 -2
- package/js/components/PaymentButton/_styles.scss +0 -27
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +0 -340
- 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
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import { FormattedMessage, useIntl
|
|
2
|
-
import { Button } from '@openfun/cunningham-react';
|
|
3
|
-
import classNames from 'classnames';
|
|
1
|
+
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
4
2
|
import { generatePath } from 'react-router-dom';
|
|
5
|
-
import {
|
|
3
|
+
import { CredentialOrder, OrderState } from 'types/Joanie';
|
|
6
4
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
7
5
|
import { CoursesHelper } from 'utils/CoursesHelper';
|
|
8
|
-
import { useCertificate } from 'hooks/useCertificates';
|
|
9
|
-
import { Spinner } from 'components/Spinner';
|
|
10
6
|
import { DashboardSubItem } from 'widgets/Dashboard/components/DashboardItem/DashboardSubItem';
|
|
11
|
-
import { DashboardItemCertificate } from 'widgets/Dashboard/components/DashboardItem/Certificate';
|
|
12
7
|
import { RouterButton } from 'widgets/Dashboard/components/RouterButton';
|
|
13
8
|
import { useCourseProduct } from 'hooks/useCourseProducts';
|
|
14
9
|
import { OrderHelper } from 'utils/OrderHelper';
|
|
15
|
-
import ContractStatus from 'components/ContractStatus';
|
|
16
|
-
import SignContractButton from 'components/SignContractButton';
|
|
17
|
-
import { AddressView } from 'components/Address';
|
|
18
10
|
import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
|
|
11
|
+
import OrganizationBlock from 'widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock';
|
|
12
|
+
import CertificateItem from 'widgets/Dashboard/components/DashboardItem/Order/CertificateItem';
|
|
13
|
+
import { ProductHelper } from 'utils/ProductHelper';
|
|
19
14
|
import { DashboardSubItemsList } from '../DashboardSubItemsList';
|
|
20
15
|
import { DashboardItemCourseEnrolling } from '../CourseEnrolling';
|
|
21
16
|
import { DashboardItem } from '../index';
|
|
@@ -28,55 +23,32 @@ const messages = defineMessages({
|
|
|
28
23
|
description: 'Button that redirects to the order details',
|
|
29
24
|
defaultMessage: 'View details',
|
|
30
25
|
},
|
|
31
|
-
loadingCertificate: {
|
|
32
|
-
id: 'components.DashboardItemOrder.loadingCertificate',
|
|
33
|
-
description: 'Accessible label displayed while certificate is being fetched on the dashboard.',
|
|
34
|
-
defaultMessage: 'Loading certificate...',
|
|
35
|
-
},
|
|
36
26
|
syllabusLinkLabel: {
|
|
37
27
|
id: 'components.DashboardItemOrder.syllabusLinkLabel',
|
|
38
28
|
description: 'Syllabus link label on order details',
|
|
39
29
|
defaultMessage: 'Go to syllabus',
|
|
40
30
|
},
|
|
41
|
-
|
|
42
|
-
id: 'components.DashboardItemOrder.
|
|
43
|
-
description: '
|
|
44
|
-
defaultMessage: '
|
|
45
|
-
},
|
|
46
|
-
contactButton: {
|
|
47
|
-
id: 'components.DashboardItemOrder.contactButton',
|
|
48
|
-
description: 'Button to contact the organization',
|
|
49
|
-
defaultMessage: 'Contact',
|
|
50
|
-
},
|
|
51
|
-
organizationHeader: {
|
|
52
|
-
id: 'components.DashboardItemOrder.organizationHeader',
|
|
53
|
-
description: 'Header of the organization section',
|
|
54
|
-
defaultMessage: 'This training is provided by',
|
|
31
|
+
missingPaymentMethodTitle: {
|
|
32
|
+
id: 'components.DashboardItemOrder.missingPaymentMethodTitle',
|
|
33
|
+
description: 'Main message displayed when the order is missing a payment method',
|
|
34
|
+
defaultMessage: 'A payment method is missing',
|
|
55
35
|
},
|
|
56
|
-
|
|
57
|
-
id: 'components.DashboardItemOrder.
|
|
58
|
-
description: '
|
|
59
|
-
defaultMessage: '
|
|
36
|
+
missingPaymentMethodDescription: {
|
|
37
|
+
id: 'components.DashboardItemOrder.missingPaymentMethodDescription',
|
|
38
|
+
description: 'Description message displayed when the order is missing a payment method',
|
|
39
|
+
defaultMessage: 'You must define a payment method to finalize your subscription.',
|
|
60
40
|
},
|
|
61
|
-
|
|
62
|
-
id: 'components.DashboardItemOrder.
|
|
63
|
-
description: '
|
|
64
|
-
defaultMessage: '
|
|
41
|
+
missingPaymentMethodCTA: {
|
|
42
|
+
id: 'components.DashboardItemOrder.missingPaymentMethodCTA',
|
|
43
|
+
description: 'CTA label displayed when the order is missing a payment method',
|
|
44
|
+
defaultMessage: 'Define',
|
|
65
45
|
},
|
|
66
|
-
|
|
67
|
-
id: 'components.DashboardItemOrder.
|
|
68
|
-
description:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
id: 'components.DashboardItemOrder.organizationPhoneContactLabel',
|
|
73
|
-
description: 'Label for the organization phone contact',
|
|
74
|
-
defaultMessage: 'Phone',
|
|
75
|
-
},
|
|
76
|
-
organizationDpoContactLabel: {
|
|
77
|
-
id: 'components.DashboardItemOrder.organizationDpoContactLabel',
|
|
78
|
-
description: 'Label for the organization DPO contact',
|
|
79
|
-
defaultMessage: 'Data protection email',
|
|
46
|
+
notResumable: {
|
|
47
|
+
id: 'components.DashboardItemOrder.notResumable',
|
|
48
|
+
description:
|
|
49
|
+
'Message displayed when the order subscription is not completed but the product is no more purchasable',
|
|
50
|
+
defaultMessage:
|
|
51
|
+
'The subscription process cannot be resumed. The related training is no more purchasable.',
|
|
80
52
|
},
|
|
81
53
|
});
|
|
82
54
|
|
|
@@ -87,63 +59,33 @@ interface DashboardItemOrderProps {
|
|
|
87
59
|
writable?: boolean;
|
|
88
60
|
}
|
|
89
61
|
|
|
90
|
-
interface DashboardItemOrderCertificateProps {
|
|
91
|
-
order: CredentialOrder;
|
|
92
|
-
product: Product;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const DashboardItemOrderCertificate = ({ order, product }: DashboardItemOrderCertificateProps) => {
|
|
96
|
-
if (!order.certificate_id) {
|
|
97
|
-
return (
|
|
98
|
-
<DashboardItemCertificate
|
|
99
|
-
certificateDefinition={product.certificate_definition}
|
|
100
|
-
productType={product.type}
|
|
101
|
-
mode="compact"
|
|
102
|
-
/>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
const certificate = useCertificate(order.certificate_id);
|
|
106
|
-
return (
|
|
107
|
-
<>
|
|
108
|
-
{certificate.states.fetching && (
|
|
109
|
-
<Spinner aria-labelledby="loading-certificate">
|
|
110
|
-
<span id="loading-certificate">
|
|
111
|
-
<FormattedMessage {...messages.loadingCertificate} />
|
|
112
|
-
</span>
|
|
113
|
-
</Spinner>
|
|
114
|
-
)}
|
|
115
|
-
{certificate.item && (
|
|
116
|
-
<DashboardItemCertificate
|
|
117
|
-
certificate={certificate.item}
|
|
118
|
-
productType={product.type}
|
|
119
|
-
mode="compact"
|
|
120
|
-
/>
|
|
121
|
-
)}
|
|
122
|
-
</>
|
|
123
|
-
);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
62
|
export const DashboardItemOrder = ({
|
|
127
63
|
order,
|
|
128
64
|
showDetailsButton = true,
|
|
129
65
|
showCertificate,
|
|
130
66
|
writable = false,
|
|
131
67
|
}: DashboardItemOrderProps) => {
|
|
132
|
-
const course = order
|
|
133
|
-
|
|
68
|
+
const { course } = order;
|
|
134
69
|
const intl = useIntl();
|
|
135
70
|
const { item: courseProductRelation } = useCourseProduct({
|
|
136
71
|
product_id: order.product_id,
|
|
137
72
|
course_id: course.code,
|
|
138
73
|
});
|
|
139
74
|
const { product } = courseProductRelation || {};
|
|
140
|
-
const needsSignature = OrderHelper.orderNeedsSignature(order
|
|
75
|
+
const needsSignature = OrderHelper.orderNeedsSignature(order);
|
|
76
|
+
const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
|
|
77
|
+
const isActive = OrderHelper.isActive(order);
|
|
78
|
+
const isProductPurchasable = ProductHelper.isPurchasable(courseProductRelation?.product);
|
|
79
|
+
const isNotResumable = !isActive && !isProductPurchasable;
|
|
80
|
+
const canEnroll = OrderHelper.allowEnrollment(order);
|
|
81
|
+
|
|
82
|
+
if (!product) return null;
|
|
141
83
|
|
|
142
84
|
return (
|
|
143
85
|
<div className="dashboard-item-order">
|
|
144
86
|
<DashboardItem
|
|
145
87
|
data-testid={`dashboard-item-order-${order.id}`}
|
|
146
|
-
title={product
|
|
88
|
+
title={product.title}
|
|
147
89
|
code={'Ref. ' + course.code}
|
|
148
90
|
imageUrl={course.cover?.src}
|
|
149
91
|
more={
|
|
@@ -157,13 +99,19 @@ export const DashboardItemOrder = ({
|
|
|
157
99
|
<>
|
|
158
100
|
<div className="dashboard-item-order__footer">
|
|
159
101
|
<div className="dashboard-item__block__status">
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
102
|
+
{isNotResumable ? (
|
|
103
|
+
<>
|
|
104
|
+
<Icon name={IconTypeEnum.ROUND_CLOSE} size="small" />
|
|
105
|
+
<FormattedMessage {...messages.notResumable} />
|
|
106
|
+
</>
|
|
107
|
+
) : (
|
|
108
|
+
<>
|
|
109
|
+
<Icon name={IconTypeEnum.SCHOOL} />
|
|
110
|
+
<OrderStateLearnerMessage order={order} />
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
165
113
|
</div>
|
|
166
|
-
{showDetailsButton && (
|
|
114
|
+
{!isNotResumable && showDetailsButton && (
|
|
167
115
|
<RouterButton
|
|
168
116
|
size="small"
|
|
169
117
|
className="dashboard-item__button"
|
|
@@ -174,168 +122,86 @@ export const DashboardItemOrder = ({
|
|
|
174
122
|
</RouterButton>
|
|
175
123
|
)}
|
|
176
124
|
</div>
|
|
177
|
-
{!writable && needsSignature && (
|
|
125
|
+
{!writable && isProductPurchasable && needsSignature && (
|
|
178
126
|
<DashboardItemContract
|
|
179
127
|
key={`DashboardItemOrderContract_${order.id}`}
|
|
180
128
|
title={product.title}
|
|
181
129
|
order={order}
|
|
182
|
-
contract_definition={product
|
|
130
|
+
contract_definition={product.contract_definition!}
|
|
183
131
|
contract={order.contract}
|
|
184
132
|
writable={writable}
|
|
185
133
|
mode="compact"
|
|
186
134
|
/>
|
|
187
135
|
)}
|
|
136
|
+
{!writable && isProductPurchasable && needsPaymentMethod && (
|
|
137
|
+
<DashboardItem
|
|
138
|
+
title=""
|
|
139
|
+
data-testid={`dashboard-item-payment-method-${order.id}`}
|
|
140
|
+
mode="compact"
|
|
141
|
+
footer={
|
|
142
|
+
<>
|
|
143
|
+
<div className="dashboard-contract__body">
|
|
144
|
+
<Icon name={IconTypeEnum.CREDIT_CARD} />
|
|
145
|
+
<span>
|
|
146
|
+
<FormattedMessage {...messages.missingPaymentMethodTitle} />
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="dashboard-contract__footer">
|
|
150
|
+
<span className="dashboard-contract__footer__status">
|
|
151
|
+
<FormattedMessage {...messages.missingPaymentMethodDescription} />
|
|
152
|
+
</span>
|
|
153
|
+
<div>
|
|
154
|
+
<RouterButton
|
|
155
|
+
size="small"
|
|
156
|
+
href={generatePath(LearnerDashboardPaths.ORDER, {
|
|
157
|
+
orderId: order.id,
|
|
158
|
+
})}
|
|
159
|
+
className="dashboard-item__button"
|
|
160
|
+
>
|
|
161
|
+
<FormattedMessage {...messages.missingPaymentMethodCTA} />
|
|
162
|
+
</RouterButton>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</>
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
188
169
|
</>
|
|
189
170
|
}
|
|
190
171
|
>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
/>
|
|
214
|
-
</DashboardItem>
|
|
215
|
-
{showCertificate && !!product?.certificate_definition && (
|
|
216
|
-
<DashboardItemOrderCertificate order={order} product={product} />
|
|
217
|
-
)}
|
|
218
|
-
{writable && <OrganizationBlock order={order} product={product} />}
|
|
219
|
-
</div>
|
|
220
|
-
);
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const OrganizationBlock = ({ order, product }: { order: CredentialOrder; product: Product }) => {
|
|
224
|
-
const { organization } = order;
|
|
225
|
-
if (!organization) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const showContactsBlock =
|
|
230
|
-
organization.contact_email || organization.contact_phone || organization.dpo_email;
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<div className="dashboard-splitted-card mt-s" data-testid="organization-block">
|
|
234
|
-
<div className="dashboard-splitted-card__column order-organization__caption">
|
|
235
|
-
<div className="dashboard-item-order__organization">
|
|
236
|
-
<div className="dashboard-item-order__organization__header">
|
|
237
|
-
<FormattedMessage {...messages.organizationHeader} />
|
|
238
|
-
</div>
|
|
239
|
-
<div
|
|
240
|
-
className="dashboard-item-order__organization__logo"
|
|
241
|
-
style={{
|
|
242
|
-
backgroundImage: `url(${organization.logo?.src})`,
|
|
243
|
-
}}
|
|
172
|
+
{!isNotResumable && (
|
|
173
|
+
<DashboardSubItemsList
|
|
174
|
+
subItems={order.target_courses?.map((targetCourse) => (
|
|
175
|
+
<DashboardSubItem
|
|
176
|
+
title={targetCourse.title}
|
|
177
|
+
footer={
|
|
178
|
+
<DashboardItemCourseEnrolling
|
|
179
|
+
writable={writable}
|
|
180
|
+
course={targetCourse}
|
|
181
|
+
order={order}
|
|
182
|
+
activeEnrollment={CoursesHelper.findActiveCourseEnrollmentInOrder(
|
|
183
|
+
targetCourse,
|
|
184
|
+
order,
|
|
185
|
+
)}
|
|
186
|
+
notEnrolledUrl={generatePath(LearnerDashboardPaths.ORDER, {
|
|
187
|
+
orderId: order.id,
|
|
188
|
+
})}
|
|
189
|
+
hideEnrollButtons={!canEnroll}
|
|
190
|
+
/>
|
|
191
|
+
}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
244
194
|
/>
|
|
245
|
-
<div className="dashboard-item-order__organization__name">{organization.title}</div>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
<div className="dashboard-splitted-card__separator order-organization__separator" />
|
|
249
|
-
<div className="dashboard-splitted-card__column order-organization__items">
|
|
250
|
-
<ContractItem order={order} product={product} />
|
|
251
|
-
{showContactsBlock && (
|
|
252
|
-
<div className="dashboard-splitted-card__item">
|
|
253
|
-
<div className="dashboard-splitted-card__item__title">Contacts</div>
|
|
254
|
-
<div className="dashboard-splitted-card__item__description">
|
|
255
|
-
{organization.contact_email && (
|
|
256
|
-
<div className="organization-block__contact__item">
|
|
257
|
-
<FormattedMessage {...messages.organizationMailContactLabel} />
|
|
258
|
-
<Button
|
|
259
|
-
size="small"
|
|
260
|
-
color="tertiary"
|
|
261
|
-
href={'mailto:' + (organization.contact_email ?? '')}
|
|
262
|
-
>
|
|
263
|
-
{organization.contact_email}
|
|
264
|
-
</Button>
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
{organization.contact_phone && (
|
|
268
|
-
<div className="organization-block__contact__item">
|
|
269
|
-
<FormattedMessage {...messages.organizationPhoneContactLabel} />
|
|
270
|
-
<Button
|
|
271
|
-
size="small"
|
|
272
|
-
color="tertiary"
|
|
273
|
-
href={'tel:' + (organization.contact_phone ?? '')}
|
|
274
|
-
>
|
|
275
|
-
{organization.contact_phone}
|
|
276
|
-
</Button>
|
|
277
|
-
</div>
|
|
278
|
-
)}
|
|
279
|
-
{organization.dpo_email && (
|
|
280
|
-
<div className="organization-block__contact__item">
|
|
281
|
-
<FormattedMessage {...messages.organizationDpoContactLabel} />
|
|
282
|
-
<Button
|
|
283
|
-
size="small"
|
|
284
|
-
color="tertiary"
|
|
285
|
-
href={'mailto:' + (organization.dpo_email ?? '')}
|
|
286
|
-
>
|
|
287
|
-
{organization.dpo_email}
|
|
288
|
-
</Button>
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
293
195
|
)}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
</div>
|
|
304
|
-
);
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const ContractItem = ({ product, order }: { order: CredentialOrder; product: Product }) => {
|
|
308
|
-
if (!product?.contract_definition) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const needsSignature = OrderHelper.orderNeedsSignature(order, product.contract_definition);
|
|
313
|
-
return (
|
|
314
|
-
<div
|
|
315
|
-
id={`dashboard-item-contract-${order.id}`}
|
|
316
|
-
className="dashboard-splitted-card__item"
|
|
317
|
-
data-testid={`dashboard-item-contract-${order.id}`}
|
|
318
|
-
>
|
|
319
|
-
<div
|
|
320
|
-
className={classNames('dashboard-splitted-card__item__title', {
|
|
321
|
-
'dashboard-splitted-card__item__title--dot': needsSignature,
|
|
322
|
-
})}
|
|
323
|
-
>
|
|
324
|
-
<span>
|
|
325
|
-
<FormattedMessage {...messages.trainingContractTitle} />
|
|
326
|
-
</span>
|
|
327
|
-
</div>
|
|
328
|
-
<div className="dashboard-splitted-card__item__description">
|
|
329
|
-
<ContractStatus contract={order.contract} />
|
|
330
|
-
</div>
|
|
331
|
-
<div className="dashboard-splitted-card__item__actions">
|
|
332
|
-
<SignContractButton
|
|
333
|
-
order={order}
|
|
334
|
-
contract={order.contract}
|
|
335
|
-
writable={true}
|
|
336
|
-
className="dashboard-item__button"
|
|
337
|
-
/>
|
|
338
|
-
</div>
|
|
196
|
+
</DashboardItem>
|
|
197
|
+
{!isNotResumable && (
|
|
198
|
+
<>
|
|
199
|
+
{showCertificate && !!product?.certificate_definition && (
|
|
200
|
+
<CertificateItem order={order} product={product} />
|
|
201
|
+
)}
|
|
202
|
+
{writable && <OrganizationBlock order={order} product={product} />}
|
|
203
|
+
</>
|
|
204
|
+
)}
|
|
339
205
|
</div>
|
|
340
206
|
);
|
|
341
207
|
};
|
package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fetchMock from 'fetch-mock';
|
|
2
2
|
import { faker } from '@faker-js/faker';
|
|
3
|
-
import { screen } from '@testing-library/react';
|
|
3
|
+
import { screen, within } from '@testing-library/react';
|
|
4
|
+
import queryString from 'query-string';
|
|
4
5
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
5
6
|
import { DashboardTest } from 'widgets/Dashboard/components/DashboardTest';
|
|
6
|
-
import { CourseLight } from 'types/Joanie';
|
|
7
|
+
import { CourseLight, NOT_CANCELED_ORDER_STATES, OrderState } from 'types/Joanie';
|
|
7
8
|
import {
|
|
8
9
|
ContractDefinitionFactory,
|
|
9
10
|
ContractFactory,
|
|
@@ -77,7 +78,9 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
77
78
|
await screen.findByRole('heading', { level: 5, name: product.title });
|
|
78
79
|
|
|
79
80
|
expect(
|
|
80
|
-
screen.queryByText(
|
|
81
|
+
screen.queryByText(
|
|
82
|
+
'You have to sign this training contract to finalize your subscription.',
|
|
83
|
+
),
|
|
81
84
|
).not.toBeInTheDocument();
|
|
82
85
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
83
86
|
expect(screen.getByText('On going')).toBeInTheDocument();
|
|
@@ -111,7 +114,9 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
111
114
|
await screen.findByRole('heading', { level: 5, name: product.title });
|
|
112
115
|
|
|
113
116
|
expect(
|
|
114
|
-
screen.queryByText(
|
|
117
|
+
screen.queryByText(
|
|
118
|
+
'You have to sign this training contract to finalize your subscription.',
|
|
119
|
+
),
|
|
115
120
|
).not.toBeInTheDocument();
|
|
116
121
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
117
122
|
expect(screen.getByText('On going')).toBeInTheDocument();
|
|
@@ -124,6 +129,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
124
129
|
|
|
125
130
|
it('renders a non-writable order with a contract not signed yet', async () => {
|
|
126
131
|
const order = CredentialOrderFactory({
|
|
132
|
+
state: OrderState.TO_SIGN,
|
|
127
133
|
target_courses: TargetCourseFactory().many(1),
|
|
128
134
|
target_enrollments: [],
|
|
129
135
|
contract: ContractFactory({ student_signed_on: undefined }).one(),
|
|
@@ -146,7 +152,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
146
152
|
|
|
147
153
|
expect(screen.getByText('Ref. ' + (order.course as CourseLight).code)).toBeInTheDocument();
|
|
148
154
|
expect(
|
|
149
|
-
screen.getByText('You have to sign this training contract to
|
|
155
|
+
screen.getByText('You have to sign this training contract to finalize your subscription.'),
|
|
150
156
|
).toBeInTheDocument();
|
|
151
157
|
expect(screen.getByText('Signature required')).toBeInTheDocument();
|
|
152
158
|
expect(screen.getByRole('link', { name: 'Sign' })).toBeInTheDocument();
|
|
@@ -173,6 +179,15 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
173
179
|
{ overwriteRoutes: true },
|
|
174
180
|
);
|
|
175
181
|
|
|
182
|
+
const orderQueryParameters = {
|
|
183
|
+
course_code: order.course.code,
|
|
184
|
+
product_id: order.product_id,
|
|
185
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
186
|
+
};
|
|
187
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
188
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
189
|
+
fetchMock.get(url, [order]);
|
|
190
|
+
|
|
176
191
|
render(
|
|
177
192
|
<DashboardTest initialRoute={LearnerDashboardPaths.ORDER.replace(':orderId', order.id)} />,
|
|
178
193
|
{ wrapper: BaseJoanieAppWrapper },
|
|
@@ -186,6 +201,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
186
201
|
});
|
|
187
202
|
it('renders a writable order with a contract not signed yet', async () => {
|
|
188
203
|
const order = CredentialOrderFactory({
|
|
204
|
+
state: OrderState.TO_SIGN,
|
|
189
205
|
target_courses: TargetCourseFactory().many(1),
|
|
190
206
|
target_enrollments: [],
|
|
191
207
|
contract: null,
|
|
@@ -199,6 +215,15 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
199
215
|
{ overwriteRoutes: true },
|
|
200
216
|
);
|
|
201
217
|
|
|
218
|
+
const orderQueryParameters = {
|
|
219
|
+
course_code: order.course.code,
|
|
220
|
+
product_id: order.product_id,
|
|
221
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
222
|
+
};
|
|
223
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
224
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
225
|
+
fetchMock.get(url, [order]);
|
|
226
|
+
|
|
202
227
|
render(
|
|
203
228
|
<DashboardTest initialRoute={LearnerDashboardPaths.ORDER.replace(':orderId', order.id)} />,
|
|
204
229
|
{ wrapper: BaseJoanieAppWrapper },
|
|
@@ -210,7 +235,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
210
235
|
expect(screen.getByText('Signature required')).toBeInTheDocument();
|
|
211
236
|
expect(screen.getByRole('button', { name: 'Sign' })).toBeInTheDocument();
|
|
212
237
|
expect(
|
|
213
|
-
screen.getByText('You have to sign this training contract to
|
|
238
|
+
screen.getByText('You have to sign this training contract to finalize your subscription.'),
|
|
214
239
|
).toBeInTheDocument();
|
|
215
240
|
|
|
216
241
|
expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
|
|
@@ -220,7 +245,15 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
220
245
|
expect($enrollButtons).toHaveLength(order.target_courses[0].course_runs.length);
|
|
221
246
|
$enrollButtons.forEach(($button) => expect($button).toBeDisabled());
|
|
222
247
|
|
|
223
|
-
await expectBannerError(
|
|
248
|
+
await expectBannerError(
|
|
249
|
+
'You have to sign your training contract to finalize your subscription.',
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// The payment block should not display information about the payment schedule
|
|
253
|
+
const paymentBlock = screen.getByTestId('dashboard-item-payment-method');
|
|
254
|
+
within(paymentBlock).getByText(
|
|
255
|
+
'You will able to manage your payment installment here once your subscription is finalized.',
|
|
256
|
+
);
|
|
224
257
|
});
|
|
225
258
|
|
|
226
259
|
it('renders a writable order with a signed contract', async () => {
|
|
@@ -240,6 +273,15 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
240
273
|
{ overwriteRoutes: true },
|
|
241
274
|
);
|
|
242
275
|
|
|
276
|
+
const orderQueryParameters = {
|
|
277
|
+
course_code: order.course.code,
|
|
278
|
+
product_id: order.product_id,
|
|
279
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
280
|
+
};
|
|
281
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
282
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
283
|
+
fetchMock.get(url, [order]);
|
|
284
|
+
|
|
243
285
|
render(
|
|
244
286
|
<DashboardTest initialRoute={LearnerDashboardPaths.ORDER.replace(':orderId', order.id)} />,
|
|
245
287
|
{ wrapper: BaseJoanieAppWrapper },
|
|
@@ -252,7 +294,9 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
252
294
|
expect(screen.getByText('On going')).toBeInTheDocument();
|
|
253
295
|
expect(screen.queryByRole('button', { name: 'Sign' })).not.toBeInTheDocument();
|
|
254
296
|
expect(
|
|
255
|
-
screen.queryByText(
|
|
297
|
+
screen.queryByText(
|
|
298
|
+
'You have to sign this training contract to finalize your subscription.',
|
|
299
|
+
),
|
|
256
300
|
).not.toBeInTheDocument();
|
|
257
301
|
|
|
258
302
|
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument();
|
|
@@ -284,6 +328,15 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
284
328
|
{ overwriteRoutes: true },
|
|
285
329
|
);
|
|
286
330
|
|
|
331
|
+
const orderQueryParameters = {
|
|
332
|
+
course_code: order.course.code,
|
|
333
|
+
product_id: order.product_id,
|
|
334
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
335
|
+
};
|
|
336
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
337
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
338
|
+
fetchMock.get(url, [order]);
|
|
339
|
+
|
|
287
340
|
const DOWNLOAD_URL = `https://joanie.endpoint/api/v1.0/contracts/${
|
|
288
341
|
order.contract!.id
|
|
289
342
|
}/download/`;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fetchMock from 'fetch-mock';
|
|
4
4
|
import { act, screen, waitFor, within } from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import queryString from 'query-string';
|
|
6
7
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
7
8
|
import { DashboardTest } from 'widgets/Dashboard/components/DashboardTest';
|
|
8
9
|
import {
|
|
@@ -19,6 +20,7 @@ import { render } from 'utils/test/render';
|
|
|
19
20
|
import { BaseJoanieAppWrapper } from 'utils/test/wrappers/BaseJoanieAppWrapper';
|
|
20
21
|
|
|
21
22
|
import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
|
|
23
|
+
import { NOT_CANCELED_ORDER_STATES, OrderState } from 'types/Joanie';
|
|
22
24
|
|
|
23
25
|
jest.mock('utils/context', () => ({
|
|
24
26
|
__esModule: true,
|
|
@@ -49,9 +51,10 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
49
51
|
describe('writable', () => {
|
|
50
52
|
it('successfully sign a contract', async () => {
|
|
51
53
|
const order = CredentialOrderFactory({
|
|
54
|
+
state: OrderState.TO_SIGN,
|
|
52
55
|
target_courses: TargetCourseFactory().many(1),
|
|
53
56
|
target_enrollments: [],
|
|
54
|
-
contract: ContractFactory({ student_signed_on:
|
|
57
|
+
contract: ContractFactory({ student_signed_on: null }).one(),
|
|
55
58
|
}).one();
|
|
56
59
|
|
|
57
60
|
// learner dashboard course page do one call to course product relation per order
|
|
@@ -64,6 +67,16 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
64
67
|
{ overwriteRoutes: true },
|
|
65
68
|
);
|
|
66
69
|
|
|
70
|
+
// overwrite useProductOrder call
|
|
71
|
+
const orderQueryParameters = {
|
|
72
|
+
course_code: order.course.code,
|
|
73
|
+
product_id: order.product_id,
|
|
74
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
75
|
+
};
|
|
76
|
+
const queryParams = queryString.stringify(orderQueryParameters);
|
|
77
|
+
const url = `https://joanie.endpoint/api/v1.0/orders/?${queryParams}`;
|
|
78
|
+
fetchMock.get(url, [order]);
|
|
79
|
+
|
|
67
80
|
// mock useUnionResources calls
|
|
68
81
|
fetchMock.get('begin:https://joanie.endpoint/api/v1.0/enrollments/', {
|
|
69
82
|
results: [],
|
|
@@ -82,6 +95,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
82
95
|
`https://joanie.endpoint/api/v1.0/orders/${order.id}/submit_for_signature/`,
|
|
83
96
|
submitDeferred.promise,
|
|
84
97
|
);
|
|
98
|
+
fetchMock.post(`https://joanie.endpoint/api/v1.0/signature/notifications/`, 200);
|
|
85
99
|
|
|
86
100
|
// delay: null is needed because as we are using fake timers it would mock the timers of
|
|
87
101
|
// RTL too. See https://github.com/testing-library/user-event/issues/833.
|
|
@@ -92,11 +106,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
92
106
|
});
|
|
93
107
|
|
|
94
108
|
await expectNoSpinner('Loading orders and enrollments...');
|
|
95
|
-
|
|
96
|
-
expect(
|
|
97
|
-
await screen.findByRole('heading', { level: 5, name: product.title }),
|
|
98
|
-
).toBeInTheDocument();
|
|
99
|
-
|
|
109
|
+
await screen.findByRole('heading', { level: 5, name: product.title });
|
|
100
110
|
// Make sure the sign button is shown.
|
|
101
111
|
await user.click(screen.getByRole('link', { name: 'Sign' }));
|
|
102
112
|
|
|
@@ -212,7 +222,7 @@ describe('<DashboardItemOrder/> Contract', () => {
|
|
|
212
222
|
// We have the success message.
|
|
213
223
|
await screen.findByRole('heading', { name: 'Congratulations!' });
|
|
214
224
|
screen.getByText(
|
|
215
|
-
'You will receive an email once your contract will be fully signed. You can now
|
|
225
|
+
'You will receive an email once your contract will be fully signed. You can now finalize your subscription.',
|
|
216
226
|
);
|
|
217
227
|
const nextButton = screen.getByRole('button', { name: 'Next' });
|
|
218
228
|
|