richie-education 3.1.3-dev15 → 3.1.3-dev17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/js/api/joanie.ts +8 -8
  2. package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +12 -11
  3. package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
  4. package/js/components/CourseGlimpse/utils.ts +28 -22
  5. package/js/components/CourseGlimpseList/utils.ts +2 -2
  6. package/js/components/PurchaseButton/index.tsx +3 -3
  7. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +3 -3
  8. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +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 -16
  16. package/js/hooks/useCourseProductUnion/index.ts +7 -7
  17. package/js/hooks/useCourseProducts.ts +4 -4
  18. package/js/hooks/useDefaultOrganizationId/index.tsx +4 -4
  19. package/js/hooks/useOffering/index.ts +32 -0
  20. package/js/hooks/useTeacherCoursesSearch/index.tsx +4 -4
  21. package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
  22. package/js/pages/DashboardCourses/index.spec.tsx +17 -14
  23. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +11 -8
  24. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +6 -3
  25. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
  26. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -10
  27. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
  28. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +5 -5
  29. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -8
  30. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
  31. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
  32. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
  33. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
  34. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -21
  35. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +19 -13
  36. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -11
  37. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
  38. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +6 -3
  39. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
  40. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
  41. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +7 -4
  42. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
  43. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -5
  44. package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +55 -55
  45. package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
  46. package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
  47. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
  48. package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
  49. package/js/pages/TeacherDashboardTraining/index.spec.tsx +25 -25
  50. package/js/pages/TeacherDashboardTraining/index.tsx +16 -12
  51. package/js/types/Joanie.ts +21 -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 -23
  61. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -4
  62. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +20 -17
  63. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +22 -16
  64. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
  65. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -3
  66. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
  67. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +14 -10
  68. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +87 -63
  69. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +2 -2
  70. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +24 -20
  71. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +8 -8
  72. package/package.json +1 -1
  73. package/js/hooks/useOffer/index.ts +0 -32
@@ -6,7 +6,7 @@ import {
6
6
  PacedCourseFactory,
7
7
  } from 'utils/test/factories/richie';
8
8
  import {
9
- OfferFactory,
9
+ OfferingFactory,
10
10
  EnrollmentFactory,
11
11
  CredentialOrderFactory,
12
12
  ProductFactory,
@@ -74,8 +74,8 @@ describe('CourseProductItem', () => {
74
74
  }).format(price);
75
75
 
76
76
  it('should display a loader until product is loaded', async () => {
77
- const offer = OfferFactory().one();
78
- const { product } = offer;
77
+ const offering = OfferingFactory().one();
78
+ const { product } = offering;
79
79
  const productDeferred = new Deferred();
80
80
  fetchMock.get(
81
81
  `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
@@ -92,14 +92,17 @@ describe('CourseProductItem', () => {
92
92
 
93
93
  // - A loader should be displayed while product information are fetching
94
94
  await expectSpinner('Loading product information...');
95
- productDeferred.resolve(offer);
95
+ productDeferred.resolve(offering);
96
96
  await expectNoSpinner('Loading product information...');
97
97
  });
98
98
 
99
99
  it('renders product information for anonymous user', async () => {
100
- const offer = OfferFactory().one();
101
- const { product } = offer;
102
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
100
+ const offering = OfferingFactory().one();
101
+ const { product } = offering;
102
+ fetchMock.get(
103
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
104
+ offering,
105
+ );
103
106
 
104
107
  render(
105
108
  <CourseProductItem
@@ -128,7 +131,7 @@ describe('CourseProductItem', () => {
128
131
  ).not.toBeInTheDocument();
129
132
 
130
133
  // - Render all target courses information
131
- offer.product.target_courses.forEach((course) => {
134
+ offering.product.target_courses.forEach((course) => {
132
135
  const $item = screen.getByTestId(`course-item-${course.code}`);
133
136
  // the course title shouldn't be a heading to prevent misdirection for screen reader users,
134
137
  // but we want to it to visually look like a h5
@@ -148,7 +151,7 @@ describe('CourseProductItem', () => {
148
151
  });
149
152
 
150
153
  it('renders discount rate for anonymous user', async () => {
151
- const offer = OfferFactory({
154
+ const offering = OfferingFactory({
152
155
  product: CredentialProductFactory({
153
156
  price: 840,
154
157
  price_currency: 'EUR',
@@ -161,8 +164,11 @@ describe('CourseProductItem', () => {
161
164
  discount_end: new Date('2023-12-31T23:59:59Z').toISOString(),
162
165
  },
163
166
  }).one();
164
- const { product } = offer;
165
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
167
+ const { product } = offering;
168
+ fetchMock.get(
169
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
170
+ offering,
171
+ );
166
172
 
167
173
  render(
168
174
  <CourseProductItem
@@ -188,7 +194,7 @@ describe('CourseProductItem', () => {
188
194
  const discountedPriceLabel = screen.getByText('Discounted price:');
189
195
  expect(discountedPriceLabel.classList.contains('offscreen')).toBe(true);
190
196
  const discountedPrice = screen.getByText(
191
- priceFormatter(product.price_currency, offer.rules.discounted_price!).replace(
197
+ priceFormatter(product.price_currency, offering.rules.discounted_price!).replace(
192
198
  /(\u202F|\u00a0)/g,
193
199
  ' ',
194
200
  ),
@@ -208,7 +214,7 @@ describe('CourseProductItem', () => {
208
214
  });
209
215
 
210
216
  it('renders discount amount for anonymous user', async () => {
211
- const offer = OfferFactory({
217
+ const offering = OfferingFactory({
212
218
  product: CredentialProductFactory({
213
219
  price: 840,
214
220
  price_currency: 'EUR',
@@ -221,8 +227,11 @@ describe('CourseProductItem', () => {
221
227
  discount_end: new Date('2023-12-31T23:59:59Z').toISOString(),
222
228
  },
223
229
  }).one();
224
- const { product } = offer;
225
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
230
+ const { product } = offering;
231
+ fetchMock.get(
232
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
233
+ offering,
234
+ );
226
235
 
227
236
  render(
228
237
  <CourseProductItem
@@ -248,7 +257,7 @@ describe('CourseProductItem', () => {
248
257
  const discountedPriceLabel = screen.getByText('Discounted price:');
249
258
  expect(discountedPriceLabel.classList.contains('offscreen')).toBe(true);
250
259
  const discountedPrice = screen.getByText(
251
- priceFormatter(product.price_currency, offer.rules.discounted_price!).replace(
260
+ priceFormatter(product.price_currency, offering.rules.discounted_price!).replace(
252
261
  /(\u202F|\u00a0)/g,
253
262
  ' ',
254
263
  ),
@@ -268,13 +277,16 @@ describe('CourseProductItem', () => {
268
277
  });
269
278
 
270
279
  it('does not render <CertificateItem /> if product do not have a certificate', async () => {
271
- const offer = OfferFactory({
280
+ const offering = OfferingFactory({
272
281
  product: ProductFactory({
273
282
  certificate_definition: undefined,
274
283
  }).one(),
275
284
  }).one();
276
- const { product } = offer;
277
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
285
+ const { product } = offering;
286
+ fetchMock.get(
287
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
288
+ offering,
289
+ );
278
290
 
279
291
  render(
280
292
  <CourseProductItem
@@ -292,16 +304,16 @@ describe('CourseProductItem', () => {
292
304
  });
293
305
 
294
306
  it('renders product information in compact mode', async () => {
295
- const offer = OfferFactory().one();
307
+ const offering = OfferingFactory().one();
296
308
  fetchMock.get(
297
- `https://joanie.endpoint/api/v1.0/courses/00000/products/${offer.product.id}/`,
298
- offer,
309
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${offering.product.id}/`,
310
+ offering,
299
311
  );
300
312
 
301
313
  const { container } = render(
302
314
  <CourseProductItem
303
315
  course={PacedCourseFactory({ code: '00000' }).one()}
304
- productId={offer.product.id}
316
+ productId={offering.product.id}
305
317
  compact
306
318
  />,
307
319
  { queryOptions: { client: createTestQueryClient({ user: null }) } },
@@ -309,14 +321,14 @@ describe('CourseProductItem', () => {
309
321
 
310
322
  // In the header, we should display the product title, the product price
311
323
  // and product date range and languages
312
- await screen.findByRole('heading', { level: 3, name: offer.product.title });
324
+ await screen.findByRole('heading', { level: 3, name: offering.product.title });
313
325
  // the price shouldn't be a heading to prevent misdirection for screen reader users,
314
326
  // but we want to it to visually look like a h6
315
327
 
316
328
  const $price = screen.getByText(
317
329
  // the price formatter generates non-breaking spaces and getByText doesn't seem to handle that well, replace it
318
330
  // with a regular space. We replace NNBSP (\u202F) and NBSP (\u00a0) with a regular space
319
- priceFormatter(offer.product.price_currency, offer.product.price).replace(
331
+ priceFormatter(offering.product.price_currency, offering.product.price).replace(
320
332
  /(\u202F|\u00a0)/g,
321
333
  ' ',
322
334
  ),
@@ -332,7 +344,7 @@ describe('CourseProductItem', () => {
332
344
  expect($productWidgetContent).not.toBeInTheDocument();
333
345
 
334
346
  // - Any target courses information should be displayed
335
- offer.product.target_courses.forEach((course) => {
347
+ offering.product.target_courses.forEach((course) => {
336
348
  const $item = screen.queryByTestId(`course-item-${course.code}`);
337
349
  expect($item).not.toBeInTheDocument();
338
350
  });
@@ -341,7 +353,7 @@ describe('CourseProductItem', () => {
341
353
  expect(screen.queryByTestId('CertificateItem')).not.toBeInTheDocument();
342
354
 
343
355
  // - Render a login button
344
- screen.getByRole('button', { name: `Login to purchase "${offer.product.title}"` });
356
+ screen.getByRole('button', { name: `Login to purchase "${offering.product.title}"` });
345
357
  // - Does not render PurchaseButton cta
346
358
  expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
347
359
  });
@@ -349,8 +361,8 @@ describe('CourseProductItem', () => {
349
361
  it.each([OrderState.PENDING, OrderState.NO_PAYMENT])(
350
362
  'renders product informations for %s order',
351
363
  async (state) => {
352
- const offer = OfferFactory().one();
353
- const { product } = offer;
364
+ const offering = OfferingFactory().one();
365
+ const { product } = offering;
354
366
  const order = CredentialOrderFactory({
355
367
  product_id: product.id,
356
368
  course: PacedCourseFactory({ code: '00000' }).one(),
@@ -360,7 +372,7 @@ describe('CourseProductItem', () => {
360
372
 
361
373
  fetchMock.get(
362
374
  `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
363
- offer,
375
+ offering,
364
376
  );
365
377
  const orderQueryParameters = {
366
378
  course_code: order.course.code,
@@ -374,13 +386,13 @@ describe('CourseProductItem', () => {
374
386
  render(
375
387
  <CourseProductItem
376
388
  course={PacedCourseFactory({ code: '00000' }).one()}
377
- productId={offer.product.id}
389
+ productId={offering.product.id}
378
390
  />,
379
391
  );
380
392
 
381
393
  // In the header, we should display the product title, the product price
382
394
  // and product date range and languages
383
- await screen.findByRole('heading', { level: 3, name: offer.product.title });
395
+ await screen.findByRole('heading', { level: 3, name: offering.product.title });
384
396
  // the price shouldn't be a heading to prevent misdirection for screen reader users,
385
397
  // but we want to it to visually look like a h6
386
398
 
@@ -410,8 +422,8 @@ describe('CourseProductItem', () => {
410
422
  it.each([OrderState.PENDING, OrderState.NO_PAYMENT])(
411
423
  'renders product informations for %s order in compact mode',
412
424
  async (state) => {
413
- const offer = OfferFactory().one();
414
- const { product } = offer;
425
+ const offering = OfferingFactory().one();
426
+ const { product } = offering;
415
427
  const order = CredentialOrderFactory({
416
428
  product_id: product.id,
417
429
  course: PacedCourseFactory({ code: '00000' }).one(),
@@ -421,7 +433,7 @@ describe('CourseProductItem', () => {
421
433
 
422
434
  fetchMock.get(
423
435
  `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
424
- offer,
436
+ offering,
425
437
  );
426
438
  const orderQueryParameters = {
427
439
  course_code: order.course.code,
@@ -435,14 +447,14 @@ describe('CourseProductItem', () => {
435
447
  render(
436
448
  <CourseProductItem
437
449
  course={PacedCourseFactory({ code: '00000' }).one()}
438
- productId={offer.product.id}
450
+ productId={offering.product.id}
439
451
  compact
440
452
  />,
441
453
  );
442
454
 
443
455
  // In the header, we should display the product title, the product price
444
456
  // and product date range and languages
445
- await screen.findByRole('heading', { level: 3, name: offer.product.title });
457
+ await screen.findByRole('heading', { level: 3, name: offering.product.title });
446
458
  // the price shouldn't be a heading to prevent misdirection for screen reader users,
447
459
  // but we want to it to visually look like a h6
448
460
 
@@ -452,7 +464,7 @@ describe('CourseProductItem', () => {
452
464
  expect($enrolledInfo.classList.contains('h6')).toBe(true);
453
465
 
454
466
  // - Any target courses information should be displayed
455
- offer.product.target_courses.forEach((course) => {
467
+ offering.product.target_courses.forEach((course) => {
456
468
  const $item = screen.queryByTestId(`course-item-${course.code}`);
457
469
  expect($item).not.toBeInTheDocument();
458
470
  });
@@ -463,8 +475,8 @@ describe('CourseProductItem', () => {
463
475
  );
464
476
 
465
477
  it.each(ENROLLABLE_ORDER_STATES)('renders product information for a %s order', async (state) => {
466
- const offer = OfferFactory().one();
467
- const { product } = offer;
478
+ const offering = OfferingFactory().one();
479
+ const { product } = offering;
468
480
  const order = CredentialOrderFactory({
469
481
  product_id: product.id,
470
482
  course: PacedCourseFactory({ code: '00000' }).one(),
@@ -472,7 +484,10 @@ describe('CourseProductItem', () => {
472
484
  state,
473
485
  }).one();
474
486
 
475
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
487
+ fetchMock.get(
488
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
489
+ offering,
490
+ );
476
491
  const orderQueryParameters = {
477
492
  course_code: order.course.code,
478
493
  product_id: order.product_id,
@@ -522,17 +537,17 @@ describe('CourseProductItem', () => {
522
537
  it.each(ENROLLABLE_ORDER_STATES)(
523
538
  'renders product informations for a %s order in compact mode',
524
539
  async (state) => {
525
- const offer = OfferFactory().one();
540
+ const offering = OfferingFactory().one();
526
541
  const order: CredentialOrder = CredentialOrderFactory({
527
- product_id: offer.product.id,
542
+ product_id: offering.product.id,
528
543
  course: PacedCourseFactory({ code: '00000' }).one(),
529
- target_courses: offer.product.target_courses,
544
+ target_courses: offering.product.target_courses,
530
545
  state,
531
546
  }).one();
532
547
 
533
548
  fetchMock.get(
534
- `https://joanie.endpoint/api/v1.0/courses/00000/products/${offer.product.id}/`,
535
- offer,
549
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${offering.product.id}/`,
550
+ offering,
536
551
  );
537
552
  const orderQueryParameters = {
538
553
  product_id: order.product_id,
@@ -546,14 +561,14 @@ describe('CourseProductItem', () => {
546
561
 
547
562
  render(
548
563
  <CourseProductItem
549
- productId={offer.product.id}
564
+ productId={offering.product.id}
550
565
  course={PacedCourseFactory({ code: '00000' }).one()}
551
566
  compact
552
567
  />,
553
568
  );
554
569
 
555
570
  // Wait for product information to be fetched
556
- await screen.findByRole('heading', { level: 3, name: offer.product.title });
571
+ await screen.findByRole('heading', { level: 3, name: offering.product.title });
557
572
 
558
573
  // - In place of product price, a label should be displayed
559
574
  const $enrolledInfo = await screen.findByText('Purchased');
@@ -590,8 +605,8 @@ describe('CourseProductItem', () => {
590
605
  );
591
606
 
592
607
  it('renders enrollment information when user is enrolled to a course run', async () => {
593
- const offer = OfferFactory().one();
594
- const { product } = offer;
608
+ const offering = OfferingFactory().one();
609
+ const { product } = offering;
595
610
  // - Create an order with an active enrollment
596
611
  const enrollment: Enrollment = EnrollmentFactory({
597
612
  course_run: product.target_courses[0]!.course_runs[0]! as CourseRun,
@@ -603,7 +618,10 @@ describe('CourseProductItem', () => {
603
618
  target_enrollments: [enrollment],
604
619
  }).one();
605
620
 
606
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
621
+ fetchMock.get(
622
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
623
+ offering,
624
+ );
607
625
  const orderQueryParameters = {
608
626
  product_id: order.product_id,
609
627
  course_code: order.course?.code,
@@ -654,8 +672,8 @@ describe('CourseProductItem', () => {
654
672
  it.each(PURCHASABLE_ORDER_STATES)(
655
673
  'renders sale tunnel button if user already has a %s order',
656
674
  async (state) => {
657
- const offer = OfferFactory().one();
658
- const { product } = offer;
675
+ const offering = OfferingFactory().one();
676
+ const { product } = offering;
659
677
  const order = CredentialOrderFactory({
660
678
  product_id: product.id,
661
679
  course: PacedCourseFactory({ code: '00000' }).one(),
@@ -664,7 +682,7 @@ describe('CourseProductItem', () => {
664
682
  }).one();
665
683
  fetchMock.get(
666
684
  `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
667
- offer,
685
+ offering,
668
686
  );
669
687
  const orderQueryParameters = {
670
688
  product_id: order.product_id,
@@ -695,7 +713,7 @@ describe('CourseProductItem', () => {
695
713
  expect($price.classList.contains('h6')).toBe(true);
696
714
 
697
715
  // - Render all target courses information
698
- offer.product.target_courses.forEach((course) => {
716
+ offering.product.target_courses.forEach((course) => {
699
717
  const $item = screen.getByTestId(`course-item-${course.code}`);
700
718
  // the course title shouldn't be a heading to prevent misdirection for screen reader users,
701
719
  // but we want to it to visually look like a h5
@@ -710,9 +728,12 @@ describe('CourseProductItem', () => {
710
728
  );
711
729
 
712
730
  it('renders sale tunnel button if user already has a canceled order', async () => {
713
- const offer = OfferFactory().one();
714
- const { product } = offer;
715
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
731
+ const offering = OfferingFactory().one();
732
+ const { product } = offering;
733
+ fetchMock.get(
734
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
735
+ offering,
736
+ );
716
737
  const orderQueryParameters = {
717
738
  product_id: product.id,
718
739
  course_code: '00000',
@@ -742,7 +763,7 @@ describe('CourseProductItem', () => {
742
763
  expect($price.classList.contains('h6')).toBe(true);
743
764
 
744
765
  // - Render all target courses information
745
- offer.product.target_courses.forEach((course) => {
766
+ offering.product.target_courses.forEach((course) => {
746
767
  const $item = screen.getByTestId(`course-item-${course.code}`);
747
768
  // the course title shouldn't be a heading to prevent misdirection for screen reader users,
748
769
  // but we want to it to visually look like a h5
@@ -756,7 +777,7 @@ describe('CourseProductItem', () => {
756
777
  });
757
778
 
758
779
  it('renders error message when product fetching has failed', async () => {
759
- const { product } = OfferFactory().one();
780
+ const { product } = OfferingFactory().one();
760
781
 
761
782
  fetchMock.get(
762
783
  `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
@@ -777,20 +798,23 @@ describe('CourseProductItem', () => {
777
798
  });
778
799
 
779
800
  it('renders a warning message that tells that no seats are left', async () => {
780
- const offer = OfferFactory({
801
+ const offering = OfferingFactory({
781
802
  rules: {
782
803
  nb_available_seats: 0,
783
804
  has_seats_left: false,
784
805
  },
785
806
  }).one();
786
- const { product } = offer;
807
+ const { product } = offering;
787
808
  const order = CredentialOrderFactory({
788
809
  product_id: product.id,
789
810
  course: PacedCourseFactory({ code: '00000' }).one(),
790
811
  target_courses: product.target_courses,
791
812
  state: OrderState.DRAFT,
792
813
  }).one();
793
- fetchMock.get(`https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`, offer);
814
+ fetchMock.get(
815
+ `https://joanie.endpoint/api/v1.0/courses/00000/products/${product.id}/`,
816
+ offering,
817
+ );
794
818
  const orderQueryParameters = {
795
819
  product_id: order.product_id,
796
820
  course_code: order.course?.code,
@@ -3,7 +3,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
3
3
  import fetchMock from 'fetch-mock';
4
4
  import { StorybookHelper } from 'utils/StorybookHelper';
5
5
  import {
6
- OfferFactory,
6
+ OfferingFactory,
7
7
  CourseRunFactory,
8
8
  CredentialOrderFactory,
9
9
  CredentialProductFactory,
@@ -22,7 +22,7 @@ const render = (args: CourseProductItemProps, options?: Maybe<{ order: Credentia
22
22
  fetchMock.get(`http://localhost:8071/api/v1.0/addresses/`, [], { overwriteRoutes: true });
23
23
  fetchMock.get(
24
24
  `http://localhost:8071/api/v1.0/courses/${args.course.code}/products/${args.productId}/`,
25
- OfferFactory({
25
+ OfferingFactory({
26
26
  product: CredentialProductFactory({
27
27
  price: 840,
28
28
  price_currency: 'EUR',
@@ -1,7 +1,7 @@
1
1
  import { Children, useEffect, useMemo } from 'react';
2
2
  import { defineMessages, FormattedMessage, FormattedNumber, useIntl } from 'react-intl';
3
3
  import c from 'classnames';
4
- import { Offer, CredentialOrder, Product, ProductType } from 'types/Joanie';
4
+ import { Offering, CredentialOrder, Product, ProductType } from 'types/Joanie';
5
5
  import { useCourseProduct } from 'hooks/useCourseProducts';
6
6
  import { Spinner } from 'components/Spinner';
7
7
  import { Icon, IconTypeEnum } from 'components/Icon';
@@ -77,9 +77,9 @@ type HeaderProps = {
77
77
  canPurchase: boolean;
78
78
  order: Maybe<CredentialOrder>;
79
79
  product: Product;
80
- offer: Offer;
80
+ offering: Offering;
81
81
  };
82
- const Header = ({ product, order, offer, hasPurchased, canPurchase, compact }: HeaderProps) => {
82
+ const Header = ({ product, order, offering, hasPurchased, canPurchase, compact }: HeaderProps) => {
83
83
  const intl = useIntl();
84
84
  const formatDate = useDateFormat();
85
85
 
@@ -103,7 +103,7 @@ const Header = ({ product, order, offer, hasPurchased, canPurchase, compact }: H
103
103
  return null;
104
104
  }
105
105
 
106
- if (offer.rules.discounted_price != null) {
106
+ if (offering.rules.discounted_price != null) {
107
107
  return (
108
108
  <>
109
109
  <span id="original-price" className="offscreen">
@@ -122,7 +122,7 @@ const Header = ({ product, order, offer, hasPurchased, canPurchase, compact }: H
122
122
  <ins aria-describedby="discount-price" className="product-widget__price-discount">
123
123
  <FormattedNumber
124
124
  currency={product.price_currency}
125
- value={offer.rules.discounted_price}
125
+ value={offering.rules.discounted_price}
126
126
  style="currency"
127
127
  />
128
128
  </ins>
@@ -133,7 +133,7 @@ const Header = ({ product, order, offer, hasPurchased, canPurchase, compact }: H
133
133
  return (
134
134
  <FormattedNumber currency={product.price_currency} value={product.price} style="currency" />
135
135
  );
136
- }, [canPurchase, offer.rules.discounted_price, product.price]);
136
+ }, [canPurchase, offering.rules.discounted_price, product.price]);
137
137
 
138
138
  return (
139
139
  <header className="product-widget__header">
@@ -144,39 +144,39 @@ const Header = ({ product, order, offer, hasPurchased, canPurchase, compact }: H
144
144
  {hasPurchased && <FormattedMessage {...messages.purchased} />}
145
145
  {displayPrice}
146
146
  </strong>
147
- {offer?.rules.description && (
148
- <p className="product-widget__header-description">{offer.rules.description}</p>
147
+ {offering?.rules.description && (
148
+ <p className="product-widget__header-description">{offering.rules.description}</p>
149
149
  )}
150
- {offer?.rules.discounted_price && (
150
+ {offering?.rules.discounted_price && (
151
151
  <p className="product-widget__header-discount">
152
- {offer.rules.discount_rate ? (
152
+ {offering.rules.discount_rate ? (
153
153
  <span className="product-widget__header-discount-rate">
154
- <FormattedNumber value={-offer.rules.discount_rate} style="percent" />
154
+ <FormattedNumber value={-offering.rules.discount_rate} style="percent" />
155
155
  </span>
156
156
  ) : (
157
157
  <span className="product-widget__header-discount-amount">
158
158
  <FormattedNumber
159
159
  currency={product.price_currency}
160
- value={-offer.rules.discount_amount!}
160
+ value={-offering.rules.discount_amount!}
161
161
  style="currency"
162
162
  />
163
163
  </span>
164
164
  )}
165
- {offer.rules.discount_start && (
165
+ {offering.rules.discount_start && (
166
166
  <span className="product-widget__header-discount-date">
167
167
  &nbsp;
168
168
  <FormattedMessage
169
169
  {...messages.from}
170
- values={{ from: formatDate(offer.rules.discount_start) }}
170
+ values={{ from: formatDate(offering.rules.discount_start) }}
171
171
  />
172
172
  </span>
173
173
  )}
174
- {offer.rules.discount_end && (
174
+ {offering.rules.discount_end && (
175
175
  <span className="product-widget__header-discount-date">
176
176
  &nbsp;
177
177
  <FormattedMessage
178
178
  {...messages.to}
179
- values={{ to: formatDate(offer.rules.discount_end) }}
179
+ values={{ to: formatDate(offering.rules.discount_end) }}
180
180
  />
181
181
  </span>
182
182
  )}
@@ -239,12 +239,12 @@ const Content = ({ product, order }: { product: Product; order?: CredentialOrder
239
239
  const CourseProductItem = ({ productId, course, compact = false }: CourseProductItemProps) => {
240
240
  // FIXME(rlecellier): useCourseProduct need's a filter on product.type that only return
241
241
  // CredentialOrder
242
- const { item: offer, states: productQueryStates } = useCourseProduct({
242
+ const { item: offering, states: productQueryStates } = useCourseProduct({
243
243
  product_id: productId,
244
244
  course_id: course.code,
245
245
  });
246
246
 
247
- const product = offer?.product;
247
+ const product = offering?.product;
248
248
  const { item: productOrder, states: orderQueryStates } = useProductOrder({
249
249
  productId,
250
250
  courseCode: course.code,
@@ -302,14 +302,18 @@ const CourseProductItem = ({ productId, course, compact = false }: CourseProduct
302
302
  <Header
303
303
  product={product}
304
304
  order={order}
305
- offer={offer}
305
+ offering={offering}
306
306
  canPurchase={canPurchase}
307
307
  hasPurchased={hasPurchased}
308
308
  compact={compact}
309
309
  />
310
310
  {canShowContent && <Content product={product} order={order} />}
311
311
  <footer className="product-widget__footer">
312
- <CourseProductItemFooter course={course} offer={offer} canPurchase={canPurchase} />
312
+ <CourseProductItemFooter
313
+ course={course}
314
+ offering={offering}
315
+ canPurchase={canPurchase}
316
+ />
313
317
  </footer>
314
318
  </>
315
319
  )}
@@ -22,8 +22,8 @@ import {
22
22
  import SyllabusCourseRunsList from 'widgets/SyllabusCourseRunsList/index';
23
23
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
24
24
  import { CourseRun, Priority } from 'types';
25
- import { Offer } from 'types/Joanie';
26
- import { OfferFactory } from 'utils/test/factories/joanie';
25
+ import { Offering } from 'types/Joanie';
26
+ import { OfferingFactory } from 'utils/test/factories/joanie';
27
27
  import { DEFAULT_DATE_FORMAT } from 'hooks/useDateFormat';
28
28
  import { StringHelper } from 'utils/StringHelper';
29
29
  import { computeStates } from 'utils/CourseRuns';
@@ -211,9 +211,9 @@ describe('<SyllabusCourseRunsList/>', () => {
211
211
  });
212
212
  };
213
213
 
214
- const expectCourseProduct = async (container: HTMLElement, offer: Offer) => {
214
+ const expectCourseProduct = async (container: HTMLElement, offering: Offering) => {
215
215
  const heading = await findByRole(container, 'heading', {
216
- name: offer.product.title,
216
+ name: offering.product.title,
217
217
  });
218
218
  expect(Array.from(heading.classList)).toContain('product-widget__title');
219
219
  };
@@ -383,9 +383,9 @@ describe('<SyllabusCourseRunsList/>', () => {
383
383
 
384
384
  it('has one opened product', async () => {
385
385
  const course = PacedCourseFactory().one();
386
- const offer = OfferFactory().one();
387
- const resourceLink = `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${offer.product.id}/`;
388
- fetchMock.get(resourceLink, offer);
386
+ const offering = OfferingFactory().one();
387
+ const resourceLink = `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${offering.product.id}/`;
388
+ fetchMock.get(resourceLink, offering);
389
389
 
390
390
  const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
391
391
  resource_link: resourceLink,
@@ -406,7 +406,7 @@ describe('<SyllabusCourseRunsList/>', () => {
406
406
  expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
407
407
  1,
408
408
  );
409
- await expectCourseProduct(getHeaderContainer(), offer);
409
+ await expectCourseProduct(getHeaderContainer(), offering);
410
410
 
411
411
  // Portal.
412
412
  expectEmptyPortalContainer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "3.1.3-dev15",
3
+ "version": "3.1.3-dev17",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {