richie-education 2.34.1-dev2 → 2.34.1-dev22
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/components/ContractFrame/AbstractContractFrame.spec.tsx +3 -5
- package/js/components/Icon/index.tsx +2 -0
- package/js/hooks/useCreditCards/index.spec.tsx +3 -2
- package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.spec.tsx +1 -1
- package/js/types/index.ts +5 -0
- package/js/utils/test/factories/richie.ts +25 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +2 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/index.spec.tsx +6 -4
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +107 -0
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +107 -0
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +450 -5
- package/package.json +59 -59
- package/scss/colors/_theme.scss +10 -0
- package/scss/components/_header.scss +103 -14
- package/scss/components/_styleguide.scss +2 -1
- package/scss/components/templates/courses/cms/_program_detail.scss +71 -0
- package/scss/objects/_selector.scss +1 -0
- package/scss/settings/_variables.scss +2 -0
|
@@ -27,7 +27,7 @@ jest.mock('settings', () => ({
|
|
|
27
27
|
CONTRACT_SETTINGS: {
|
|
28
28
|
...jest.requireActual('settings').CONTRACT_SETTINGS,
|
|
29
29
|
pollInterval: 10,
|
|
30
|
-
dummySignatureSignTimeout:
|
|
30
|
+
dummySignatureSignTimeout: 50,
|
|
31
31
|
},
|
|
32
32
|
}));
|
|
33
33
|
|
|
@@ -204,12 +204,10 @@ describe('<AbstractContractFrame />', () => {
|
|
|
204
204
|
await user.click(button);
|
|
205
205
|
|
|
206
206
|
// The dummy interface should be loading
|
|
207
|
-
screen.
|
|
207
|
+
await screen.findByRole('heading', { name: 'Signing the contract ...' });
|
|
208
208
|
|
|
209
209
|
// Then the signature check polling should be started
|
|
210
|
-
await
|
|
211
|
-
expect(screen.getByRole('heading', { name: 'Verifying signature ...' })).toBeInTheDocument();
|
|
212
|
-
});
|
|
210
|
+
await screen.findByRole('heading', { name: 'Verifying signature ...' });
|
|
213
211
|
expect(
|
|
214
212
|
screen.getByText(
|
|
215
213
|
'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
|
|
@@ -35,6 +35,7 @@ export enum IconTypeEnum {
|
|
|
35
35
|
CHEVRON_RIGHT_OUTLINE = 'icon-chevron-right-outline',
|
|
36
36
|
CHEVRON_UP_OUTLINE = 'icon-chevron-up-outline',
|
|
37
37
|
CLOCK = 'icon-clock',
|
|
38
|
+
COURSES = 'icon-courses',
|
|
38
39
|
CREDIT_CARD = 'icon-creditCard',
|
|
39
40
|
CROSS = 'icon-cross',
|
|
40
41
|
DURATION = 'icon-duration',
|
|
@@ -49,6 +50,7 @@ export enum IconTypeEnum {
|
|
|
49
50
|
LOGOUT_SQUARE = 'icon-logout-square',
|
|
50
51
|
MAGNIFYING_GLASS = 'icon-magnifying-glass',
|
|
51
52
|
MENU = 'icon-menu',
|
|
53
|
+
MONEY = 'icon-money',
|
|
52
54
|
MORE = 'icon-more',
|
|
53
55
|
ORG = 'icon-org',
|
|
54
56
|
PACE = 'icon-pace',
|
|
@@ -208,8 +208,9 @@ describe('useCreditCards', () => {
|
|
|
208
208
|
await act(async () => {
|
|
209
209
|
responseDeferred.resolve({});
|
|
210
210
|
});
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
await waitFor(() => {
|
|
212
|
+
expect(result.current.states.updating).toBe(false);
|
|
213
|
+
});
|
|
213
214
|
expect(result.current.states.isPending).toBe(false);
|
|
214
215
|
expect(result.current.states.error).toBe(undefined);
|
|
215
216
|
});
|
|
@@ -181,7 +181,7 @@ describe('<DahsboardEditCreditCard/>', () => {
|
|
|
181
181
|
await screen.findByText('Credit cards');
|
|
182
182
|
|
|
183
183
|
// The title is correctly updated.
|
|
184
|
-
screen.
|
|
184
|
+
await screen.findByRole('heading', {
|
|
185
185
|
level: 6,
|
|
186
186
|
name: creditCardUpdated.title,
|
|
187
187
|
});
|
package/js/types/index.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface CourseRun {
|
|
|
35
35
|
title?: string;
|
|
36
36
|
snapshot?: string;
|
|
37
37
|
display_mode: CourseRunDisplayMode;
|
|
38
|
+
price?: number;
|
|
39
|
+
price_currency?: string;
|
|
40
|
+
offer?: string;
|
|
41
|
+
certificate_price?: number;
|
|
42
|
+
certificate_offer?: string;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export enum Priority {
|
|
@@ -45,7 +45,27 @@ export const CourseStateFutureOpenFactory = factory<CourseState>(() => {
|
|
|
45
45
|
};
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
enum OfferType {
|
|
49
|
+
PAID = 'PAID',
|
|
50
|
+
FREE = 'FREE',
|
|
51
|
+
PARTIALLY_FREE = 'PARTIALLY_FREE',
|
|
52
|
+
SUBSCRIPTION = 'SUBSCRIPTION',
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
export const CourseRunFactory = factory<CourseRun>(() => {
|
|
56
|
+
const offerValues = Object.values(OfferType);
|
|
57
|
+
const offer = offerValues[Math.floor(Math.random() * offerValues.length)];
|
|
58
|
+
const certificateOfferValues = [OfferType.PAID, OfferType.FREE, OfferType.SUBSCRIPTION];
|
|
59
|
+
const certificateOffer =
|
|
60
|
+
certificateOfferValues[Math.floor(Math.random() * certificateOfferValues.length)];
|
|
61
|
+
const currency = faker.finance.currency().code;
|
|
62
|
+
const price = [OfferType.FREE, OfferType.PARTIALLY_FREE].includes(offer)
|
|
63
|
+
? 0
|
|
64
|
+
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
|
|
65
|
+
const certificatePrice =
|
|
66
|
+
certificateOffer === OfferType.FREE
|
|
67
|
+
? 0
|
|
68
|
+
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
|
|
49
69
|
return {
|
|
50
70
|
id: faker.number.int(),
|
|
51
71
|
resource_link: FactoryHelper.unique(faker.internet.url),
|
|
@@ -58,6 +78,11 @@ export const CourseRunFactory = factory<CourseRun>(() => {
|
|
|
58
78
|
dashboard_link: null,
|
|
59
79
|
title: faker.lorem.sentence(3),
|
|
60
80
|
display_mode: CourseRunDisplayMode.DETAILED,
|
|
81
|
+
price,
|
|
82
|
+
price_currency: currency,
|
|
83
|
+
offer,
|
|
84
|
+
certificate_price: certificatePrice,
|
|
85
|
+
certificate_offer: certificateOffer,
|
|
61
86
|
};
|
|
62
87
|
});
|
|
63
88
|
|
|
@@ -139,11 +139,12 @@ const EnrollableCourseRunList = ({ courseRuns, order }: Props) => {
|
|
|
139
139
|
<ol className="course-runs-list">
|
|
140
140
|
{Children.toArray(
|
|
141
141
|
courseRuns.map((courseRun) => (
|
|
142
|
-
<li className="course-runs-item form-field">
|
|
142
|
+
<li key={`${order.id}|${courseRun.id}`} className="course-runs-item form-field">
|
|
143
143
|
<input
|
|
144
144
|
className="form-field__radio-input"
|
|
145
145
|
type="radio"
|
|
146
146
|
id={`${order.id}|${courseRun.id}`}
|
|
147
|
+
data-testid={`radio-input-${order.id}-${courseRun.id}`}
|
|
147
148
|
name={order.id}
|
|
148
149
|
disabled={needsSignature}
|
|
149
150
|
aria-label={intl.formatMessage(messages.ariaSelectCourseRun, {
|
|
@@ -166,11 +166,13 @@ describe('CourseProductCourseRuns', () => {
|
|
|
166
166
|
);
|
|
167
167
|
|
|
168
168
|
// - A radio input
|
|
169
|
-
screen.
|
|
170
|
-
|
|
169
|
+
const $input = screen.getByTestId(`radio-input-${order.id}-${courseRun.id}`);
|
|
170
|
+
expect($input).toHaveAttribute('type', 'radio');
|
|
171
|
+
expect($input).toHaveAccessibleName(
|
|
172
|
+
`Select course run from ${dateFormatter.format(
|
|
171
173
|
new Date(courseRun.start),
|
|
172
174
|
)} to ${dateFormatter.format(new Date(courseRun.end))}.`,
|
|
173
|
-
|
|
175
|
+
);
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
// A call to action should be displayed
|
|
@@ -215,7 +217,7 @@ describe('CourseProductCourseRuns', () => {
|
|
|
215
217
|
});
|
|
216
218
|
|
|
217
219
|
// A spinner should be displayed
|
|
218
|
-
screen.
|
|
220
|
+
await screen.findByRole('status', { name: 'Enrolling...' });
|
|
219
221
|
|
|
220
222
|
await act(async () => {
|
|
221
223
|
enrollmentDeferred.resolve(HttpStatusCode.OK);
|
|
@@ -46,6 +46,51 @@ const messages = defineMessages({
|
|
|
46
46
|
description: 'Course date of an opened course run block',
|
|
47
47
|
defaultMessage: 'From {startDate} {endDate, select, undefined {} other {to {endDate}}}',
|
|
48
48
|
},
|
|
49
|
+
coursePrice: {
|
|
50
|
+
id: 'components.SyllabusCourseRun.coursePrice',
|
|
51
|
+
description: 'Title of the course enrollment price section of an opened course run block',
|
|
52
|
+
defaultMessage: 'Enrollment price',
|
|
53
|
+
},
|
|
54
|
+
certificationPrice: {
|
|
55
|
+
id: 'components.SyllabusCourseRun.certificationPrice',
|
|
56
|
+
description: 'Title of the certification price section of an opened course run block',
|
|
57
|
+
defaultMessage: 'Certification price',
|
|
58
|
+
},
|
|
59
|
+
coursePaidOffer: {
|
|
60
|
+
id: 'components.SyllabusCourseRun.coursePaidOffer',
|
|
61
|
+
description: 'Message for the paid course offer of an opened course run block',
|
|
62
|
+
defaultMessage: 'The course content is paid.',
|
|
63
|
+
},
|
|
64
|
+
courseFreeOffer: {
|
|
65
|
+
id: 'components.SyllabusCourseRun.courseFreeOffer',
|
|
66
|
+
description: 'Message for the free course offer of an opened course run block',
|
|
67
|
+
defaultMessage: 'The course content is free.',
|
|
68
|
+
},
|
|
69
|
+
coursePartiallyFree: {
|
|
70
|
+
id: 'components.SyllabusCourseRun.coursePartiallyFree',
|
|
71
|
+
description: 'Message for the partially free course offer of an opened course run block',
|
|
72
|
+
defaultMessage: 'The course content is free.',
|
|
73
|
+
},
|
|
74
|
+
courseSubscriptionOffer: {
|
|
75
|
+
id: 'components.SyllabusCourseRun.courseSubscriptionOffer',
|
|
76
|
+
description: 'Message for the subscription course offer of an opened course run block',
|
|
77
|
+
defaultMessage: 'Subscribe to access the course content.',
|
|
78
|
+
},
|
|
79
|
+
certificatePaidOffer: {
|
|
80
|
+
id: 'components.SyllabusCourseRun.certificatePaidOffer',
|
|
81
|
+
description: 'Messagge for the paid certification offer of an opened course run block',
|
|
82
|
+
defaultMessage: 'The certification process is paid.',
|
|
83
|
+
},
|
|
84
|
+
certificateFreeOffer: {
|
|
85
|
+
id: 'components.SyllabusCourseRun.certificateFreeOffer',
|
|
86
|
+
description: 'Message for the free certification offer of an opened course run block',
|
|
87
|
+
defaultMessage: 'The certification process is free.',
|
|
88
|
+
},
|
|
89
|
+
certificateSubscriptionOffer: {
|
|
90
|
+
id: 'components.SyllabusCourseRun.certificateSubscriptionOffer',
|
|
91
|
+
description: 'Message for the subscription certification offer of an opened course run block',
|
|
92
|
+
defaultMessage: 'The certification process is offered through subscription.',
|
|
93
|
+
},
|
|
49
94
|
});
|
|
50
95
|
|
|
51
96
|
const OpenedCourseRun = ({
|
|
@@ -63,6 +108,44 @@ const OpenedCourseRun = ({
|
|
|
63
108
|
const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
|
|
64
109
|
const start = courseRun.start ? formatDate(courseRun.start) : '...';
|
|
65
110
|
const end = courseRun.end ? formatDate(courseRun.end) : '...';
|
|
111
|
+
let courseOfferMessage = null;
|
|
112
|
+
let certificationOfferMessage = null;
|
|
113
|
+
let enrollmentPrice = '';
|
|
114
|
+
let certificatePrice = '';
|
|
115
|
+
|
|
116
|
+
if (courseRun.offer) {
|
|
117
|
+
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
118
|
+
courseOfferMessage = {
|
|
119
|
+
PAID: messages.coursePaidOffer,
|
|
120
|
+
FREE: messages.courseFreeOffer,
|
|
121
|
+
PARTIALLY_FREE: messages.coursePartiallyFree,
|
|
122
|
+
SUBSCRIPTION: messages.courseSubscriptionOffer,
|
|
123
|
+
}[offer];
|
|
124
|
+
|
|
125
|
+
if ((courseRun.price ?? -1) >= 0) {
|
|
126
|
+
enrollmentPrice = intl.formatNumber(courseRun.price!, {
|
|
127
|
+
style: 'currency',
|
|
128
|
+
currency: courseRun.price_currency,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (courseRun.certificate_offer) {
|
|
134
|
+
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
|
|
135
|
+
certificationOfferMessage = {
|
|
136
|
+
PAID: messages.certificatePaidOffer,
|
|
137
|
+
FREE: messages.certificateFreeOffer,
|
|
138
|
+
SUBSCRIPTION: messages.certificateSubscriptionOffer,
|
|
139
|
+
}[certificationOffer];
|
|
140
|
+
|
|
141
|
+
if ((courseRun.certificate_price ?? -1) >= 0) {
|
|
142
|
+
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
|
|
143
|
+
style: 'currency',
|
|
144
|
+
currency: courseRun.price_currency,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
66
149
|
return (
|
|
67
150
|
<>
|
|
68
151
|
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
|
|
@@ -99,6 +182,30 @@ const OpenedCourseRun = ({
|
|
|
99
182
|
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
|
|
100
183
|
</>
|
|
101
184
|
)}
|
|
185
|
+
{courseOfferMessage && (
|
|
186
|
+
<>
|
|
187
|
+
<dt>
|
|
188
|
+
<FormattedMessage {...messages.coursePrice} />
|
|
189
|
+
</dt>
|
|
190
|
+
<dd>
|
|
191
|
+
<FormattedMessage {...courseOfferMessage} />
|
|
192
|
+
<br />
|
|
193
|
+
{enrollmentPrice}
|
|
194
|
+
</dd>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
{certificationOfferMessage && (
|
|
198
|
+
<>
|
|
199
|
+
<dt>
|
|
200
|
+
<FormattedMessage {...messages.certificationPrice} />
|
|
201
|
+
</dt>
|
|
202
|
+
<dd>
|
|
203
|
+
<FormattedMessage {...certificationOfferMessage} />
|
|
204
|
+
<br />
|
|
205
|
+
{certificatePrice}
|
|
206
|
+
</dd>
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
102
209
|
</dl>
|
|
103
210
|
{findLmsBackend(courseRun.resource_link) ? (
|
|
104
211
|
<CourseRunEnrollment courseRun={courseRun} />
|
|
@@ -41,6 +41,51 @@ const messages = defineMessages({
|
|
|
41
41
|
description: 'Self paced course run block with no end date',
|
|
42
42
|
defaultMessage: 'Available',
|
|
43
43
|
},
|
|
44
|
+
coursePrice: {
|
|
45
|
+
id: 'components.SyllabusCourseRunCompacted.coursePrice',
|
|
46
|
+
description: 'Title of the course enrollment price section of an opened course run block',
|
|
47
|
+
defaultMessage: 'Enrollment price',
|
|
48
|
+
},
|
|
49
|
+
certificationPrice: {
|
|
50
|
+
id: 'components.SyllabusCourseRunCompacted.certificationPrice',
|
|
51
|
+
description: 'Title of the certification price section of an opened course run block',
|
|
52
|
+
defaultMessage: 'Certification price',
|
|
53
|
+
},
|
|
54
|
+
coursePaidOffer: {
|
|
55
|
+
id: 'components.SyllabusCourseRunCompacted.coursePaidOffer',
|
|
56
|
+
description: 'Message for the paid course offer of an opened course run block',
|
|
57
|
+
defaultMessage: 'The course content is paid.',
|
|
58
|
+
},
|
|
59
|
+
courseFreeOffer: {
|
|
60
|
+
id: 'components.SyllabusCourseRunCompacted.courseFreeOffer',
|
|
61
|
+
description: 'Message for the free course offer of an opened course run block',
|
|
62
|
+
defaultMessage: 'The course content is free.',
|
|
63
|
+
},
|
|
64
|
+
coursePartiallyFree: {
|
|
65
|
+
id: 'components.SyllabusCourseRunCompacted.coursePartiallyFree',
|
|
66
|
+
description: 'Message for the partially free course offer of an opened course run block',
|
|
67
|
+
defaultMessage: 'The course content is free.',
|
|
68
|
+
},
|
|
69
|
+
courseSubscriptionOffer: {
|
|
70
|
+
id: 'components.SyllabusCourseRunCompacted.courseSubscriptionOffer',
|
|
71
|
+
description: 'Message for the subscription course offer of an opened course run block',
|
|
72
|
+
defaultMessage: 'Subscribe to access the course content.',
|
|
73
|
+
},
|
|
74
|
+
certificatePaidOffer: {
|
|
75
|
+
id: 'components.SyllabusCourseRunCompacted.certificatePaidOffer',
|
|
76
|
+
description: 'Messagge for the paid certification offer of an opened course run block',
|
|
77
|
+
defaultMessage: 'The certification process is paid.',
|
|
78
|
+
},
|
|
79
|
+
certificateFreeOffer: {
|
|
80
|
+
id: 'components.SyllabusCourseRunCompacted.certificateFreeOffer',
|
|
81
|
+
description: 'Message for the free certification offer of an opened course run block',
|
|
82
|
+
defaultMessage: 'The certification process is free.',
|
|
83
|
+
},
|
|
84
|
+
certificateSubscriptionOffer: {
|
|
85
|
+
id: 'components.SyllabusCourseRunCompacted.certificateSubscriptionOffer',
|
|
86
|
+
description: 'Message for the subscription certification offer of an opened course run block',
|
|
87
|
+
defaultMessage: 'The certification process is offered through subscription.',
|
|
88
|
+
},
|
|
44
89
|
});
|
|
45
90
|
|
|
46
91
|
const OpenedSelfPacedCourseRun = ({
|
|
@@ -54,6 +99,44 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
54
99
|
const intl = useIntl();
|
|
55
100
|
const end = courseRun.end ? formatDate(courseRun.end) : '...';
|
|
56
101
|
const hasEndDate = end !== '...';
|
|
102
|
+
let courseOfferMessage = null;
|
|
103
|
+
let certificationOfferMessage = null;
|
|
104
|
+
let enrollmentPrice = '';
|
|
105
|
+
let certificatePrice = '';
|
|
106
|
+
|
|
107
|
+
if (courseRun.offer) {
|
|
108
|
+
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
109
|
+
courseOfferMessage = {
|
|
110
|
+
PAID: messages.coursePaidOffer,
|
|
111
|
+
FREE: messages.courseFreeOffer,
|
|
112
|
+
PARTIALLY_FREE: messages.coursePartiallyFree,
|
|
113
|
+
SUBSCRIPTION: messages.courseSubscriptionOffer,
|
|
114
|
+
}[offer];
|
|
115
|
+
|
|
116
|
+
if ((courseRun.price ?? -1) >= 0) {
|
|
117
|
+
enrollmentPrice = intl.formatNumber(courseRun.price!, {
|
|
118
|
+
style: 'currency',
|
|
119
|
+
currency: courseRun.price_currency,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (courseRun.certificate_offer) {
|
|
125
|
+
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
|
|
126
|
+
certificationOfferMessage = {
|
|
127
|
+
PAID: messages.certificatePaidOffer,
|
|
128
|
+
FREE: messages.certificateFreeOffer,
|
|
129
|
+
SUBSCRIPTION: messages.certificateSubscriptionOffer,
|
|
130
|
+
}[certificationOffer];
|
|
131
|
+
|
|
132
|
+
if ((courseRun.certificate_price ?? -1) >= 0) {
|
|
133
|
+
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
|
|
134
|
+
style: 'currency',
|
|
135
|
+
currency: courseRun.price_currency,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
57
140
|
return (
|
|
58
141
|
<>
|
|
59
142
|
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
|
|
@@ -83,6 +166,30 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
83
166
|
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
|
|
84
167
|
</>
|
|
85
168
|
)}
|
|
169
|
+
{courseOfferMessage && (
|
|
170
|
+
<>
|
|
171
|
+
<dt>
|
|
172
|
+
<FormattedMessage {...messages.coursePrice} />
|
|
173
|
+
</dt>
|
|
174
|
+
<dd>
|
|
175
|
+
<FormattedMessage {...courseOfferMessage} />
|
|
176
|
+
<br />
|
|
177
|
+
{enrollmentPrice}
|
|
178
|
+
</dd>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
{certificationOfferMessage && (
|
|
182
|
+
<>
|
|
183
|
+
<dt>
|
|
184
|
+
<FormattedMessage {...messages.certificationPrice} />
|
|
185
|
+
</dt>
|
|
186
|
+
<dd>
|
|
187
|
+
<FormattedMessage {...certificationOfferMessage} />
|
|
188
|
+
<br />
|
|
189
|
+
{certificatePrice}
|
|
190
|
+
</dd>
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
86
193
|
</dl>
|
|
87
194
|
{findLmsBackend(courseRun.resource_link) ? (
|
|
88
195
|
<CourseRunEnrollment courseRun={courseRun} />
|