richie-education 3.1.2 → 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 (80) 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/CredentialSaleTunnel/index.tsx +1 -3
  8. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +3 -1
  9. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -2
  10. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +1 -2
  11. package/js/components/SaleTunnel/index.credential.spec.tsx +5 -19
  12. package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
  13. package/js/components/SaleTunnel/index.spec.tsx +75 -0
  14. package/js/components/SaleTunnel/index.stories.tsx +0 -1
  15. package/js/components/SaleTunnel/index.tsx +2 -2
  16. package/js/components/TeacherDashboardCourseList/index.spec.tsx +3 -3
  17. package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
  18. package/js/hooks/useContractArchive/index.ts +3 -3
  19. package/js/hooks/useCourseProductUnion/index.spec.tsx +16 -18
  20. package/js/hooks/useCourseProductUnion/index.ts +7 -7
  21. package/js/hooks/useCourseProducts.ts +4 -8
  22. package/js/hooks/useDefaultOrganizationId/index.tsx +4 -7
  23. package/js/hooks/useOffer/index.ts +32 -0
  24. package/js/hooks/useTeacherCoursesSearch/index.tsx +4 -4
  25. package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
  26. package/js/pages/DashboardCourses/index.spec.tsx +14 -17
  27. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +8 -14
  28. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +4 -12
  29. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
  30. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -13
  31. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
  32. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +20 -28
  33. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -11
  34. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
  35. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
  36. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
  37. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
  38. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -28
  39. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +13 -23
  40. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -13
  41. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
  42. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +3 -6
  43. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
  44. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
  45. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +4 -7
  46. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
  47. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -10
  48. package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +61 -79
  49. package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
  50. package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
  51. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
  52. package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
  53. package/js/pages/TeacherDashboardTraining/index.spec.tsx +25 -33
  54. package/js/pages/TeacherDashboardTraining/index.tsx +12 -20
  55. package/js/types/Joanie.ts +26 -30
  56. package/js/utils/ProductHelper/index.ts +1 -5
  57. package/js/utils/test/factories/joanie.ts +12 -25
  58. package/js/utils/test/mockCourseProductWithOrder.ts +4 -4
  59. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
  60. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +1 -1
  61. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +3 -3
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +1 -1
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +4 -4
  64. package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +1 -1
  65. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +23 -28
  66. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -8
  67. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +17 -27
  68. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +16 -25
  69. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
  70. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -7
  71. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
  72. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +23 -38
  73. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/_styles.scss +27 -7
  74. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/CourseRunList.tsx +1 -1
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +174 -158
  76. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +9 -2
  77. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +108 -28
  78. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
  79. package/package.json +1 -1
  80. 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,13 +182,18 @@ export interface CourseProductRelationLight {
182
182
  created_on: string;
183
183
  }
184
184
 
185
- export interface CourseProductRelation extends CourseProductRelationLight {
186
- order_groups: OrderGroup[];
185
+ export interface Offer extends OfferLight {
187
186
  is_withdrawable: boolean;
188
- }
189
- export function isCourseProductRelation(
190
- entity: CourseListItem | CourseProductRelationLight | RichieCourse,
191
- ): entity is CourseProductRelationLight {
187
+ discounted_price: Nullable<number>;
188
+ discount_rate: Nullable<number>;
189
+ discount_amount: Nullable<number>;
190
+ discount_start: Nullable<string>;
191
+ discount_end: Nullable<string>;
192
+ description: Nullable<string>;
193
+ nb_seats_available: Nullable<number>;
194
+ seats: Nullable<number>;
195
+ }
196
+ export function isOffer(entity: CourseListItem | OfferLight | RichieCourse): entity is OfferLight {
192
197
  return 'course' in entity && 'product' in entity;
193
198
  }
194
199
 
@@ -233,7 +238,7 @@ export interface Enrollment {
233
238
  was_created_by_order: boolean;
234
239
  created_on: string;
235
240
  orders: OrderEnrollment[];
236
- product_relations: CourseProductRelation[];
241
+ offers: Offer[];
237
242
  certificate_id: Nullable<string>;
238
243
  }
239
244
  export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
@@ -247,7 +252,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
247
252
  'was_created_by_order' in obj &&
248
253
  'created_on' in obj &&
249
254
  'orders' in obj &&
250
- 'product_relations' in obj &&
255
+ 'offers' in obj &&
251
256
  'certificate_id' in obj
252
257
  );
253
258
  };
@@ -325,7 +330,6 @@ export interface Order {
325
330
  enrollment: Nullable<EnrollmentLight>;
326
331
  organization_id: Organization['id'];
327
332
  organization: Organization;
328
- order_group_id?: OrderGroup['id'];
329
333
  payment_schedule?: PaymentSchedule;
330
334
  credit_card_id?: CreditCard['id'];
331
335
  }
@@ -398,18 +402,11 @@ export interface NestedCourseOrder {
398
402
 
399
403
  export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
400
404
  course_id?: CourseListItem['id'];
401
- course_product_relation_id?: CourseProductRelation['id'];
405
+ offer_id?: Offer['id'];
402
406
  organization_id?: Organization['id'];
403
407
  product_id?: Product['id'];
404
408
  }
405
409
 
406
- export interface OrderGroup {
407
- id: string;
408
- is_active: boolean;
409
- nb_seats: number;
410
- nb_available_seats: number;
411
- }
412
-
413
410
  export enum CreditCardBrand {
414
411
  MASTERCARD = 'mastercard',
415
412
  MAESTRO = 'maestro',
@@ -481,7 +478,6 @@ export interface AddressCreationPayload extends Omit<Address, 'id' | 'is_main'>
481
478
 
482
479
  interface AbstractOrderProductCreationPayload {
483
480
  product_id: Product['id'];
484
- order_group_id?: OrderGroup['id'];
485
481
  billing_address: Omit<Address, 'id' | 'is_main'>;
486
482
  has_waived_withdrawal_right: boolean;
487
483
  }
@@ -541,8 +537,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
541
537
  id?: Product['id'];
542
538
  course_id?: CourseListItem['id'];
543
539
  }
544
- export interface CourseProductRelationQueryFilters extends PaginatedResourceQuery {
545
- id?: CourseProductRelation['id'];
540
+ export interface OfferQueryFilters extends PaginatedResourceQuery {
541
+ id?: Offer['id'];
546
542
  organization_id?: Organization['id'];
547
543
  product_type?: ProductType;
548
544
  query?: string;
@@ -555,7 +551,7 @@ export enum ContractState {
555
551
  }
556
552
  export interface ContractResourceQuery extends PaginatedResourceQuery {
557
553
  organization_id?: Organization['id'];
558
- course_product_relation_id?: CourseProductRelation['id'];
554
+ offer_id?: Offer['id'];
559
555
  contract_ids?: Contract['id'][];
560
556
  signature_state?: ContractState;
561
557
  }
@@ -563,7 +559,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
563
559
  export interface OrganizationContractSignatureLinksFilters {
564
560
  contracts_ids?: string[];
565
561
  organization_id: Organization['id'];
566
- course_product_relation_ids?: CourseProductRelation['id'][];
562
+ offer_ids?: Offer['id'][];
567
563
  }
568
564
 
569
565
  export interface ContractInvitationLinkResponse {
@@ -657,10 +653,10 @@ interface APIUser {
657
653
  check: (id: string) => Promise<Response>;
658
654
  create: ({
659
655
  organization_id,
660
- course_product_relation_id,
656
+ offer_id,
661
657
  }: {
662
658
  organization_id?: Organization['id'];
663
- course_product_relation_id?: CourseProductRelation['id'];
659
+ offer_id?: Offer['id'];
664
660
  }) => Promise<{ url: string }>;
665
661
  get: (id: string) => Promise<File>;
666
662
  };
@@ -676,7 +672,7 @@ export interface API {
676
672
  ? Promise<Nullable<CourseListItem>>
677
673
  : Promise<PaginatedResponse<CourseListItem>>;
678
674
  products: {
679
- get(filters?: CourseProductQueryFilters): Promise<Nullable<CourseProductRelation>>;
675
+ get(filters?: CourseProductQueryFilters): Promise<Nullable<Offer>>;
680
676
  paymentSchedule: {
681
677
  get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
682
678
  };
@@ -709,12 +705,12 @@ export interface API {
709
705
  filters?: CourseRunFilters,
710
706
  ): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
711
707
  };
712
- courseProductRelations: {
708
+ offers: {
713
709
  get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
714
710
  filters?: Filters,
715
711
  ): Filters extends { id: string }
716
- ? Promise<Nullable<CourseProductRelation>>
717
- : Promise<PaginatedResponse<CourseProductRelationLight>>;
712
+ ? Promise<Nullable<Offer>>
713
+ : Promise<PaginatedResponse<OfferLight>>;
718
714
  };
719
715
  contractDefinitions: {
720
716
  previewTemplate(id: string): Promise<File>;
@@ -1,5 +1,5 @@
1
1
  import { IntlShape } from 'react-intl';
2
- import { CourseProductRelation, Product, TargetCourse } from 'types/Joanie';
2
+ import { Product, TargetCourse } from 'types/Joanie';
3
3
  import { Maybe } from 'types/utils';
4
4
  import { IntlHelper } from 'utils/IntlHelper';
5
5
  import * as Joanie from 'types/Joanie';
@@ -44,10 +44,6 @@ export class ProductHelper {
44
44
  return IntlHelper.getLocalizedLanguages(uniqueLanguages, intl);
45
45
  }
46
46
 
47
- static getActiveOrderGroups(courseProductRelation: CourseProductRelation) {
48
- return courseProductRelation.order_groups?.filter((orderGroup) => orderGroup.is_active);
49
- }
50
-
51
47
  static hasRemainingSeats(product: Maybe<Product>) {
52
48
  if (!product) return false;
53
49
  return typeof product?.remaining_order_count !== 'number' || product.remaining_order_count > 0;
@@ -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,
@@ -28,7 +28,6 @@ import {
28
28
  NestedCredentialOrder,
29
29
  Order,
30
30
  OrderEnrollment,
31
- OrderGroup,
32
31
  PaymentInstallment,
33
32
  OrderLite,
34
33
  OrderState,
@@ -90,7 +89,7 @@ export const EnrollmentFactory = factory((): Enrollment => {
90
89
  id: faker.string.uuid(),
91
90
  course_run: CourseRunWithCourseFactory().one(),
92
91
  is_active: true,
93
- product_relations: CourseProductRelationFactory().many(1),
92
+ offers: OfferFactory().many(1),
94
93
  state: EnrollmentState.SET,
95
94
  was_created_by_order: false,
96
95
  created_on: faker.date.past({ years: 1 }).toISOString(),
@@ -197,7 +196,7 @@ export const CredentialProductFactory = factory((): CredentialProduct => {
197
196
  created_on: faker.date.past().toISOString(),
198
197
  title: FactoryHelper.sequence((counter) => `Certificate Product ${counter}`),
199
198
  type: ProductType.CREDENTIAL,
200
- price: faker.number.int(),
199
+ price: faker.number.int({ min: 1, max: 1000, multipleOf: 10 }),
201
200
  price_currency: faker.finance.currencyCode(),
202
201
  call_to_action: faker.lorem.words(3),
203
202
  certificate_definition: CertificationDefinitionFactory().one(),
@@ -294,25 +293,6 @@ export const CourseLightFactory = factory((): CourseLight => {
294
293
  };
295
294
  });
296
295
 
297
- export const OrderGroupFactory = factory((): OrderGroup => {
298
- const seats = faker.number.int({ min: 5, max: 100 });
299
- return {
300
- id: faker.string.uuid(),
301
- is_active: true,
302
- nb_seats: seats,
303
- nb_available_seats: faker.number.int({ min: 2, max: seats }),
304
- };
305
- });
306
-
307
- export const OrderGroupFullFactory = factory((): OrderGroup => {
308
- return {
309
- id: faker.string.uuid(),
310
- is_active: true,
311
- nb_seats: faker.number.int({ min: 5, max: 100 }),
312
- nb_available_seats: 0,
313
- };
314
- });
315
-
316
296
  export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
317
297
  return {
318
298
  id: faker.string.uuid(),
@@ -329,15 +309,22 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
329
309
  };
330
310
  });
331
311
 
332
- export const CourseProductRelationFactory = factory((): CourseProductRelation => {
312
+ export const OfferFactory = factory((): Offer => {
333
313
  return {
334
314
  id: faker.string.uuid(),
335
315
  created_on: faker.date.past().toISOString(),
336
316
  course: CourseFactory().one(),
337
317
  product: ProductFactory().one(),
338
318
  organizations: OrganizationFactory().many(1),
339
- order_groups: [],
340
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,
341
328
  };
342
329
  });
343
330
 
@@ -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
  };