richie-education 3.1.3-dev12 → 3.1.3-dev17

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.
Files changed (73) hide show
  1. package/js/api/joanie.ts +8 -8
  2. package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +12 -11
  3. package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
  4. package/js/components/CourseGlimpse/utils.ts +28 -22
  5. package/js/components/CourseGlimpseList/utils.ts +2 -2
  6. package/js/components/PurchaseButton/index.tsx +3 -3
  7. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +3 -3
  8. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +5 -3
  9. package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
  10. package/js/components/SaleTunnel/index.spec.tsx +76 -63
  11. package/js/components/SaleTunnel/index.tsx +2 -2
  12. package/js/components/TeacherDashboardCourseList/index.spec.tsx +3 -3
  13. package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
  14. package/js/hooks/useContractArchive/index.ts +3 -3
  15. package/js/hooks/useCourseProductUnion/index.spec.tsx +16 -16
  16. package/js/hooks/useCourseProductUnion/index.ts +7 -7
  17. package/js/hooks/useCourseProducts.ts +4 -4
  18. package/js/hooks/useDefaultOrganizationId/index.tsx +4 -4
  19. package/js/hooks/useOffering/index.ts +32 -0
  20. package/js/hooks/useTeacherCoursesSearch/index.tsx +4 -4
  21. package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
  22. package/js/pages/DashboardCourses/index.spec.tsx +17 -14
  23. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +11 -8
  24. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +6 -3
  25. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
  26. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -10
  27. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
  28. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +5 -5
  29. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -8
  30. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
  31. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
  32. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
  33. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
  34. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -21
  35. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +19 -13
  36. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -11
  37. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
  38. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +6 -3
  39. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
  40. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
  41. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +7 -4
  42. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
  43. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -5
  44. package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +55 -55
  45. package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
  46. package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
  47. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
  48. package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
  49. package/js/pages/TeacherDashboardTraining/index.spec.tsx +25 -25
  50. package/js/pages/TeacherDashboardTraining/index.tsx +16 -12
  51. package/js/types/Joanie.ts +27 -20
  52. package/js/utils/test/factories/joanie.ts +14 -11
  53. package/js/utils/test/mockCourseProductWithOrder.ts +4 -4
  54. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
  55. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +1 -1
  56. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +3 -3
  57. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +1 -1
  58. package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +4 -4
  59. package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +1 -1
  60. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +23 -23
  61. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -4
  62. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +20 -17
  63. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +22 -16
  64. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
  65. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -3
  66. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
  67. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +14 -14
  68. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +105 -75
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +6 -4
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +27 -20
  71. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
  72. package/package.json +1 -1
  73. package/js/hooks/useOffer/index.ts +0 -32
package/js/api/joanie.ts CHANGED
@@ -127,8 +127,8 @@ export const getRoutes = () => {
127
127
  },
128
128
  organizations: {
129
129
  get: `${baseUrl}/organizations/:id/`,
130
- offers: {
131
- get: `${baseUrl}/organizations/:organization_id/offers/:id/`,
130
+ offerings: {
131
+ get: `${baseUrl}/organizations/:organization_id/offerings/:id/`,
132
132
  },
133
133
  courses: {
134
134
  get: `${baseUrl}/organizations/:organization_id/courses/:id/`,
@@ -156,8 +156,8 @@ export const getRoutes = () => {
156
156
  courseRuns: {
157
157
  get: `${baseUrl}/course-runs/:id/`,
158
158
  },
159
- offers: {
160
- get: `${baseUrl}/offers/:id/`,
159
+ offerings: {
160
+ get: `${baseUrl}/offerings/:id/`,
161
161
  },
162
162
  contractDefinitions: {
163
163
  previewTemplate: `${baseUrl}/contract_definitions/:id/preview_template/`,
@@ -470,12 +470,12 @@ const API = (): Joanie.API => {
470
470
  ).then(checkStatus);
471
471
  },
472
472
  },
473
- offers: {
474
- get: (filters?: Joanie.OfferQueryFilters) => {
473
+ offerings: {
474
+ get: (filters?: Joanie.OfferingQueryFilters) => {
475
475
  return fetchWithJWT(
476
476
  filters?.organization_id
477
- ? buildApiUrl(ROUTES.organizations.offers.get, filters)
478
- : buildApiUrl(ROUTES.offers.get, filters),
477
+ ? buildApiUrl(ROUTES.organizations.offerings.get, filters)
478
+ : buildApiUrl(ROUTES.offerings.get, filters),
479
479
  ).then(checkStatus);
480
480
  },
481
481
  },
@@ -6,10 +6,10 @@ import { IntlProvider } from 'react-intl';
6
6
  import fetchMock from 'fetch-mock';
7
7
  import { QueryStateFactory } from 'utils/test/factories/reactQuery';
8
8
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
9
- import { ContractFactory, OfferFactory, OrganizationFactory } from 'utils/test/factories/joanie';
9
+ import { ContractFactory, OfferingFactory, OrganizationFactory } from 'utils/test/factories/joanie';
10
10
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
11
11
  import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
12
- import { isOffer } from 'types/Joanie';
12
+ import { isOffering } from 'types/Joanie';
13
13
  import { Props } from './AbstractContractFrame';
14
14
  import { OrganizationContractFrame } from '.';
15
15
 
@@ -73,28 +73,29 @@ describe('OrganizationContractFrame', () => {
73
73
 
74
74
  it.each([
75
75
  {
76
- label: 'contractList: undefined, offer: undefined',
76
+ label: 'contractList: undefined, offering: undefined',
77
77
  contractList: undefined,
78
- offer: undefined,
78
+ offering: undefined,
79
79
  },
80
80
  {
81
- label: 'contractList: 2 Contract, offer: undefined',
81
+ label: 'contractList: 2 Contract, offering: undefined',
82
82
  contractList: ContractFactory().many(2),
83
- offer: undefined,
83
+ offering: undefined,
84
84
  },
85
85
  {
86
- label: 'contractList: undefined, offer: one Offer',
86
+ label: 'contractList: undefined, offering: one Offering',
87
87
  contractList: undefined,
88
- offer: OfferFactory().one(),
88
+ offering: OfferingFactory().one(),
89
89
  },
90
90
  ])(
91
91
  'should implement AbstractContractFrame for organization and $label',
92
- async ({ contractList, offer }) => {
92
+ async ({ contractList, offering }) => {
93
93
  const organization = OrganizationFactory().one();
94
94
  const contracts = contractList || ContractFactory().many(2);
95
95
  const isOpen = faker.datatype.boolean();
96
96
 
97
- const invitationLinkQueryString = offer && isOffer(offer) ? `?offer_ids=${offer.id}` : '';
97
+ const invitationLinkQueryString =
98
+ offering && isOffering(offering) ? `?offering_ids=${offering.id}` : '';
98
99
  const expectedUrls = {
99
100
  getInvitationLink: `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts-signature-link/${invitationLinkQueryString}`,
100
101
  checkSignature: `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?id=${contracts[0].id}&id=${contracts[1].id}`,
@@ -126,7 +127,7 @@ describe('OrganizationContractFrame', () => {
126
127
  <Wrapper client={client}>
127
128
  <OrganizationContractFrame
128
129
  organizationId={organization.id}
129
- offerIds={offer ? [offer.id] : undefined}
130
+ offeringIds={offering ? [offering.id] : undefined}
130
131
  isOpen={isOpen}
131
132
  onDone={handleDone}
132
133
  onClose={handleClose}
@@ -4,17 +4,17 @@ import { useJoanieApi } from 'contexts/JoanieApiContext';
4
4
  import AbstractContractFrame, {
5
5
  AbstractProps,
6
6
  } from 'components/ContractFrame/AbstractContractFrame';
7
- import { Contract, Offer } from 'types/Joanie';
7
+ import { Contract, Offering } from 'types/Joanie';
8
8
 
9
9
  interface Props extends AbstractProps {
10
10
  contractIds?: Contract['id'][];
11
11
  organizationId: string;
12
- offerIds?: Offer['id'][];
12
+ offeringIds?: Offering['id'][];
13
13
  }
14
14
 
15
15
  const OrganizationContractFrame = ({
16
16
  organizationId,
17
- offerIds = [],
17
+ offeringIds = [],
18
18
  contractIds,
19
19
  onDone,
20
20
  ...props
@@ -29,7 +29,7 @@ const OrganizationContractFrame = ({
29
29
  be signed. We need to keep track of these ids to check if all contracts have been signed.
30
30
  */
31
31
  const response = await api.organizations.contracts.getSignatureLinks({
32
- offer_ids: offerIds,
32
+ offering_ids: offeringIds,
33
33
  organization_id: organizationId,
34
34
  contracts_ids: contractIds,
35
35
  });
@@ -6,18 +6,23 @@ import {
6
6
  Course as RichieCourse,
7
7
  isRichieCourse,
8
8
  } from 'types/Course';
9
- import { CourseListItem as JoanieCourse, OfferLight, isOffer, ProductType } from 'types/Joanie';
9
+ import {
10
+ CourseListItem as JoanieCourse,
11
+ OfferingLight,
12
+ isOffering,
13
+ ProductType,
14
+ } from 'types/Joanie';
10
15
  import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherDashboardPaths';
11
16
  import { CourseGlimpseCourse } from '.';
12
17
 
13
- const getCourseGlimpsePropsFromOffer = (
14
- offer: OfferLight,
18
+ const getCourseGlimpsePropsFromOffering = (
19
+ offering: OfferingLight,
15
20
  intl: IntlShape,
16
21
  organizationId?: string,
17
22
  ): CourseGlimpseCourse => {
18
23
  const courseRouteParams = {
19
- courseId: offer.course.id,
20
- offerId: offer.id,
24
+ courseId: offering.course.id,
25
+ offeringId: offering.id,
21
26
  };
22
27
  const courseRoute = organizationId
23
28
  ? generatePath(TeacherDashboardPaths.ORGANIZATION_PRODUCT, {
@@ -26,27 +31,28 @@ const getCourseGlimpsePropsFromOffer = (
26
31
  })
27
32
  : generatePath(TeacherDashboardPaths.COURSE_PRODUCT, courseRouteParams);
28
33
  return {
29
- id: offer.id,
30
- code: offer.course.code,
31
- title: offer.product.title,
32
- cover_image: offer.course.cover
34
+ id: offering.id,
35
+ code: offering.course.code,
36
+ title: offering.product.title,
37
+ cover_image: offering.course.cover
33
38
  ? {
34
- src: offer.course.cover.src,
39
+ src: offering.course.cover.src,
35
40
  }
36
41
  : null,
37
42
  organization: {
38
- title: offer.organizations[0].title,
39
- image: offer.organizations[0].logo || null,
43
+ title: offering.organizations[0].title,
44
+ image: offering.organizations[0].logo || null,
40
45
  },
41
- product_id: offer.product.id,
46
+ product_id: offering.product.id,
42
47
  course_route: courseRoute,
43
- state: offer.product.state,
48
+ state: offering.product.state,
44
49
  certificate_offer:
45
- offer.product.type === ProductType.CERTIFICATE ? CourseCertificateOffer.PAID : null,
46
- offer: offer.product.type === ProductType.CREDENTIAL ? CourseOffer.PAID : null,
47
- certificate_price: offer.product.type === ProductType.CERTIFICATE ? offer.product.price : null,
48
- price: offer.product.type === ProductType.CREDENTIAL ? offer.product.price : null,
49
- price_currency: offer.product.price_currency,
50
+ offering.product.type === ProductType.CERTIFICATE ? CourseCertificateOffer.PAID : null,
51
+ offer: offering.product.type === ProductType.CREDENTIAL ? CourseOffer.PAID : null,
52
+ certificate_price:
53
+ offering.product.type === ProductType.CERTIFICATE ? offering.product.price : null,
54
+ price: offering.product.type === ProductType.CREDENTIAL ? offering.product.price : null,
55
+ price_currency: offering.product.price_currency,
50
56
  };
51
57
  };
52
58
 
@@ -112,12 +118,12 @@ const getCourseGlimpsePropsFromJoanieCourse = (
112
118
  };
113
119
 
114
120
  export const getCourseGlimpseProps = (
115
- course: RichieCourse | (JoanieCourse | OfferLight),
121
+ course: RichieCourse | (JoanieCourse | OfferingLight),
116
122
  intl?: IntlShape,
117
123
  organizationId?: string,
118
124
  ): CourseGlimpseCourse => {
119
- if (isOffer(course)) {
120
- return getCourseGlimpsePropsFromOffer(course, intl!, organizationId);
125
+ if (isOffering(course)) {
126
+ return getCourseGlimpsePropsFromOffering(course, intl!, organizationId);
121
127
  }
122
128
 
123
129
  if (isRichieCourse(course)) {
@@ -1,10 +1,10 @@
1
1
  import { IntlShape } from 'react-intl';
2
- import { OfferLight, CourseListItem as JoanieCourse } from 'types/Joanie';
2
+ import { OfferingLight, CourseListItem as JoanieCourse } from 'types/Joanie';
3
3
  import { Course as RichieCourse } from 'types/Course';
4
4
  import { CourseGlimpseCourse, getCourseGlimpseProps } from 'components/CourseGlimpse';
5
5
 
6
6
  export const getCourseGlimpseListProps = (
7
- courses: RichieCourse[] | (JoanieCourse | OfferLight)[],
7
+ courses: RichieCourse[] | (JoanieCourse | OfferingLight)[],
8
8
  intl?: IntlShape,
9
9
  organizationId?: string,
10
10
  ): CourseGlimpseCourse[] => {
@@ -42,7 +42,7 @@ const messages = defineMessages({
42
42
 
43
43
  interface PurchaseButtonPropsBase {
44
44
  product: Joanie.CredentialProduct | Joanie.CertificateProduct;
45
- offer?: Joanie.Offer;
45
+ offering?: Joanie.Offering;
46
46
  isWithdrawable: boolean;
47
47
  disabled?: boolean;
48
48
  className?: string;
@@ -66,7 +66,7 @@ interface CertificatePurchaseButtonProps extends PurchaseButtonPropsBase {
66
66
  const PurchaseButton = ({
67
67
  product,
68
68
  course,
69
- offer,
69
+ offering,
70
70
  enrollment,
71
71
  isWithdrawable,
72
72
  organizations,
@@ -140,7 +140,7 @@ const PurchaseButton = ({
140
140
  {...saleTunnelModal}
141
141
  product={product}
142
142
  organizations={organizations}
143
- offer={offer}
143
+ offering={offering}
144
144
  enrollment={enrollment}
145
145
  course={course}
146
146
  isWithdrawable={isWithdrawable}
@@ -10,7 +10,7 @@ import {
10
10
  } from 'react';
11
11
  import { SaleTunnelSponsors } from 'components/SaleTunnel/Sponsors/SaleTunnelSponsors';
12
12
  import { SaleTunnelProps } from 'components/SaleTunnel/index';
13
- import { Address, Offer, CreditCard, Order, OrderState, Product } from 'types/Joanie';
13
+ import { Address, Offering, CreditCard, Order, OrderState, Product } from 'types/Joanie';
14
14
  import useProductOrder from 'hooks/useProductOrder';
15
15
  import { SaleTunnelSuccess } from 'components/SaleTunnel/SaleTunnelSuccess';
16
16
  import WebAnalyticsAPIHandler from 'api/web-analytics';
@@ -26,7 +26,7 @@ export interface SaleTunnelContextType {
26
26
  order?: Order;
27
27
  product: Product;
28
28
  webAnalyticsEventKey: string;
29
- offer?: Offer;
29
+ offering?: Offering;
30
30
 
31
31
  // internal
32
32
  step: SaleTunnelStep;
@@ -114,7 +114,7 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
114
114
  webAnalyticsEventKey: props.eventKey,
115
115
  order,
116
116
  product: props.product,
117
- offer: props.offer,
117
+ offering: props.offering,
118
118
  props,
119
119
  billingAddress,
120
120
  setBillingAddress,
@@ -8,6 +8,7 @@ import useOpenEdxProfile from 'hooks/useOpenEdxProfile';
8
8
  import { usePaymentSchedule } from 'hooks/usePaymentSchedule';
9
9
  import { Spinner } from 'components/Spinner';
10
10
  import WithdrawRightCheckbox from 'components/SaleTunnel/WithdrawRightCheckbox';
11
+ import { ProductType } from 'types/Joanie';
11
12
 
12
13
  const messages = defineMessages({
13
14
  title: {
@@ -54,6 +55,7 @@ const messages = defineMessages({
54
55
  });
55
56
 
56
57
  export const SaleTunnelInformation = () => {
58
+ const { product } = useSaleTunnelContext();
57
59
  return (
58
60
  <div className="sale-tunnel__main__column sale-tunnel__information">
59
61
  <div>
@@ -70,7 +72,7 @@ export const SaleTunnelInformation = () => {
70
72
  </div>
71
73
  </div>
72
74
  <div>
73
- <PaymentScheduleBlock />
75
+ {product.type === ProductType.CREDENTIAL && <PaymentScheduleBlock />}
74
76
  <Total />
75
77
  <WithdrawRightCheckbox />
76
78
  </div>
@@ -99,7 +101,7 @@ const Email = () => {
99
101
  };
100
102
 
101
103
  const Total = () => {
102
- const { product, offer } = useSaleTunnelContext();
104
+ const { product, offering } = useSaleTunnelContext();
103
105
  return (
104
106
  <div className="sale-tunnel__total">
105
107
  <div className="sale-tunnel__total__amount mt-t" data-testid="sale-tunnel__total__amount">
@@ -108,7 +110,7 @@ const Total = () => {
108
110
  </div>
109
111
  <div className="block-title">
110
112
  <FormattedNumber
111
- value={offer?.discounted_price || product.price}
113
+ value={offering?.rules.discounted_price || product.price}
112
114
  style="currency"
113
115
  currency={product.price_currency}
114
116
  />
@@ -15,7 +15,7 @@ import CourseProductItem from 'widgets/SyllabusCourseRunsList/components/CourseP
15
15
  import {
16
16
  AddressFactory,
17
17
  ContractFactory,
18
- OfferFactory,
18
+ OfferingFactory,
19
19
  CredentialOrderFactory,
20
20
  CreditCardFactory,
21
21
  PaymentFactory,
@@ -99,7 +99,7 @@ describe('SaleTunnel', () => {
99
99
  */
100
100
  const course = PacedCourseFactory().one();
101
101
  const product = ProductFactory().one();
102
- const offer = OfferFactory({
102
+ const offering = OfferingFactory({
103
103
  course,
104
104
  product,
105
105
  is_withdrawable: false,
@@ -108,7 +108,7 @@ describe('SaleTunnel', () => {
108
108
 
109
109
  fetchMock.get(
110
110
  `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
111
- offer,
111
+ offering,
112
112
  );
113
113
  fetchMock.get(
114
114
  `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-schedule/`,
@@ -15,7 +15,7 @@ import {
15
15
  AddressFactory,
16
16
  CertificateOrderFactory,
17
17
  CertificateProductFactory,
18
- OfferFactory,
18
+ OfferingFactory,
19
19
  CredentialOrderFactory,
20
20
  CredentialProductFactory,
21
21
  CreditCardFactory,
@@ -182,7 +182,9 @@ describe.each([
182
182
  nbApiCalls += 1; // useProductOrder call.
183
183
  nbApiCalls += 1; // get user account call.
184
184
  nbApiCalls += 1; // get user preferences call.
185
- nbApiCalls += 1; // product payment-schedule call
185
+ if (product.type === ProductType.CREDENTIAL) {
186
+ nbApiCalls += 1; // product payment-schedule call
187
+ }
186
188
  await waitFor(() => expect(fetchMock.calls()).toHaveLength(nbApiCalls));
187
189
 
188
190
  const user = userEvent.setup({ delay: null });
@@ -269,7 +271,9 @@ describe.each([
269
271
  nbApiCalls += 1; // useProductOrder get order with filters
270
272
  nbApiCalls += 1; // get user account call.
271
273
  nbApiCalls += 1; // get user preferences call.
272
- nbApiCalls += 1; // get product payment schedule.
274
+ if (product.type === ProductType.CREDENTIAL) {
275
+ nbApiCalls += 1; // get product payment schedule.
276
+ }
273
277
  await waitFor(() => expect(fetchMock.calls()).toHaveLength(nbApiCalls));
274
278
 
275
279
  const user = userEvent.setup({ delay: null });
@@ -403,36 +407,41 @@ describe.each([
403
407
  queryOptions: { client: createTestQueryClient({ user: richieUser }) },
404
408
  });
405
409
 
406
- await screen.findByRole('heading', {
407
- level: 4,
408
- name: 'Payment schedule',
409
- });
410
+ if (product.type === ProductType.CREDENTIAL) {
411
+ await screen.findByRole('heading', {
412
+ level: 4,
413
+ name: 'Payment schedule',
414
+ });
410
415
 
411
- const scheduleTable = screen.getByRole('table');
412
- const scheduleTableRows = within(scheduleTable).getAllByRole('row');
413
- expect(scheduleTableRows).toHaveLength(schedule.length);
416
+ const scheduleTable = screen.getByRole('table');
417
+ const scheduleTableRows = within(scheduleTable).getAllByRole('row');
418
+ expect(scheduleTableRows).toHaveLength(schedule.length);
414
419
 
415
- scheduleTableRows.forEach((row, index) => {
416
- const installment = schedule[index];
417
- // A first column should show the installment index
418
- within(row).getByRole('cell', {
419
- name: (index + 1).toString(),
420
- });
421
- // A 2nd column should show the installment amount
422
- within(row).getByRole('cell', {
423
- name: formatPrice(installment.amount, installment.currency),
424
- });
425
- // A 3rd column should show the installment withdraw date
426
- within(row).getByRole('cell', {
427
- name: `Withdrawn on ${intl.formatDate(installment.due_date, {
428
- ...DEFAULT_DATE_FORMAT,
429
- })}`,
430
- });
431
- // A 4th column should show the installment state
432
- within(row).getByRole('cell', {
433
- name: StringHelper.capitalizeFirst(installment.state.replace('_', ' '))!,
420
+ scheduleTableRows.forEach((row, index) => {
421
+ const installment = schedule[index];
422
+ // A first column should show the installment index
423
+ within(row).getByRole('cell', {
424
+ name: (index + 1).toString(),
425
+ });
426
+ // A 2nd column should show the installment amount
427
+ within(row).getByRole('cell', {
428
+ name: formatPrice(installment.amount, installment.currency),
429
+ });
430
+ // A 3rd column should show the installment withdraw date
431
+ within(row).getByRole('cell', {
432
+ name: `Withdrawn on ${intl.formatDate(installment.due_date, {
433
+ ...DEFAULT_DATE_FORMAT,
434
+ })}`,
435
+ });
436
+ // A 4th column should show the installment state
437
+ within(row).getByRole('cell', {
438
+ name: StringHelper.capitalizeFirst(installment.state.replace('_', ' '))!,
439
+ });
434
440
  });
435
- });
441
+ } else {
442
+ expect(screen.queryByRole('heading', { level: 4, name: 'Payment schedule' })).toBeNull();
443
+ expect(screen.queryByRole('table')).toBeNull();
444
+ }
436
445
 
437
446
  const $totalAmount = screen.getByTestId('sale-tunnel__total__amount');
438
447
  expect($totalAmount).toHaveTextContent(
@@ -444,15 +453,17 @@ describe.each([
444
453
  const intl = createIntl({ locale: 'en' });
445
454
  const schedule = PaymentInstallmentFactory().many(2);
446
455
 
447
- const offer = OfferFactory({
456
+ const offering = OfferingFactory({
448
457
  product: ProductFactory({
449
458
  price: 840,
450
459
  price_currency: 'EUR',
451
460
  }).one(),
452
- discounted_price: 800,
453
- discount_rate: 0.3,
461
+ rules: {
462
+ discounted_price: 800,
463
+ discount_rate: 0.3,
464
+ },
454
465
  }).one();
455
- const { product } = offer;
466
+ const { product } = offering;
456
467
 
457
468
  fetchMock
458
469
  .get(
@@ -464,45 +475,47 @@ describe.each([
464
475
  schedule,
465
476
  );
466
477
 
467
- render(<Wrapper product={product} offer={offer} isWithdrawable={true} />, {
478
+ render(<Wrapper product={product} offering={offering} isWithdrawable={true} />, {
468
479
  queryOptions: { client: createTestQueryClient({ user: richieUser }) },
469
480
  });
470
481
 
471
- await screen.findByRole('heading', {
472
- level: 4,
473
- name: 'Payment schedule',
474
- });
482
+ if (product.type === ProductType.CREDENTIAL) {
483
+ await screen.findByRole('heading', { level: 4, name: 'Payment schedule' });
475
484
 
476
- const scheduleTable = screen.getByRole('table');
477
- const scheduleTableRows = within(scheduleTable).getAllByRole('row');
478
- expect(scheduleTableRows).toHaveLength(schedule.length);
485
+ const scheduleTable = screen.getByRole('table');
486
+ const scheduleTableRows = within(scheduleTable).getAllByRole('row');
487
+ expect(scheduleTableRows).toHaveLength(schedule.length);
479
488
 
480
- scheduleTableRows.forEach((row, index) => {
481
- const installment = schedule[index];
482
- // A first column should show the installment index
483
- within(row).getByRole('cell', {
484
- name: (index + 1).toString(),
485
- });
486
- // A 2nd column should show the installment amount
487
- within(row).getByRole('cell', {
488
- name: formatPrice(installment.amount, installment.currency),
489
- });
490
- // A 3rd column should show the installment withdraw date
491
- within(row).getByRole('cell', {
492
- name: `Withdrawn on ${intl.formatDate(installment.due_date, {
493
- ...DEFAULT_DATE_FORMAT,
494
- })}`,
495
- });
496
- // A 4th column should show the installment state
497
- within(row).getByRole('cell', {
498
- name: StringHelper.capitalizeFirst(installment.state.replace('_', ' '))!,
489
+ scheduleTableRows.forEach((row, index) => {
490
+ const installment = schedule[index];
491
+ // A first column should show the installment index
492
+ within(row).getByRole('cell', {
493
+ name: (index + 1).toString(),
494
+ });
495
+ // A 2nd column should show the installment amount
496
+ within(row).getByRole('cell', {
497
+ name: formatPrice(installment.amount, installment.currency),
498
+ });
499
+ // A 3rd column should show the installment withdraw date
500
+ within(row).getByRole('cell', {
501
+ name: `Withdrawn on ${intl.formatDate(installment.due_date, {
502
+ ...DEFAULT_DATE_FORMAT,
503
+ })}`,
504
+ });
505
+ // A 4th column should show the installment state
506
+ within(row).getByRole('cell', {
507
+ name: StringHelper.capitalizeFirst(installment.state.replace('_', ' '))!,
508
+ });
499
509
  });
500
- });
510
+ } else {
511
+ expect(screen.queryByRole('heading', { level: 4, name: 'Payment schedule' })).toBeNull();
512
+ expect(screen.queryByRole('table')).toBeNull();
513
+ }
501
514
 
502
515
  const $totalAmount = screen.getByTestId('sale-tunnel__total__amount');
503
516
  expect($totalAmount).toHaveTextContent(
504
517
  'Total' +
505
- formatPrice(offer!.discounted_price!, product.price_currency).replace(
518
+ formatPrice(offering!.rules.discounted_price!, product.price_currency).replace(
506
519
  /(\u202F|\u00a0)/g,
507
520
  ' ',
508
521
  ),
@@ -2,7 +2,7 @@ import { ModalProps } from '@openfun/cunningham-react';
2
2
  import {
3
3
  CertificateProduct,
4
4
  CourseLight,
5
- Offer,
5
+ Offering,
6
6
  CredentialProduct,
7
7
  Enrollment,
8
8
  Order,
@@ -16,7 +16,7 @@ import { PacedCourse } from 'types';
16
16
 
17
17
  export interface SaleTunnelProps extends Pick<ModalProps, 'isOpen' | 'onClose'> {
18
18
  product: Product;
19
- offer?: Offer;
19
+ offering?: Offering;
20
20
  organizations?: Organization[];
21
21
  isWithdrawable: boolean;
22
22
  course?: PacedCourse | CourseLight;
@@ -1,7 +1,7 @@
1
1
  import { screen } from '@testing-library/react';
2
2
  import fetchMock from 'fetch-mock';
3
3
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
4
- import { CourseListItemFactory, OfferFactory } from 'utils/test/factories/joanie';
4
+ import { CourseListItemFactory, OfferingFactory } from 'utils/test/factories/joanie';
5
5
  import { render } from 'utils/test/render';
6
6
  import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
7
7
  import { expectNoSpinner, expectSpinner } from 'utils/test/expectSpinner';
@@ -35,7 +35,7 @@ describe('components/TeacherDashboardCourseList', () => {
35
35
  });
36
36
 
37
37
  it('should render loading more state', async () => {
38
- const trainings = OfferFactory().many(2);
38
+ const trainings = OfferingFactory().many(2);
39
39
  const courses = CourseListItemFactory().many(2);
40
40
  const courseAndProductList = [...courses, ...trainings];
41
41
 
@@ -60,7 +60,7 @@ describe('components/TeacherDashboardCourseList', () => {
60
60
  });
61
61
 
62
62
  it('should render courses and products list', async () => {
63
- const trainings = OfferFactory().many(2);
63
+ const trainings = OfferingFactory().many(2);
64
64
  const courses = CourseListItemFactory().many(2);
65
65
  const courseAndProductList = [...courses, ...trainings];
66
66
 
@@ -6,7 +6,7 @@ import { CourseGlimpseList, getCourseGlimpseListProps } from 'components/CourseG
6
6
  import { Spinner } from 'components/Spinner';
7
7
  import context from 'utils/context';
8
8
  import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
9
- import { CourseListItem, OfferLight } from 'types/Joanie';
9
+ import { CourseListItem, OfferingLight } from 'types/Joanie';
10
10
  import Banner from 'components/Banner';
11
11
 
12
12
  const messages = defineMessages({
@@ -31,7 +31,7 @@ interface TeacherDashboardCourseListProps {
31
31
  titleTranslated?: string;
32
32
  organizationId?: string;
33
33
  loadMore: () => void;
34
- courseAndProductList?: (CourseListItem | OfferLight)[];
34
+ courseAndProductList?: (CourseListItem | OfferingLight)[];
35
35
  isLoadingMore?: boolean;
36
36
  hasMore?: boolean;
37
37
  isNewSearchLoading?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { useJoanieApi } from 'contexts/JoanieApiContext';
2
- import { Offer, Organization } from 'types/Joanie';
2
+ import { Offering, Organization } from 'types/Joanie';
3
3
  import { browserDownloadFromBlob } from 'utils/download';
4
4
  import { HttpStatusCode } from 'utils/errors/HttpError';
5
5
  import { handle } from 'utils/errors/handle';
@@ -53,11 +53,11 @@ const useContractArchive = () => {
53
53
  },
54
54
  create: async (
55
55
  organizationId?: Organization['id'],
56
- offerId?: Offer['id'],
56
+ offeringId?: Offering['id'],
57
57
  ): Promise<string> => {
58
58
  const response = await api.user.contracts.zip_archive.create({
59
59
  organization_id: organizationId,
60
- offer_id: offerId,
60
+ offering_id: offeringId,
61
61
  });
62
62
 
63
63
  return extractArchiveId(response.url);