richie-education 3.1.3-dev11 → 3.1.3-dev12

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 +11 -20
  3. package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
  4. package/js/components/CourseGlimpse/utils.ts +22 -35
  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 -10
  8. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -2
  9. package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
  10. package/js/components/SaleTunnel/index.spec.tsx +5 -5
  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 -18
  16. package/js/hooks/useCourseProductUnion/index.ts +7 -7
  17. package/js/hooks/useCourseProducts.ts +4 -8
  18. package/js/hooks/useDefaultOrganizationId/index.tsx +4 -7
  19. package/js/hooks/useOffer/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 +14 -17
  23. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +8 -14
  24. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +4 -12
  25. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
  26. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -13
  27. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
  28. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +20 -28
  29. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -11
  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 -28
  35. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +13 -23
  36. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -13
  37. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
  38. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +3 -6
  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 +4 -7
  42. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
  43. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -10
  44. package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +61 -79
  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 -33
  50. package/js/pages/TeacherDashboardTraining/index.tsx +12 -20
  51. package/js/types/Joanie.ts +17 -19
  52. package/js/utils/test/factories/joanie.ts +3 -3
  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 -28
  61. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -8
  62. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +17 -27
  63. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +16 -25
  64. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
  65. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -7
  66. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
  67. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +9 -13
  68. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +63 -87
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +2 -2
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +20 -34
  71. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
  72. package/package.json +1 -1
  73. package/js/hooks/useCourseProductRelation/index.ts +0 -44
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
- courseProductRelations: {
131
- get: `${baseUrl}/organizations/:organization_id/course-product-relations/:id/`,
130
+ offers: {
131
+ get: `${baseUrl}/organizations/:organization_id/offers/: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
- courseProductRelations: {
160
- get: `${baseUrl}/course-product-relations/:id/`,
159
+ offers: {
160
+ get: `${baseUrl}/offers/: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
- courseProductRelations: {
474
- get: (filters?: Joanie.CourseProductRelationQueryFilters) => {
473
+ offers: {
474
+ get: (filters?: Joanie.OfferQueryFilters) => {
475
475
  return fetchWithJWT(
476
476
  filters?.organization_id
477
- ? buildApiUrl(ROUTES.organizations.courseProductRelations.get, filters)
478
- : buildApiUrl(ROUTES.courseProductRelations.get, filters),
477
+ ? buildApiUrl(ROUTES.organizations.offers.get, filters)
478
+ : buildApiUrl(ROUTES.offers.get, filters),
479
479
  ).then(checkStatus);
480
480
  },
481
481
  },
@@ -6,14 +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 {
10
- ContractFactory,
11
- CourseProductRelationFactory,
12
- OrganizationFactory,
13
- } from 'utils/test/factories/joanie';
9
+ import { ContractFactory, OfferFactory, OrganizationFactory } from 'utils/test/factories/joanie';
14
10
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
15
11
  import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
16
- import { isCourseProductRelation } from 'types/Joanie';
12
+ import { isOffer } from 'types/Joanie';
17
13
  import { Props } from './AbstractContractFrame';
18
14
  import { OrganizationContractFrame } from '.';
19
15
 
@@ -77,31 +73,28 @@ describe('OrganizationContractFrame', () => {
77
73
 
78
74
  it.each([
79
75
  {
80
- label: 'contractList: undefined, courseProductRelation: undefined',
76
+ label: 'contractList: undefined, offer: undefined',
81
77
  contractList: undefined,
82
- courseProductRelation: undefined,
78
+ offer: undefined,
83
79
  },
84
80
  {
85
- label: 'contractList: 2 Contract, courseProductRelation: undefined',
81
+ label: 'contractList: 2 Contract, offer: undefined',
86
82
  contractList: ContractFactory().many(2),
87
- courseProductRelation: undefined,
83
+ offer: undefined,
88
84
  },
89
85
  {
90
- label: 'contractList: undefined, courseProductRelation: one CourseProductRelation',
86
+ label: 'contractList: undefined, offer: one Offer',
91
87
  contractList: undefined,
92
- courseProductRelation: CourseProductRelationFactory().one(),
88
+ offer: OfferFactory().one(),
93
89
  },
94
90
  ])(
95
91
  'should implement AbstractContractFrame for organization and $label',
96
- async ({ contractList, courseProductRelation }) => {
92
+ async ({ contractList, offer }) => {
97
93
  const organization = OrganizationFactory().one();
98
94
  const contracts = contractList || ContractFactory().many(2);
99
95
  const isOpen = faker.datatype.boolean();
100
96
 
101
- const invitationLinkQueryString =
102
- courseProductRelation && isCourseProductRelation(courseProductRelation)
103
- ? `?course_product_relation_ids=${courseProductRelation.id}`
104
- : '';
97
+ const invitationLinkQueryString = offer && isOffer(offer) ? `?offer_ids=${offer.id}` : '';
105
98
  const expectedUrls = {
106
99
  getInvitationLink: `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts-signature-link/${invitationLinkQueryString}`,
107
100
  checkSignature: `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?id=${contracts[0].id}&id=${contracts[1].id}`,
@@ -133,9 +126,7 @@ describe('OrganizationContractFrame', () => {
133
126
  <Wrapper client={client}>
134
127
  <OrganizationContractFrame
135
128
  organizationId={organization.id}
136
- courseProductRelationIds={
137
- courseProductRelation ? [courseProductRelation.id] : undefined
138
- }
129
+ offerIds={offer ? [offer.id] : undefined}
139
130
  isOpen={isOpen}
140
131
  onDone={handleDone}
141
132
  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, CourseProductRelation } from 'types/Joanie';
7
+ import { Contract, Offer } from 'types/Joanie';
8
8
 
9
9
  interface Props extends AbstractProps {
10
10
  contractIds?: Contract['id'][];
11
11
  organizationId: string;
12
- courseProductRelationIds?: CourseProductRelation['id'][];
12
+ offerIds?: Offer['id'][];
13
13
  }
14
14
 
15
15
  const OrganizationContractFrame = ({
16
16
  organizationId,
17
- courseProductRelationIds = [],
17
+ offerIds = [],
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
- course_product_relation_ids: courseProductRelationIds,
32
+ offer_ids: offerIds,
33
33
  organization_id: organizationId,
34
34
  contracts_ids: contractIds,
35
35
  });
@@ -6,23 +6,18 @@ import {
6
6
  Course as RichieCourse,
7
7
  isRichieCourse,
8
8
  } from 'types/Course';
9
- import {
10
- CourseListItem as JoanieCourse,
11
- CourseProductRelationLight,
12
- isCourseProductRelation,
13
- ProductType,
14
- } from 'types/Joanie';
9
+ import { CourseListItem as JoanieCourse, OfferLight, isOffer, ProductType } from 'types/Joanie';
15
10
  import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherDashboardPaths';
16
11
  import { CourseGlimpseCourse } from '.';
17
12
 
18
- const getCourseGlimpsePropsFromCourseProductRelation = (
19
- courseProductRelation: CourseProductRelationLight,
13
+ const getCourseGlimpsePropsFromOffer = (
14
+ offer: OfferLight,
20
15
  intl: IntlShape,
21
16
  organizationId?: string,
22
17
  ): CourseGlimpseCourse => {
23
18
  const courseRouteParams = {
24
- courseId: courseProductRelation.course.id,
25
- courseProductRelationId: courseProductRelation.id,
19
+ courseId: offer.course.id,
20
+ offerId: offer.id,
26
21
  };
27
22
  const courseRoute = organizationId
28
23
  ? generatePath(TeacherDashboardPaths.ORGANIZATION_PRODUCT, {
@@ -31,35 +26,27 @@ const getCourseGlimpsePropsFromCourseProductRelation = (
31
26
  })
32
27
  : generatePath(TeacherDashboardPaths.COURSE_PRODUCT, courseRouteParams);
33
28
  return {
34
- id: courseProductRelation.id,
35
- code: courseProductRelation.course.code,
36
- title: courseProductRelation.product.title,
37
- cover_image: courseProductRelation.course.cover
29
+ id: offer.id,
30
+ code: offer.course.code,
31
+ title: offer.product.title,
32
+ cover_image: offer.course.cover
38
33
  ? {
39
- src: courseProductRelation.course.cover.src,
34
+ src: offer.course.cover.src,
40
35
  }
41
36
  : null,
42
37
  organization: {
43
- title: courseProductRelation.organizations[0].title,
44
- image: courseProductRelation.organizations[0].logo || null,
38
+ title: offer.organizations[0].title,
39
+ image: offer.organizations[0].logo || null,
45
40
  },
46
- product_id: courseProductRelation.product.id,
41
+ product_id: offer.product.id,
47
42
  course_route: courseRoute,
48
- state: courseProductRelation.product.state,
43
+ state: offer.product.state,
49
44
  certificate_offer:
50
- courseProductRelation.product.type === ProductType.CERTIFICATE
51
- ? CourseCertificateOffer.PAID
52
- : null,
53
- offer: courseProductRelation.product.type === ProductType.CREDENTIAL ? CourseOffer.PAID : null,
54
- certificate_price:
55
- courseProductRelation.product.type === ProductType.CERTIFICATE
56
- ? courseProductRelation.product.price
57
- : null,
58
- price:
59
- courseProductRelation.product.type === ProductType.CREDENTIAL
60
- ? courseProductRelation.product.price
61
- : null,
62
- price_currency: courseProductRelation.product.price_currency,
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,
63
50
  };
64
51
  };
65
52
 
@@ -125,12 +112,12 @@ const getCourseGlimpsePropsFromJoanieCourse = (
125
112
  };
126
113
 
127
114
  export const getCourseGlimpseProps = (
128
- course: RichieCourse | (JoanieCourse | CourseProductRelationLight),
115
+ course: RichieCourse | (JoanieCourse | OfferLight),
129
116
  intl?: IntlShape,
130
117
  organizationId?: string,
131
118
  ): CourseGlimpseCourse => {
132
- if (isCourseProductRelation(course)) {
133
- return getCourseGlimpsePropsFromCourseProductRelation(course, intl!, organizationId);
119
+ if (isOffer(course)) {
120
+ return getCourseGlimpsePropsFromOffer(course, intl!, organizationId);
134
121
  }
135
122
 
136
123
  if (isRichieCourse(course)) {
@@ -1,10 +1,10 @@
1
1
  import { IntlShape } from 'react-intl';
2
- import { CourseProductRelationLight, CourseListItem as JoanieCourse } from 'types/Joanie';
2
+ import { OfferLight, 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 | CourseProductRelationLight)[],
7
+ courses: RichieCourse[] | (JoanieCourse | OfferLight)[],
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
- courseProductRelation?: Joanie.CourseProductRelation;
45
+ offer?: Joanie.Offer;
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
- courseProductRelation,
69
+ offer,
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
- courseProductRelation={courseProductRelation}
143
+ offer={offer}
144
144
  enrollment={enrollment}
145
145
  course={course}
146
146
  isWithdrawable={isWithdrawable}
@@ -10,14 +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 {
14
- Address,
15
- CourseProductRelation,
16
- CreditCard,
17
- Order,
18
- OrderState,
19
- Product,
20
- } from 'types/Joanie';
13
+ import { Address, Offer, CreditCard, Order, OrderState, Product } from 'types/Joanie';
21
14
  import useProductOrder from 'hooks/useProductOrder';
22
15
  import { SaleTunnelSuccess } from 'components/SaleTunnel/SaleTunnelSuccess';
23
16
  import WebAnalyticsAPIHandler from 'api/web-analytics';
@@ -33,7 +26,7 @@ export interface SaleTunnelContextType {
33
26
  order?: Order;
34
27
  product: Product;
35
28
  webAnalyticsEventKey: string;
36
- relation?: CourseProductRelation;
29
+ offer?: Offer;
37
30
 
38
31
  // internal
39
32
  step: SaleTunnelStep;
@@ -121,7 +114,7 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
121
114
  webAnalyticsEventKey: props.eventKey,
122
115
  order,
123
116
  product: props.product,
124
- relation: props.courseProductRelation,
117
+ offer: props.offer,
125
118
  props,
126
119
  billingAddress,
127
120
  setBillingAddress,
@@ -99,7 +99,7 @@ const Email = () => {
99
99
  };
100
100
 
101
101
  const Total = () => {
102
- const { product, relation } = useSaleTunnelContext();
102
+ const { product, offer } = useSaleTunnelContext();
103
103
  return (
104
104
  <div className="sale-tunnel__total">
105
105
  <div className="sale-tunnel__total__amount mt-t" data-testid="sale-tunnel__total__amount">
@@ -108,7 +108,7 @@ const Total = () => {
108
108
  </div>
109
109
  <div className="block-title">
110
110
  <FormattedNumber
111
- value={relation?.discounted_price || product.price}
111
+ value={offer?.discounted_price || product.price}
112
112
  style="currency"
113
113
  currency={product.price_currency}
114
114
  />
@@ -15,7 +15,7 @@ import CourseProductItem from 'widgets/SyllabusCourseRunsList/components/CourseP
15
15
  import {
16
16
  AddressFactory,
17
17
  ContractFactory,
18
- CourseProductRelationFactory,
18
+ OfferFactory,
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 relation = CourseProductRelationFactory({
102
+ const offer = OfferFactory({
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
- relation,
111
+ offer,
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
- CourseProductRelationFactory,
18
+ OfferFactory,
19
19
  CredentialOrderFactory,
20
20
  CredentialProductFactory,
21
21
  CreditCardFactory,
@@ -444,7 +444,7 @@ describe.each([
444
444
  const intl = createIntl({ locale: 'en' });
445
445
  const schedule = PaymentInstallmentFactory().many(2);
446
446
 
447
- const relation = CourseProductRelationFactory({
447
+ const offer = OfferFactory({
448
448
  product: ProductFactory({
449
449
  price: 840,
450
450
  price_currency: 'EUR',
@@ -452,7 +452,7 @@ describe.each([
452
452
  discounted_price: 800,
453
453
  discount_rate: 0.3,
454
454
  }).one();
455
- const { product } = relation;
455
+ const { product } = offer;
456
456
 
457
457
  fetchMock
458
458
  .get(
@@ -464,7 +464,7 @@ describe.each([
464
464
  schedule,
465
465
  );
466
466
 
467
- render(<Wrapper product={product} courseProductRelation={relation} isWithdrawable={true} />, {
467
+ render(<Wrapper product={product} offer={offer} isWithdrawable={true} />, {
468
468
  queryOptions: { client: createTestQueryClient({ user: richieUser }) },
469
469
  });
470
470
 
@@ -502,7 +502,7 @@ describe.each([
502
502
  const $totalAmount = screen.getByTestId('sale-tunnel__total__amount');
503
503
  expect($totalAmount).toHaveTextContent(
504
504
  'Total' +
505
- formatPrice(relation!.discounted_price!, product.price_currency).replace(
505
+ formatPrice(offer!.discounted_price!, product.price_currency).replace(
506
506
  /(\u202F|\u00a0)/g,
507
507
  ' ',
508
508
  ),
@@ -2,7 +2,7 @@ import { ModalProps } from '@openfun/cunningham-react';
2
2
  import {
3
3
  CertificateProduct,
4
4
  CourseLight,
5
- CourseProductRelation,
5
+ Offer,
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
- courseProductRelation?: CourseProductRelation;
19
+ offer?: Offer;
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, CourseProductRelationFactory } from 'utils/test/factories/joanie';
4
+ import { CourseListItemFactory, OfferFactory } 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 = CourseProductRelationFactory().many(2);
38
+ const trainings = OfferFactory().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 = CourseProductRelationFactory().many(2);
63
+ const trainings = OfferFactory().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, CourseProductRelationLight } from 'types/Joanie';
9
+ import { CourseListItem, OfferLight } 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 | CourseProductRelationLight)[];
34
+ courseAndProductList?: (CourseListItem | OfferLight)[];
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 { CourseProductRelation, Organization } from 'types/Joanie';
2
+ import { Offer, 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
- courseProductRelationId?: CourseProductRelation['id'],
56
+ offerId?: Offer['id'],
57
57
  ): Promise<string> => {
58
58
  const response = await api.user.contracts.zip_archive.create({
59
59
  organization_id: organizationId,
60
- course_product_relation_id: courseProductRelationId,
60
+ offer_id: offerId,
61
61
  });
62
62
 
63
63
  return extractArchiveId(response.url);
@@ -2,13 +2,13 @@ import { renderHook, waitFor } from '@testing-library/react';
2
2
  import { QueryClient } from '@tanstack/react-query';
3
3
  import fetchMock from 'fetch-mock';
4
4
  import { PropsWithChildren } from 'react';
5
- import { CourseListItem, CourseProductRelation } from 'types/Joanie';
5
+ import { CourseListItem, Offer } from 'types/Joanie';
6
6
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
7
7
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
8
8
  import { SessionProvider } from 'contexts/SessionContext';
9
9
  import { getRoutes } from 'api/joanie';
10
10
  import { mockPaginatedResponse } from 'utils/test/mockPaginatedResponse';
11
- import { CourseListItemFactory, CourseProductRelationFactory } from 'utils/test/factories/joanie';
11
+ import { CourseListItemFactory, OfferFactory } from 'utils/test/factories/joanie';
12
12
  import { BaseJoanieAppWrapper } from 'utils/test/wrappers/BaseJoanieAppWrapper';
13
13
  import { useCourseProductUnion } from '.';
14
14
 
@@ -41,12 +41,12 @@ const renderUseCourseProductUnion = ({ organizationId }: { organizationId?: stri
41
41
 
42
42
  describe('useCourseProductUnion', () => {
43
43
  let courseList: CourseListItem[];
44
- let courseProductRelationList: CourseProductRelation[];
44
+ let offerList: Offer[];
45
45
  let nbApiCalls: number;
46
46
 
47
47
  beforeEach(() => {
48
48
  courseList = CourseListItemFactory().many(6);
49
- courseProductRelationList = CourseProductRelationFactory().many(6);
49
+ offerList = OfferFactory().many(6);
50
50
 
51
51
  fetchMock.get('https://joanie.endpoint/api/v1.0/orders/', [], { overwriteRoutes: true });
52
52
  fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', [], { overwriteRoutes: true });
@@ -59,38 +59,38 @@ describe('useCourseProductUnion', () => {
59
59
  fetchMock.restore();
60
60
  });
61
61
 
62
- it('should call courses and coursesProductRelation endpoints', async () => {
62
+ it('should call courses and offer endpoints', async () => {
63
63
  const ROUTES = getRoutes();
64
64
  const coursesUrl = ROUTES.courses.get.replace(':id/', '');
65
- const courseProductRelationsUrl = ROUTES.courseProductRelations.get.replace(':id/', '');
65
+ const offersUrl = ROUTES.offers.get.replace(':id/', '');
66
66
  fetchMock.get(
67
67
  `${coursesUrl}?has_listed_course_runs=true&page=1&page_size=${PER_PAGE}`,
68
68
  mockPaginatedResponse(courseList.slice(0, PER_PAGE), 0, false),
69
69
  );
70
70
  fetchMock.get(
71
- `${courseProductRelationsUrl}?page=1&page_size=${PER_PAGE}`,
72
- mockPaginatedResponse(courseProductRelationList.slice(0, PER_PAGE), 0, false),
71
+ `${offersUrl}?page=1&page_size=${PER_PAGE}`,
72
+ mockPaginatedResponse(offerList.slice(0, PER_PAGE), 0, false),
73
73
  );
74
74
  const { result } = renderUseCourseProductUnion();
75
75
  await waitFor(() => expect(result.current.isLoading).toBe(false));
76
76
  expect(result.current.data.length).toBe(PER_PAGE);
77
77
  nbApiCalls += 1; // courses page 1
78
- nbApiCalls += 1; // course product relations page 1
78
+ nbApiCalls += 1; // offers page 1
79
79
  const calledUrls = fetchMock.calls().map((call) => call[0]);
80
80
  expect(calledUrls).toHaveLength(nbApiCalls);
81
81
  expect(calledUrls).toContain(
82
82
  `${coursesUrl}?has_listed_course_runs=true&page=1&page_size=${PER_PAGE}`,
83
83
  );
84
- expect(calledUrls).toContain(`${courseProductRelationsUrl}?page=1&page_size=${PER_PAGE}`);
84
+ expect(calledUrls).toContain(`${offersUrl}?page=1&page_size=${PER_PAGE}`);
85
85
  }, 25000);
86
86
 
87
- it('should call organization courses and organization coursesProductRelation endpoints', async () => {
87
+ it('should call organization courses and organization offer endpoints', async () => {
88
88
  const organizationId = 'DUMMY_ORGANIZATION_ID';
89
89
  const ROUTES = getRoutes();
90
90
  const organizationCoursesUrl = ROUTES.organizations.courses.get
91
91
  .replace(':organization_id', organizationId)
92
92
  .replace(':id/', '');
93
- const organizationCourseProductRelationsUrl = ROUTES.organizations.courseProductRelations.get
93
+ const organizationOffersUrl = ROUTES.organizations.offers.get
94
94
  .replace(':organization_id', organizationId)
95
95
  .replace(':id/', '');
96
96
  fetchMock.get(
@@ -98,21 +98,19 @@ describe('useCourseProductUnion', () => {
98
98
  mockPaginatedResponse(courseList.slice(0, PER_PAGE), 0, false),
99
99
  );
100
100
  fetchMock.get(
101
- `${organizationCourseProductRelationsUrl}?page=1&page_size=${PER_PAGE}`,
102
- mockPaginatedResponse(courseProductRelationList.slice(0, PER_PAGE), 0, false),
101
+ `${organizationOffersUrl}?page=1&page_size=${PER_PAGE}`,
102
+ mockPaginatedResponse(offerList.slice(0, PER_PAGE), 0, false),
103
103
  );
104
104
  const { result } = renderUseCourseProductUnion({ organizationId: 'DUMMY_ORGANIZATION_ID' });
105
105
  await waitFor(() => expect(result.current.isLoading).toBe(false));
106
106
  expect(result.current.data.length).toBe(PER_PAGE);
107
107
  nbApiCalls += 1; // courses page 1
108
- nbApiCalls += 1; // course product relations page 1
108
+ nbApiCalls += 1; // offers page 1
109
109
  const calledUrls = fetchMock.calls().map((call) => call[0]);
110
110
  expect(calledUrls).toHaveLength(nbApiCalls);
111
111
  expect(calledUrls).toContain(
112
112
  `${organizationCoursesUrl}?has_listed_course_runs=true&page=1&page_size=${PER_PAGE}`,
113
113
  );
114
- expect(calledUrls).toContain(
115
- `${organizationCourseProductRelationsUrl}?page=1&page_size=${PER_PAGE}`,
116
- );
114
+ expect(calledUrls).toContain(`${organizationOffersUrl}?page=1&page_size=${PER_PAGE}`);
117
115
  });
118
116
  });
@@ -3,11 +3,11 @@ import { useJoanieApi } from 'contexts/JoanieApiContext';
3
3
  import {
4
4
  CourseListItem,
5
5
  Product,
6
- CourseProductRelation,
6
+ Offer,
7
7
  CourseQueryFilters,
8
- CourseProductRelationQueryFilters,
8
+ OfferQueryFilters,
9
9
  ProductType,
10
- CourseProductRelationLight,
10
+ OfferLight,
11
11
  } from 'types/Joanie';
12
12
  import useUnionResource, { ResourceUnionPaginationProps } from 'hooks/useUnionResource';
13
13
 
@@ -41,9 +41,9 @@ export const useCourseProductUnion = ({
41
41
  const api = useJoanieApi();
42
42
  return useUnionResource<
43
43
  CourseListItem,
44
- CourseProductRelation | CourseProductRelationLight,
44
+ Offer | OfferLight,
45
45
  CourseQueryFilters,
46
- CourseProductRelationQueryFilters
46
+ OfferQueryFilters
47
47
  >({
48
48
  queryAConfig: {
49
49
  queryKey: ['user', 'courses'],
@@ -51,8 +51,8 @@ export const useCourseProductUnion = ({
51
51
  filters: { query, organization_id: organizationId, has_listed_course_runs: true },
52
52
  },
53
53
  queryBConfig: {
54
- queryKey: ['user', 'course_product_relations'],
55
- fn: api.courseProductRelations.get,
54
+ queryKey: ['user', 'offers'],
55
+ fn: api.offers.get,
56
56
  filters: { query, organization_id: organizationId, product_type: productType },
57
57
  },
58
58
  perPage,