Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(courses) add offer and price fields to courseRun #2549

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- Add offer and price fields to courseRun displayed at admin view
- Add Additional Information section for a category and
use them in a course page
- Added new page extension `MainMenuEntry`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
environ_prefix=None,
)

# Course run price currency value that would be shown on course detail page
RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY = values.Value(
"EUR",
environ_name="RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY",
environ_prefix=None,
)

# Internationalization
TIME_ZONE = "Europe/Paris"
USE_I18N = True
Expand Down
5 changes: 5 additions & 0 deletions src/frontend/js/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export interface CourseRun {
title?: string;
snapshot?: string;
display_mode: CourseRunDisplayMode;
price?: number;
price_currency?: string;
offer?: string;
certificate_price?: number;
certificate_offer?: string;
}

export enum Priority {
Expand Down
17 changes: 17 additions & 0 deletions src/frontend/js/utils/test/factories/richie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ export const CourseStateFutureOpenFactory = factory<CourseState>(() => {
});

export const CourseRunFactory = factory<CourseRun>(() => {
let offers = ['PAID', 'FREE', 'PARTIALLY_FREE', 'SUBSCRIPTION'];
const offer = offers[Math.floor(Math.random() * offers.length)];
offers = ['PAID', 'FREE', 'SUBSCRIPTION'];
const certificateOffer = offers[Math.floor(Math.random() * offers.length)];
const currency = faker.finance.currency().code;
const price = ['FREE', 'PARTIALLY_FREE'].includes(offer)
? 0
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
const cerficatePrice =
certificateOffer === 'FREE'
? 0
: parseFloat(faker.finance.amount({ min: 1, max: 100, symbol: currency, autoFormat: true }));
return {
id: faker.number.int(),
resource_link: FactoryHelper.unique(faker.internet.url),
Expand All @@ -58,6 +70,11 @@ export const CourseRunFactory = factory<CourseRun>(() => {
dashboard_link: null,
title: faker.lorem.sentence(3),
display_mode: CourseRunDisplayMode.DETAILED,
price,
price_currency: currency,
offer,
certificate_price: cerficatePrice,
certificate_offer: certificateOffer,
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,51 @@ const messages = defineMessages({
description: 'Course date of an opened course run block',
defaultMessage: 'From {startDate} {endDate, select, undefined {} other {to {endDate}}}',
},
coursePrice: {
id: 'components.SyllabusCourseRun.coursePrice',
description: 'Title of the course enrollment price section of an opened course run block',
defaultMessage: 'Enrollment price',
},
certificationPrice: {
id: 'components.SyllabusCourseRun.certificationPrice',
description: 'Title of the certification price section of an opened course run block',
defaultMessage: 'Certification price',
},
coursePaidOffer: {
id: 'components.SyllabusCourseRun.coursePaidOffer',
description: 'Message for the paid course offer of an opened course run block',
defaultMessage: 'The course content is paid.',
},
courseFreeOffer: {
id: 'components.SyllabusCourseRun.courseFreeOffer',
description: 'Message for the free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
coursePartiallyFree: {
id: 'components.SyllabusCourseRun.coursePartiallyFree',
description: 'Message for the partially free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
courseSubscriptionOffer: {
id: 'components.SyllabusCourseRun.courseSubscriptionOffer',
description: 'Message for the subscription course offer of an opened course run block',
defaultMessage: 'Subscribe to access the course content.',
},
certificatePaidOffer: {
id: 'components.SyllabusCourseRun.certificatePaidOffer',
description: 'Messagge for the paid certification offer of an opened course run block',
defaultMessage: 'The certification process is paid.',
},
certificateFreeOffer: {
id: 'components.SyllabusCourseRun.certificateFreeOffer',
description: 'Message for the free certification offer of an opened course run block',
defaultMessage: 'The certification process is free.',
},
certificateSubscriptionOffer: {
id: 'components.SyllabusCourseRun.certificateSubscriptionOffer',
description: 'Message for the subscription certification offer of an opened course run block',
defaultMessage: 'The certification process is offered through subscription.',
},
});

const OpenedCourseRun = ({
Expand All @@ -63,6 +108,44 @@ const OpenedCourseRun = ({
const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
const start = courseRun.start ? formatDate(courseRun.start) : '...';
const end = courseRun.end ? formatDate(courseRun.end) : '...';
let courseOfferMessage = null;
let certificationOfferMessage = null;
let enrollmentPrice = '';
let certificatePrice = '';

if (courseRun.offer) {
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
courseOfferMessage = {
PAID: messages.coursePaidOffer,
FREE: messages.courseFreeOffer,
PARTIALLY_FREE: messages.coursePartiallyFree,
SUBSCRIPTION: messages.courseSubscriptionOffer,
}[offer];

if ((courseRun.price ?? -1) >= 0) {
enrollmentPrice = intl.formatNumber(courseRun.price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

if (courseRun.certificate_offer) {
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
certificationOfferMessage = {
PAID: messages.certificatePaidOffer,
FREE: messages.certificateFreeOffer,
SUBSCRIPTION: messages.certificateSubscriptionOffer,
}[certificationOffer];

if ((courseRun.certificate_price ?? -1) >= 0) {
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

return (
<>
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
Expand Down Expand Up @@ -99,6 +182,30 @@ const OpenedCourseRun = ({
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
</>
)}
{courseOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.coursePrice} />
</dt>
<dd>
<FormattedMessage {...courseOfferMessage} />
<br />
{`${enrollmentPrice}`}
</dd>
</>
)}
{certificationOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.certificationPrice} />
</dt>
<dd>
<FormattedMessage {...certificationOfferMessage} />
<br />
{`${certificatePrice}`}
</dd>
</>
)}
</dl>
{findLmsBackend(courseRun.resource_link) ? (
<CourseRunEnrollment courseRun={courseRun} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,51 @@ const messages = defineMessages({
description: 'Self paced course run block with no end date',
defaultMessage: 'Available',
},
coursePrice: {
id: 'components.SyllabusCourseRunCompacted.coursePrice',
description: 'Title of the course enrollment price section of an opened course run block',
defaultMessage: 'Enrollment price',
},
certificationPrice: {
id: 'components.SyllabusCourseRunCompacted.certificationPrice',
description: 'Title of the certification price section of an opened course run block',
defaultMessage: 'Certification price',
},
coursePaidOffer: {
id: 'components.SyllabusCourseRunCompacted.coursePaidOffer',
description: 'Message for the paid course offer of an opened course run block',
defaultMessage: 'The course content is paid.',
},
courseFreeOffer: {
id: 'components.SyllabusCourseRunCompacted.courseFreeOffer',
description: 'Message for the free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
coursePartiallyFree: {
id: 'components.SyllabusCourseRunCompacted.coursePartiallyFree',
description: 'Message for the partially free course offer of an opened course run block',
defaultMessage: 'The course content is free.',
},
courseSubscriptionOffer: {
id: 'components.SyllabusCourseRunCompacted.courseSubscriptionOffer',
description: 'Message for the subscription course offer of an opened course run block',
defaultMessage: 'Subscribe to access the course content.',
},
certificatePaidOffer: {
id: 'components.SyllabusCourseRunCompacted.certificatePaidOffer',
description: 'Messagge for the paid certification offer of an opened course run block',
defaultMessage: 'The certification process is paid.',
},
certificateFreeOffer: {
id: 'components.SyllabusCourseRunCompacted.certificateFreeOffer',
description: 'Message for the free certification offer of an opened course run block',
defaultMessage: 'The certification process is free.',
},
certificateSubscriptionOffer: {
id: 'components.SyllabusCourseRunCompacted.certificateSubscriptionOffer',
description: 'Message for the subscription certification offer of an opened course run block',
defaultMessage: 'The certification process is offered through subscription.',
},
});

const OpenedSelfPacedCourseRun = ({
Expand All @@ -54,6 +99,44 @@ const OpenedSelfPacedCourseRun = ({
const intl = useIntl();
const end = courseRun.end ? formatDate(courseRun.end) : '...';
const hasEndDate = end !== '...';
let courseOfferMessage = null;
let certificationOfferMessage = null;
let enrollmentPrice = '';
let certificatePrice = '';

if (courseRun.offer) {
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
courseOfferMessage = {
PAID: messages.coursePaidOffer,
FREE: messages.courseFreeOffer,
PARTIALLY_FREE: messages.coursePartiallyFree,
SUBSCRIPTION: messages.courseSubscriptionOffer,
}[offer];

if ((courseRun.price ?? -1) >= 0) {
enrollmentPrice = intl.formatNumber(courseRun.price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

if (courseRun.certificate_offer) {
const certificationOffer = courseRun.certificate_offer.toUpperCase().replaceAll(' ', '');
certificationOfferMessage = {
PAID: messages.certificatePaidOffer,
FREE: messages.certificateFreeOffer,
SUBSCRIPTION: messages.certificateSubscriptionOffer,
}[certificationOffer];

if ((courseRun.certificate_price ?? -1) >= 0) {
certificatePrice = intl.formatNumber(courseRun.certificate_price!, {
style: 'currency',
currency: courseRun.price_currency,
});
}
}

return (
<>
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
Expand Down Expand Up @@ -83,6 +166,30 @@ const OpenedSelfPacedCourseRun = ({
<dd>{IntlHelper.getLocalizedLanguages(courseRun.languages, intl)}</dd>
</>
)}
{courseOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.coursePrice} />
</dt>
<dd>
<FormattedMessage {...courseOfferMessage} />
<br />
{`${enrollmentPrice}`}
</dd>
</>
)}
{certificationOfferMessage && (
<>
<dt>
<FormattedMessage {...messages.certificationPrice} />
</dt>
<dd>
<FormattedMessage {...certificationOfferMessage} />
<br />
{`${certificatePrice}`}
</dd>
</>
)}
</dl>
{findLmsBackend(courseRun.resource_link) ? (
<CourseRunEnrollment courseRun={courseRun} />
Expand Down
Loading