richie-education 3.1.3-dev11 → 3.1.3-dev15

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 +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 -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 +25 -22
  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 -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 +10 -18
  68. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +81 -99
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +6 -4
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +20 -31
  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
@@ -11,7 +11,7 @@ import {
11
11
  UserFactory,
12
12
  } from 'utils/test/factories/richie';
13
13
  import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
14
- import { CourseProductRelationFactory, OrganizationFactory } from 'utils/test/factories/joanie';
14
+ import { OfferFactory, OrganizationFactory } from 'utils/test/factories/joanie';
15
15
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
16
16
  import { expectNoSpinner } from 'utils/test/expectSpinner';
17
17
  import { DashboardBreadcrumbsProvider } from 'widgets/Dashboard/contexts/DashboardBreadcrumbsContext';
@@ -46,15 +46,9 @@ describe('components/TeacherDashboardTrainingLoader', () => {
46
46
  });
47
47
 
48
48
  it('should render TeacherDashboardTrainingLoader page', async () => {
49
- const courseProductRelation = CourseProductRelationFactory().one();
50
- fetchMock.get(
51
- `https://joanie.endpoint/api/v1.0/organizations/?course_product_relation_id=${courseProductRelation.id}`,
52
- [],
53
- );
54
- fetchMock.get(
55
- `https://joanie.endpoint/api/v1.0/course-product-relations/${courseProductRelation.id}/`,
56
- courseProductRelation,
57
- );
49
+ const offer = OfferFactory().one();
50
+ fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/?offer_id=${offer.id}`, []);
51
+ fetchMock.get(`https://joanie.endpoint/api/v1.0/offers/${offer.id}/`, offer);
58
52
 
59
53
  const user = UserFactory().one();
60
54
  render(
@@ -67,12 +61,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
67
61
  router={createMemoryRouter(
68
62
  [
69
63
  {
70
- path: ':courseProductRelationId',
64
+ path: ':offerId',
71
65
  element: <TeacherDashboardTrainingLoader />,
72
66
  },
73
67
  ],
74
68
  {
75
- initialEntries: [`/${courseProductRelation.id}`],
69
+ initialEntries: [`/${offer.id}`],
76
70
  },
77
71
  )}
78
72
  />
@@ -88,12 +82,10 @@ describe('components/TeacherDashboardTrainingLoader', () => {
88
82
  await expectNoSpinner('Loading course...');
89
83
 
90
84
  nbApiCalls += 1; // organizations api call
91
- nbApiCalls += 1; // course-product-relations api call
85
+ nbApiCalls += 1; // offers api call
92
86
  const calledUrls = fetchMock.calls().map((call) => call[0]);
93
87
  expect(calledUrls).toHaveLength(nbApiCalls);
94
- expect(calledUrls).toContain(
95
- `https://joanie.endpoint/api/v1.0/course-product-relations/${courseProductRelation.id}/`,
96
- );
88
+ expect(calledUrls).toContain(`https://joanie.endpoint/api/v1.0/offers/${offer.id}/`);
97
89
 
98
90
  // main titles
99
91
  expect(
@@ -102,28 +94,28 @@ describe('components/TeacherDashboardTrainingLoader', () => {
102
94
  }),
103
95
  ).toBeInTheDocument();
104
96
 
105
- expect(
106
- screen.getAllByRole('heading', { name: capitalize(courseProductRelation.product.title) }),
107
- ).toHaveLength(2);
97
+ expect(screen.getAllByRole('heading', { name: capitalize(offer.product.title) })).toHaveLength(
98
+ 2,
99
+ );
108
100
 
109
- const nbCourseRun = courseProductRelation.product.target_courses.reduce(
101
+ const nbCourseRun = offer.product.target_courses.reduce(
110
102
  (acc, course) => acc + course.course_runs.length,
111
103
  0,
112
104
  );
113
105
  expect(screen.getAllByRole('link', { name: 'Go to course area' })).toHaveLength(nbCourseRun);
114
106
  });
115
107
 
116
- it('should fetch course product relation with organization id if there is one in the path', async () => {
108
+ it('should fetch offer with organization id if there is one in the path', async () => {
117
109
  const organization = OrganizationFactory().one();
118
- const courseProductRelation = CourseProductRelationFactory({
110
+ const offer = OfferFactory({
119
111
  organizations: [organization],
120
112
  }).one();
121
113
  fetchMock.get(
122
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/${courseProductRelation.id}/`,
123
- courseProductRelation,
114
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offers/${offer.id}/`,
115
+ offer,
124
116
  );
125
117
  fetchMock.get(
126
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?course_product_relation_id=${courseProductRelation.id}&signature_state=half_signed&page=1&page_size=25`,
118
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?offer_id=${offer.id}&signature_state=half_signed&page=1&page_size=25`,
127
119
  [],
128
120
  );
129
121
 
@@ -138,12 +130,12 @@ describe('components/TeacherDashboardTrainingLoader', () => {
138
130
  router={createMemoryRouter(
139
131
  [
140
132
  {
141
- path: '/:organizationId/:courseProductRelationId',
133
+ path: '/:organizationId/:offerId',
142
134
  element: <TeacherDashboardTrainingLoader />,
143
135
  },
144
136
  ],
145
137
  {
146
- initialEntries: [`/${organization.id}/${courseProductRelation.id}`],
138
+ initialEntries: [`/${organization.id}/${offer.id}`],
147
139
  },
148
140
  )}
149
141
  />
@@ -159,11 +151,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
159
151
  await expectNoSpinner('Loading course...');
160
152
 
161
153
  nbApiCalls += 1; // contracts api call
162
- nbApiCalls += 1; // course-product-relations api call
154
+ nbApiCalls += 1; // offers api call
163
155
  const calledUrls = fetchMock.calls().map((call) => call[0]);
164
156
  expect(calledUrls).toHaveLength(nbApiCalls);
165
157
  expect(calledUrls).toContain(
166
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/${courseProductRelation.id}/`,
158
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offers/${offer.id}/`,
167
159
  );
168
160
 
169
161
  // main titles
@@ -173,11 +165,11 @@ describe('components/TeacherDashboardTrainingLoader', () => {
173
165
  }),
174
166
  ).toBeInTheDocument();
175
167
 
176
- expect(
177
- screen.getAllByRole('heading', { name: capitalize(courseProductRelation.product.title) }),
178
- ).toHaveLength(2);
168
+ expect(screen.getAllByRole('heading', { name: capitalize(offer.product.title) })).toHaveLength(
169
+ 2,
170
+ );
179
171
 
180
- const nbCourseRun = courseProductRelation.product.target_courses.reduce(
172
+ const nbCourseRun = offer.product.target_courses.reduce(
181
173
  (acc, course) => acc + course.course_runs.length,
182
174
  0,
183
175
  );
@@ -6,37 +6,33 @@ import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
6
6
  import { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';
7
7
  import { Icon, IconTypeEnum } from 'components/Icon';
8
8
  import Banner, { BannerType } from 'components/Banner';
9
- import { CourseProductRelation } from 'types/Joanie';
9
+ import { Offer } from 'types/Joanie';
10
10
 
11
11
  const messages = defineMessages({
12
- errorNoCourseProductRelation: {
12
+ errorNoOffer: {
13
13
  defaultMessage: "This product doesn't exist",
14
- description: 'Message displayed when requested course product relation is not found',
15
- id: 'components.TeacherDashboardTraining.errorNoCourseProductRelation',
14
+ description: 'Message displayed when requested offer is not found',
15
+ id: 'components.TeacherDashboardTraining.errorNoOffer',
16
16
  },
17
17
  });
18
18
 
19
19
  interface TeacherDashboardTrainingProps {
20
- courseProductRelation: CourseProductRelation;
20
+ offer: Offer;
21
21
  }
22
22
 
23
- export const TeacherDashboardTraining = ({
24
- courseProductRelation,
25
- }: TeacherDashboardTrainingProps) => {
23
+ export const TeacherDashboardTraining = ({ offer }: TeacherDashboardTrainingProps) => {
26
24
  const intl = useIntl();
27
- return courseProductRelation ? (
25
+ return offer ? (
28
26
  <div className="teacher-course-page">
29
27
  <DashboardCard
30
28
  className="icon-arrow-right-rounded"
31
29
  header={
32
30
  <div>
33
31
  <div className="dashboard__title_container--small">
34
- <h2 className="dashboard__title--large">
35
- {capitalize(courseProductRelation.product.title)}
36
- </h2>
32
+ <h2 className="dashboard__title--large">{capitalize(offer.product.title)}</h2>
37
33
  </div>
38
- {courseProductRelation.product.description && (
39
- <div className="dashboard__quote">{courseProductRelation.product.description}</div>
34
+ {offer.product.description && (
35
+ <div className="dashboard__quote">{offer.product.description}</div>
40
36
  )}
41
37
  </div>
42
38
  }
@@ -44,7 +40,7 @@ export const TeacherDashboardTraining = ({
44
40
  fullWidth
45
41
  />
46
42
  <DashboardLayout.NestedSection>
47
- {courseProductRelation.product.target_courses.map((course) => (
43
+ {offer.product.target_courses.map((course) => (
48
44
  <DashboardLayout.Section key={`course_target_${course.code}`}>
49
45
  <DashboardCard
50
46
  className="icon-arrow-right-rounded"
@@ -66,11 +62,7 @@ export const TeacherDashboardTraining = ({
66
62
  </DashboardLayout.NestedSection>
67
63
  </div>
68
64
  ) : (
69
- <Banner
70
- message={intl.formatMessage(messages.errorNoCourseProductRelation)}
71
- type={BannerType.ERROR}
72
- rounded
73
- />
65
+ <Banner message={intl.formatMessage(messages.errorNoOffer)} type={BannerType.ERROR} rounded />
74
66
  );
75
67
  };
76
68
 
@@ -38,7 +38,7 @@ export interface Organization {
38
38
  }
39
39
 
40
40
  export interface OrganizationResourceQuery extends ResourcesQuery {
41
- course_product_relation_id?: CourseProductRelation['id'];
41
+ offer_id?: Offer['id'];
42
42
  }
43
43
 
44
44
  export interface ContractDefinition {
@@ -174,7 +174,7 @@ export interface DefinitionResourcesProduct {
174
174
  contract_definition_id: Nullable<ContractDefinition['id']>;
175
175
  }
176
176
 
177
- export interface CourseProductRelationLight {
177
+ export interface OfferLight {
178
178
  id: string;
179
179
  course: CourseLight;
180
180
  organizations: Organization[];
@@ -182,20 +182,23 @@ export interface CourseProductRelationLight {
182
182
  created_on: string;
183
183
  }
184
184
 
185
- export interface CourseProductRelation extends CourseProductRelationLight {
186
- is_withdrawable: boolean;
185
+ export interface OfferRule {
187
186
  discounted_price: Nullable<number>;
188
187
  discount_rate: Nullable<number>;
189
188
  discount_amount: Nullable<number>;
190
189
  discount_start: Nullable<string>;
191
190
  discount_end: Nullable<string>;
192
191
  description: Nullable<string>;
193
- nb_seats_available: Nullable<number>;
194
- seats: Nullable<number>;
192
+ nb_available_seats: Nullable<number>;
193
+ has_seat_limit: boolean;
194
+ has_seats_left: boolean;
195
+ }
196
+
197
+ export interface Offer extends OfferLight {
198
+ is_withdrawable: boolean;
199
+ rules: OfferRule;
195
200
  }
196
- export function isCourseProductRelation(
197
- entity: CourseListItem | CourseProductRelationLight | RichieCourse,
198
- ): entity is CourseProductRelationLight {
201
+ export function isOffer(entity: CourseListItem | OfferLight | RichieCourse): entity is OfferLight {
199
202
  return 'course' in entity && 'product' in entity;
200
203
  }
201
204
 
@@ -240,7 +243,7 @@ export interface Enrollment {
240
243
  was_created_by_order: boolean;
241
244
  created_on: string;
242
245
  orders: OrderEnrollment[];
243
- product_relations: CourseProductRelation[];
246
+ offers: Offer[];
244
247
  certificate_id: Nullable<string>;
245
248
  }
246
249
  export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
@@ -254,7 +257,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
254
257
  'was_created_by_order' in obj &&
255
258
  'created_on' in obj &&
256
259
  'orders' in obj &&
257
- 'product_relations' in obj &&
260
+ 'offers' in obj &&
258
261
  'certificate_id' in obj
259
262
  );
260
263
  };
@@ -404,7 +407,7 @@ export interface NestedCourseOrder {
404
407
 
405
408
  export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
406
409
  course_id?: CourseListItem['id'];
407
- course_product_relation_id?: CourseProductRelation['id'];
410
+ offer_id?: Offer['id'];
408
411
  organization_id?: Organization['id'];
409
412
  product_id?: Product['id'];
410
413
  }
@@ -539,8 +542,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
539
542
  id?: Product['id'];
540
543
  course_id?: CourseListItem['id'];
541
544
  }
542
- export interface CourseProductRelationQueryFilters extends PaginatedResourceQuery {
543
- id?: CourseProductRelation['id'];
545
+ export interface OfferQueryFilters extends PaginatedResourceQuery {
546
+ id?: Offer['id'];
544
547
  organization_id?: Organization['id'];
545
548
  product_type?: ProductType;
546
549
  query?: string;
@@ -553,7 +556,7 @@ export enum ContractState {
553
556
  }
554
557
  export interface ContractResourceQuery extends PaginatedResourceQuery {
555
558
  organization_id?: Organization['id'];
556
- course_product_relation_id?: CourseProductRelation['id'];
559
+ offer_id?: Offer['id'];
557
560
  contract_ids?: Contract['id'][];
558
561
  signature_state?: ContractState;
559
562
  }
@@ -561,7 +564,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
561
564
  export interface OrganizationContractSignatureLinksFilters {
562
565
  contracts_ids?: string[];
563
566
  organization_id: Organization['id'];
564
- course_product_relation_ids?: CourseProductRelation['id'][];
567
+ offer_ids?: Offer['id'][];
565
568
  }
566
569
 
567
570
  export interface ContractInvitationLinkResponse {
@@ -655,10 +658,10 @@ interface APIUser {
655
658
  check: (id: string) => Promise<Response>;
656
659
  create: ({
657
660
  organization_id,
658
- course_product_relation_id,
661
+ offer_id,
659
662
  }: {
660
663
  organization_id?: Organization['id'];
661
- course_product_relation_id?: CourseProductRelation['id'];
664
+ offer_id?: Offer['id'];
662
665
  }) => Promise<{ url: string }>;
663
666
  get: (id: string) => Promise<File>;
664
667
  };
@@ -674,7 +677,7 @@ export interface API {
674
677
  ? Promise<Nullable<CourseListItem>>
675
678
  : Promise<PaginatedResponse<CourseListItem>>;
676
679
  products: {
677
- get(filters?: CourseProductQueryFilters): Promise<Nullable<CourseProductRelation>>;
680
+ get(filters?: CourseProductQueryFilters): Promise<Nullable<Offer>>;
678
681
  paymentSchedule: {
679
682
  get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
680
683
  };
@@ -707,12 +710,12 @@ export interface API {
707
710
  filters?: CourseRunFilters,
708
711
  ): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
709
712
  };
710
- courseProductRelations: {
713
+ offers: {
711
714
  get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
712
715
  filters?: Filters,
713
716
  ): Filters extends { id: string }
714
- ? Promise<Nullable<CourseProductRelation>>
715
- : Promise<PaginatedResponse<CourseProductRelationLight>>;
717
+ ? Promise<Nullable<Offer>>
718
+ : Promise<PaginatedResponse<OfferLight>>;
716
719
  };
717
720
  contractDefinitions: {
718
721
  previewTemplate(id: string): Promise<File>;
@@ -12,7 +12,7 @@ import {
12
12
  CourseLight,
13
13
  CourseListItem,
14
14
  CourseProduct,
15
- CourseProductRelation,
15
+ Offer,
16
16
  CourseRun,
17
17
  CredentialOrder,
18
18
  CredentialProduct,
@@ -89,7 +89,7 @@ export const EnrollmentFactory = factory((): Enrollment => {
89
89
  id: faker.string.uuid(),
90
90
  course_run: CourseRunWithCourseFactory().one(),
91
91
  is_active: true,
92
- product_relations: CourseProductRelationFactory().many(1),
92
+ offers: OfferFactory().many(1),
93
93
  state: EnrollmentState.SET,
94
94
  was_created_by_order: false,
95
95
  created_on: faker.date.past({ years: 1 }).toISOString(),
@@ -309,7 +309,7 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
309
309
  };
310
310
  });
311
311
 
312
- export const CourseProductRelationFactory = factory((): CourseProductRelation => {
312
+ export const OfferFactory = factory((): Offer => {
313
313
  return {
314
314
  id: faker.string.uuid(),
315
315
  created_on: faker.date.past().toISOString(),
@@ -317,14 +317,17 @@ export const CourseProductRelationFactory = factory((): CourseProductRelation =>
317
317
  product: ProductFactory().one(),
318
318
  organizations: OrganizationFactory().many(1),
319
319
  is_withdrawable: true,
320
- discounted_price: null,
321
- discount_rate: null,
322
- discount_amount: null,
323
- discount_start: null,
324
- discount_end: null,
325
- description: null,
326
- seats: null,
327
- nb_seats_available: null,
320
+ rules: {
321
+ discounted_price: null,
322
+ discount_rate: null,
323
+ discount_amount: null,
324
+ discount_start: null,
325
+ discount_end: null,
326
+ description: null,
327
+ nb_available_seats: null,
328
+ has_seat_limit: false,
329
+ has_seats_left: true,
330
+ },
328
331
  };
329
332
  });
330
333
 
@@ -3,14 +3,14 @@ import { CredentialOrder } from 'types/Joanie';
3
3
  import {
4
4
  ContractDefinitionFactory,
5
5
  CourseFactory,
6
- CourseProductRelationFactory,
6
+ OfferFactory,
7
7
  ProductFactory,
8
8
  } from 'utils/test/factories/joanie';
9
9
 
10
10
  export const mockCourseProductWithOrder = (order: CredentialOrder) => {
11
11
  const courseCode = order.course.code;
12
12
  const productId = order.product_id;
13
- const relation = CourseProductRelationFactory({
13
+ const offer = OfferFactory({
14
14
  product: ProductFactory({
15
15
  id: order.product_id,
16
16
  contract_definition: order.contract ? ContractDefinitionFactory().one() : undefined,
@@ -22,7 +22,7 @@ export const mockCourseProductWithOrder = (order: CredentialOrder) => {
22
22
 
23
23
  fetchMock.get(
24
24
  `https://joanie.endpoint/api/v1.0/courses/${courseCode}/products/${productId}/`,
25
- relation,
25
+ offer,
26
26
  );
27
- return relation;
27
+ return offer;
28
28
  };
@@ -33,7 +33,7 @@ export const DashboardItemEnrollment = ({ enrollment }: DashboardItemCourseRunPr
33
33
  </div>
34
34
  </div>,
35
35
  ];
36
- enrollment.product_relations.forEach(({ product, is_withdrawable }) => {
36
+ enrollment.offers.forEach(({ product, is_withdrawable }) => {
37
37
  if (isCertificateProduct(product)) {
38
38
  partialFooterList.push(
39
39
  <ProductCertificateFooter
@@ -249,7 +249,7 @@ describe('<ProductCertificateFooter/>', () => {
249
249
  course,
250
250
  }).one(),
251
251
  }).one();
252
- enrollment.product_relations[0].product = CertificateProductFactory().one();
252
+ enrollment.offers[0].product = CertificateProductFactory().one();
253
253
 
254
254
  fetchMock.get(
255
255
  `https://joanie.endpoint/api/v1.0/enrollments/?was_created_by_order=false&is_active=true&page=1&page_size=${PER_PAGE.useOrdersEnrollments}`,
@@ -67,15 +67,15 @@ export const DashboardItemOrder = ({
67
67
  }: DashboardItemOrderProps) => {
68
68
  const { course } = order;
69
69
  const intl = useIntl();
70
- const { item: courseProductRelation } = useCourseProduct({
70
+ const { item: offer } = useCourseProduct({
71
71
  product_id: order.product_id,
72
72
  course_id: course.code,
73
73
  });
74
- const { product } = courseProductRelation || {};
74
+ const { product } = offer || {};
75
75
  const needsSignature = OrderHelper.orderNeedsSignature(order);
76
76
  const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
77
77
  const isActive = OrderHelper.isActive(order);
78
- const isProductPurchasable = ProductHelper.isPurchasable(courseProductRelation?.product);
78
+ const isProductPurchasable = ProductHelper.isPurchasable(offer?.product);
79
79
  const isNotResumable = !isActive && !isProductPurchasable;
80
80
  const canEnroll = OrderHelper.allowEnrollment(order);
81
81
 
@@ -57,7 +57,7 @@ describe('<DashboardItemOrder/> Contract', () => {
57
57
  contract: ContractFactory({ student_signed_on: null }).one(),
58
58
  }).one();
59
59
 
60
- // learner dashboard course page do one call to course product relation per order
60
+ // learner dashboard course page do one call to offer per order
61
61
  const { product } = mockCourseProductWithOrder(order);
62
62
 
63
63
  // overwrite useOmniscientOrders call
@@ -94,7 +94,7 @@ const Installment = ({ order }: Props) => {
94
94
 
95
95
  const PaymentMethodManager = ({ order }: Props) => {
96
96
  const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
97
- const { item: relation, states } = useCourseProduct({
97
+ const { item: offer, states } = useCourseProduct({
98
98
  course_id: order.course.code,
99
99
  product_id: order.product_id,
100
100
  });
@@ -118,9 +118,9 @@ const PaymentMethodManager = ({ order }: Props) => {
118
118
  )}
119
119
  <SaleTunnel
120
120
  {...modal}
121
- product={relation.product as CredentialProduct}
122
- course={relation.course}
123
- isWithdrawable={relation.is_withdrawable}
121
+ product={offer.product as CredentialProduct}
122
+ course={offer.course}
123
+ isWithdrawable={offer.is_withdrawable}
124
124
  />
125
125
  </>
126
126
  );
@@ -25,6 +25,6 @@ export const enrollment: Enrollment = {
25
25
  },
26
26
  languages: ['en'],
27
27
  },
28
- product_relations: [],
28
+ offers: [],
29
29
  certificate_id: null,
30
30
  };
@@ -28,7 +28,7 @@ describe('<ContractNavLink />', () => {
28
28
  fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', []);
29
29
  });
30
30
 
31
- it('should render a ContractNavLink with route and label when neither organizationId and courseProductRelationId are given', () => {
31
+ it('should render a ContractNavLink with route and label when neither organizationId and offerId are given', () => {
32
32
  const link: MenuLink = {
33
33
  to: '/dummy/url/',
34
34
  label: 'My contract navigation link',
@@ -45,31 +45,31 @@ describe('<ContractNavLink />', () => {
45
45
  it.each([
46
46
  {
47
47
  organizationId: faker.string.uuid(),
48
- courseProductRelationId: undefined,
48
+ offerId: undefined,
49
49
  },
50
50
  {
51
51
  organizationId: faker.string.uuid(),
52
- courseProductRelationId: faker.string.uuid(),
52
+ offerId: faker.string.uuid(),
53
53
  },
54
54
  {
55
55
  organizationId: undefined,
56
- courseProductRelationId: faker.string.uuid(),
56
+ offerId: faker.string.uuid(),
57
57
  },
58
58
  {
59
59
  organizationId: undefined,
60
- courseProductRelationId: undefined,
60
+ offerId: undefined,
61
61
  },
62
62
  ])(
63
- 'should never render Badge for organizationId: $organizationId and courseProductId: $courseProductRelationId',
64
- async ({ organizationId, courseProductRelationId }) => {
63
+ 'should never render Badge for organizationId: $organizationId and courseProductId: $offerId',
64
+ async ({ organizationId, offerId }) => {
65
65
  let contractQueryParams: ContractResourceQuery = {
66
66
  signature_state: ContractState.LEARNER_SIGNED,
67
67
  page: 1,
68
68
  page_size: PER_PAGE.teacherContractList,
69
69
  };
70
- if (courseProductRelationId) {
70
+ if (offerId) {
71
71
  contractQueryParams = {
72
- course_product_relation_id: courseProductRelationId,
72
+ offer_id: offerId,
73
73
  ...contractQueryParams,
74
74
  };
75
75
  }
@@ -94,7 +94,7 @@ describe('<ContractNavLink />', () => {
94
94
  label: 'My contract navigation link',
95
95
  }}
96
96
  organizationId={organizationId}
97
- courseProductRelationId={courseProductRelationId}
97
+ offerId={offerId}
98
98
  />,
99
99
  );
100
100
 
@@ -112,25 +112,25 @@ describe('<ContractNavLink />', () => {
112
112
  // with 1 contracts to sign
113
113
  {
114
114
  organizationId: faker.string.uuid(),
115
- courseProductRelationId: undefined,
115
+ offerId: undefined,
116
116
  nbContractsToSign: 1,
117
117
  expectedBadgeCount: 1,
118
118
  },
119
119
  {
120
120
  organizationId: faker.string.uuid(),
121
- courseProductRelationId: faker.string.uuid(),
121
+ offerId: faker.string.uuid(),
122
122
  nbContractsToSign: 1,
123
123
  expectedBadgeCount: 1,
124
124
  },
125
125
  {
126
126
  organizationId: undefined,
127
- courseProductRelationId: faker.string.uuid(),
127
+ offerId: faker.string.uuid(),
128
128
  nbContractsToSign: 1,
129
129
  expectedBadgeCount: undefined,
130
130
  },
131
131
  {
132
132
  organizationId: undefined,
133
- courseProductRelationId: undefined,
133
+ offerId: undefined,
134
134
  nbContractsToSign: 1,
135
135
  expectedBadgeCount: undefined,
136
136
  },
@@ -138,44 +138,39 @@ describe('<ContractNavLink />', () => {
138
138
  // with 0 contracts to sign
139
139
  {
140
140
  organizationId: faker.string.uuid(),
141
- courseProductRelationId: undefined,
141
+ offerId: undefined,
142
142
  nbContractsToSign: 0,
143
143
  expectedBadgeCount: undefined,
144
144
  },
145
145
  {
146
146
  organizationId: faker.string.uuid(),
147
- courseProductRelationId: faker.string.uuid(),
147
+ offerId: faker.string.uuid(),
148
148
  nbContractsToSign: 0,
149
149
  expectedBadgeCount: undefined,
150
150
  },
151
151
  {
152
152
  organizationId: undefined,
153
- courseProductRelationId: faker.string.uuid(),
153
+ offerId: faker.string.uuid(),
154
154
  nbContractsToSign: 0,
155
155
  expectedBadgeCount: undefined,
156
156
  },
157
157
  {
158
158
  organizationId: undefined,
159
- courseProductRelationId: undefined,
159
+ offerId: undefined,
160
160
  nbContractsToSign: 0,
161
161
  expectedBadgeCount: undefined,
162
162
  },
163
163
  ])(
164
- 'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $courseProductRelationId',
165
- async ({
166
- nbContractsToSign,
167
- organizationId,
168
- courseProductRelationId,
169
- expectedBadgeCount,
170
- }) => {
164
+ 'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $offerId',
165
+ async ({ nbContractsToSign, organizationId, offerId, expectedBadgeCount }) => {
171
166
  let contractQueryParams: ContractResourceQuery = {
172
167
  signature_state: ContractState.LEARNER_SIGNED,
173
168
  page: 1,
174
169
  page_size: PER_PAGE.teacherContractList,
175
170
  };
176
- if (courseProductRelationId) {
171
+ if (offerId) {
177
172
  contractQueryParams = {
178
- course_product_relation_id: courseProductRelationId,
173
+ offer_id: offerId,
179
174
  ...contractQueryParams,
180
175
  };
181
176
  }
@@ -199,7 +194,7 @@ describe('<ContractNavLink />', () => {
199
194
  label: 'My contract navigation link',
200
195
  }}
201
196
  organizationId={organizationId}
202
- courseProductRelationId={courseProductRelationId}
197
+ offerId={offerId}
203
198
  />,
204
199
  );
205
200