richie-education 3.1.3-dev3 → 3.1.3-dev30

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 (93) hide show
  1. package/.storybook/__mocks__/utils/context.ts +4 -0
  2. package/js/api/joanie.ts +8 -8
  3. package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +11 -19
  4. package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
  5. package/js/components/CourseGlimpse/CourseGlimpseFooter.tsx +30 -5
  6. package/js/components/CourseGlimpse/index.spec.tsx +18 -0
  7. package/js/components/CourseGlimpse/index.stories.tsx +75 -4
  8. package/js/components/CourseGlimpse/index.tsx +4 -0
  9. package/js/components/CourseGlimpse/utils.ts +35 -30
  10. package/js/components/CourseGlimpseList/utils.ts +2 -2
  11. package/js/components/PurchaseButton/index.tsx +3 -3
  12. package/js/components/SaleTunnel/CredentialSaleTunnel/index.tsx +1 -3
  13. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +13 -1
  14. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +9 -7
  15. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +1 -2
  16. package/js/components/SaleTunnel/index.credential.spec.tsx +5 -19
  17. package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
  18. package/js/components/SaleTunnel/index.spec.tsx +171 -29
  19. package/js/components/SaleTunnel/index.stories.tsx +17 -3
  20. package/js/components/SaleTunnel/index.tsx +2 -2
  21. package/js/components/TeacherDashboardCourseList/index.spec.tsx +3 -3
  22. package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
  23. package/js/hooks/useContractArchive/index.ts +3 -3
  24. package/js/hooks/useCourseProductUnion/index.spec.tsx +16 -18
  25. package/js/hooks/useCourseProductUnion/index.ts +7 -7
  26. package/js/hooks/useCourseProducts.ts +4 -8
  27. package/js/hooks/useDefaultOrganizationId/index.tsx +4 -7
  28. package/js/hooks/useOffering/index.ts +32 -0
  29. package/js/hooks/useTeacherCoursesSearch/index.tsx +2 -2
  30. package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
  31. package/js/pages/DashboardCourses/index.spec.tsx +14 -14
  32. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +11 -14
  33. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +4 -9
  34. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
  35. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -13
  36. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
  37. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +20 -28
  38. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -11
  39. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
  40. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
  41. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
  42. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
  43. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -28
  44. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +13 -17
  45. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -13
  46. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
  47. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +3 -3
  48. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
  49. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
  50. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +4 -4
  51. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
  52. package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -10
  53. package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +61 -79
  54. package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
  55. package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
  56. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
  57. package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
  58. package/js/pages/TeacherDashboardTraining/index.spec.tsx +21 -29
  59. package/js/pages/TeacherDashboardTraining/index.tsx +12 -16
  60. package/js/types/Course.ts +4 -0
  61. package/js/types/Joanie.ts +36 -29
  62. package/js/types/index.ts +6 -2
  63. package/js/utils/ProductHelper/index.ts +1 -5
  64. package/js/utils/test/factories/joanie.ts +19 -25
  65. package/js/utils/test/factories/richie.ts +10 -2
  66. package/js/utils/test/mockCourseProductWithOrder.ts +4 -4
  67. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
  68. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +1 -1
  69. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +3 -3
  70. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +1 -1
  71. package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +4 -4
  72. package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +1 -1
  73. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +23 -28
  74. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -8
  75. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +17 -24
  76. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +18 -21
  77. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
  78. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -7
  79. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
  80. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +19 -34
  81. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/_styles.scss +35 -8
  82. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/CourseRunList.tsx +3 -3
  83. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/_styles.scss +9 -0
  84. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +186 -140
  85. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +11 -2
  86. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +111 -24
  87. package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.stories.tsx +81 -0
  88. package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +14 -0
  89. package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +14 -0
  90. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +54 -8
  91. package/package.json +1 -1
  92. package/scss/objects/_course_glimpses.scss +16 -0
  93. package/js/hooks/useCourseProductRelation/index.ts +0 -44
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
4
4
  import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
5
5
  import {
6
6
  CourseListItemFactory,
7
- CourseProductRelationFactory,
7
+ OfferingFactory,
8
8
  OrganizationFactory,
9
9
  } from 'utils/test/factories/joanie';
10
10
  import { expectNoSpinner } from 'utils/test/expectSpinner';
@@ -59,8 +59,8 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
59
59
  mockPaginatedResponse(CourseListItemFactory().many(15), 15, false),
60
60
  );
61
61
  fetchMock.get(
62
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/?product_type=credential&page=1&page_size=${perPage}`,
63
- mockPaginatedResponse(CourseProductRelationFactory().many(15), 15, false),
62
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
63
+ mockPaginatedResponse(OfferingFactory().many(15), 15, false),
64
64
  );
65
65
  fetchMock.get(
66
66
  `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?signature_state=half_signed&page=1`,
@@ -76,7 +76,7 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
76
76
  await expectNoSpinner('Loading courses...');
77
77
 
78
78
  nbApiCalls += 1; // course api call
79
- nbApiCalls += 1; // course-product-relations api call
79
+ nbApiCalls += 1; // offerings api call
80
80
  nbApiCalls += 1; // contracts api call
81
81
  const calledUrls = fetchMock.calls().map((call) => call[0]);
82
82
  expect(calledUrls).toHaveLength(nbApiCalls);
@@ -84,7 +84,7 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
84
84
  `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/courses/?has_listed_course_runs=true&page=1&page_size=${perPage}`,
85
85
  );
86
86
  expect(calledUrls).toContain(
87
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/?product_type=credential&page=1&page_size=${perPage}`,
87
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
88
88
  );
89
89
  await expectNoSpinner('Loading organization...');
90
90
 
@@ -117,8 +117,8 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
117
117
  mockPaginatedResponse(CourseListItemFactory().many(15), 15, false),
118
118
  );
119
119
  fetchMock.get(
120
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/?product_type=credential&page=1&page_size=${perPage}`,
121
- mockPaginatedResponse(CourseProductRelationFactory().many(15), 15, false),
120
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?product_type=credential&page=1&page_size=${perPage}`,
121
+ mockPaginatedResponse(OfferingFactory().many(15), 15, false),
122
122
  );
123
123
 
124
124
  render(<TeacherDashboardOrganizationCourseLoader />, {
@@ -136,22 +136,22 @@ describe('components/TeacherDashboardOrganizationCourseLoader', () => {
136
136
  mockPaginatedResponse(CourseListItemFactory().many(5), 5, false),
137
137
  );
138
138
  fetchMock.get(
139
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
140
- mockPaginatedResponse(CourseProductRelationFactory().many(5), 5, false),
139
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
140
+ mockPaginatedResponse(OfferingFactory().many(5), 5, false),
141
141
  );
142
142
  const user = userEvent.setup();
143
143
  await user.type(screen.getByRole('textbox', { name: /Search/ }), 'text query');
144
144
  await user.click(screen.getByRole('button', { name: /Search/ }));
145
145
 
146
146
  nbApiCalls = 1; // course api call
147
- nbApiCalls += 1; // course-product-relations api call
147
+ nbApiCalls += 1; // offerings api call
148
148
  const calledUrls = fetchMock.calls().map((call) => call[0]);
149
149
  expect(calledUrls).toHaveLength(nbApiCalls);
150
150
  expect(calledUrls).toContain(
151
151
  `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/courses/?query=text+query&has_listed_course_runs=true&page=1&page_size=${perPage}`,
152
152
  );
153
153
  expect(calledUrls).toContain(
154
- `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/course-product-relations/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
154
+ `https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/?query=text+query&product_type=credential&page=1&page_size=${perPage}`,
155
155
  );
156
156
 
157
157
  await waitFor(() => {
@@ -1,10 +1,10 @@
1
1
  import { FormattedMessage, defineMessages } from 'react-intl';
2
2
  import { useParams } from 'react-router';
3
3
 
4
+ import { useOffering } from 'hooks/useOffering';
4
5
  import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
5
6
  import { TeacherDashboardCourseSidebar } from 'widgets/Dashboard/components/TeacherDashboardCourseSidebar';
6
7
  import { Spinner } from 'components/Spinner';
7
- import { useCourseProductRelation } from 'hooks/useCourseProductRelation';
8
8
  import { useBreadcrumbsPlaceholders } from 'hooks/useBreadcrumbsPlaceholders';
9
9
  import { TeacherDashboardTraining } from '.';
10
10
 
@@ -22,17 +22,17 @@ const messages = defineMessages({
22
22
  });
23
23
 
24
24
  export const TeacherDashboardTrainingLoader = () => {
25
- const { courseProductRelationId, organizationId } = useParams<{
26
- courseProductRelationId: string;
25
+ const { offeringId, organizationId } = useParams<{
26
+ offeringId: string;
27
27
  organizationId?: string;
28
28
  }>();
29
29
 
30
30
  const {
31
- item: courseProductRelation,
31
+ item: offering,
32
32
  states: { fetching },
33
- } = useCourseProductRelation(courseProductRelationId, { organization_id: organizationId });
33
+ } = useOffering(offeringId, { organization_id: organizationId });
34
34
  useBreadcrumbsPlaceholders({
35
- courseTitle: courseProductRelation?.product.title ?? '',
35
+ courseTitle: offering?.product.title ?? '',
36
36
  });
37
37
  return (
38
38
  <DashboardLayout sidebar={<TeacherDashboardCourseSidebar />}>
@@ -48,7 +48,7 @@ export const TeacherDashboardTrainingLoader = () => {
48
48
  </span>
49
49
  </Spinner>
50
50
  ) : (
51
- <TeacherDashboardTraining courseProductRelation={courseProductRelation} />
51
+ <TeacherDashboardTraining offering={offering} />
52
52
  )}
53
53
  </DashboardLayout>
54
54
  );
@@ -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 { OfferingFactory, 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 offering = OfferingFactory().one();
50
+ fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/?offering_id=${offering.id}`, []);
51
+ fetchMock.get(`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/`, offering);
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: ':offeringId',
71
65
  element: <TeacherDashboardTrainingLoader />,
72
66
  },
73
67
  ],
74
68
  {
75
- initialEntries: [`/${courseProductRelation.id}`],
69
+ initialEntries: [`/${offering.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; // offerings 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/offerings/${offering.id}/`);
97
89
 
98
90
  // main titles
99
91
  expect(
@@ -103,27 +95,27 @@ describe('components/TeacherDashboardTrainingLoader', () => {
103
95
  ).toBeInTheDocument();
104
96
 
105
97
  expect(
106
- screen.getAllByRole('heading', { name: capitalize(courseProductRelation.product.title) }),
98
+ screen.getAllByRole('heading', { name: capitalize(offering.product.title) }),
107
99
  ).toHaveLength(2);
108
100
 
109
- const nbCourseRun = courseProductRelation.product.target_courses.reduce(
101
+ const nbCourseRun = offering.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 offering with organization id if there is one in the path', async () => {
117
109
  const organization = OrganizationFactory().one();
118
- const courseProductRelation = CourseProductRelationFactory({
110
+ const offering = OfferingFactory({
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}/offerings/${offering.id}/`,
115
+ offering,
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/?offering_id=${offering.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/:offeringId',
142
134
  element: <TeacherDashboardTrainingLoader />,
143
135
  },
144
136
  ],
145
137
  {
146
- initialEntries: [`/${organization.id}/${courseProductRelation.id}`],
138
+ initialEntries: [`/${organization.id}/${offering.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; // offerings 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}/offerings/${offering.id}/`,
167
159
  );
168
160
 
169
161
  // main titles
@@ -174,10 +166,10 @@ describe('components/TeacherDashboardTrainingLoader', () => {
174
166
  ).toBeInTheDocument();
175
167
 
176
168
  expect(
177
- screen.getAllByRole('heading', { name: capitalize(courseProductRelation.product.title) }),
169
+ screen.getAllByRole('heading', { name: capitalize(offering.product.title) }),
178
170
  ).toHaveLength(2);
179
171
 
180
- const nbCourseRun = courseProductRelation.product.target_courses.reduce(
172
+ const nbCourseRun = offering.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 { Offering } from 'types/Joanie';
10
10
 
11
11
  const messages = defineMessages({
12
- errorNoCourseProductRelation: {
12
+ errorNoOffering: {
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 offering is not found',
15
+ id: 'components.TeacherDashboardTraining.errorNoOffering',
16
16
  },
17
17
  });
18
18
 
19
19
  interface TeacherDashboardTrainingProps {
20
- courseProductRelation: CourseProductRelation;
20
+ offering: Offering;
21
21
  }
22
22
 
23
- export const TeacherDashboardTraining = ({
24
- courseProductRelation,
25
- }: TeacherDashboardTrainingProps) => {
23
+ export const TeacherDashboardTraining = ({ offering }: TeacherDashboardTrainingProps) => {
26
24
  const intl = useIntl();
27
- return courseProductRelation ? (
25
+ return offering ? (
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(offering.product.title)}</h2>
37
33
  </div>
38
- {courseProductRelation.product.description && (
39
- <div className="dashboard__quote">{courseProductRelation.product.description}</div>
34
+ {offering.product.description && (
35
+ <div className="dashboard__quote">{offering.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
+ {offering.product.target_courses.map((course) => (
48
44
  <DashboardLayout.Section key={`course_target_${course.code}`}>
49
45
  <DashboardCard
50
46
  className="icon-arrow-right-rounded"
@@ -67,7 +63,7 @@ export const TeacherDashboardTraining = ({
67
63
  </div>
68
64
  ) : (
69
65
  <Banner
70
- message={intl.formatMessage(messages.errorNoCourseProductRelation)}
66
+ message={intl.formatMessage(messages.errorNoOffering)}
71
67
  type={BannerType.ERROR}
72
68
  rounded
73
69
  />
@@ -47,6 +47,10 @@ export interface Course extends Resource {
47
47
  certificate_price: Nullable<number>;
48
48
  price: Nullable<number>;
49
49
  price_currency: string;
50
+ discounted_price: Nullable<number>;
51
+ discount: Nullable<string>;
52
+ certificate_discounted_price: Nullable<number>;
53
+ certificate_discount: Nullable<string>;
50
54
  }
51
55
 
52
56
  export function isRichieCourse(course: Course | JoanieCourse): course is Course {
@@ -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
+ offering_id?: Offering['id'];
42
42
  }
43
43
 
44
44
  export interface ContractDefinition {
@@ -149,6 +149,10 @@ export interface Product {
149
149
  state: CourseState;
150
150
  instructions: Nullable<string>;
151
151
  contract_definition?: ContractDefinition;
152
+ discounted_price: Nullable<number>;
153
+ discount: Nullable<string>;
154
+ certificate_discounted_price: Nullable<number>;
155
+ certificate_discount: Nullable<string>;
152
156
  }
153
157
 
154
158
  export interface CredentialProduct extends Product {
@@ -174,7 +178,7 @@ export interface DefinitionResourcesProduct {
174
178
  contract_definition_id: Nullable<ContractDefinition['id']>;
175
179
  }
176
180
 
177
- export interface CourseProductRelationLight {
181
+ export interface OfferingLight {
178
182
  id: string;
179
183
  course: CourseLight;
180
184
  organizations: Organization[];
@@ -182,13 +186,25 @@ export interface CourseProductRelationLight {
182
186
  created_on: string;
183
187
  }
184
188
 
185
- export interface CourseProductRelation extends CourseProductRelationLight {
186
- order_groups: OrderGroup[];
189
+ export interface OfferingRule {
190
+ discounted_price: Nullable<number>;
191
+ discount_rate: Nullable<number>;
192
+ discount_amount: Nullable<number>;
193
+ discount_start: Nullable<string>;
194
+ discount_end: Nullable<string>;
195
+ description: Nullable<string>;
196
+ nb_available_seats: Nullable<number>;
197
+ has_seat_limit: boolean;
198
+ has_seats_left: boolean;
199
+ }
200
+
201
+ export interface Offering extends OfferingLight {
187
202
  is_withdrawable: boolean;
203
+ rules: OfferingRule;
188
204
  }
189
- export function isCourseProductRelation(
190
- entity: CourseListItem | CourseProductRelationLight | RichieCourse,
191
- ): entity is CourseProductRelationLight {
205
+ export function isOffering(
206
+ entity: CourseListItem | OfferingLight | RichieCourse,
207
+ ): entity is OfferingLight {
192
208
  return 'course' in entity && 'product' in entity;
193
209
  }
194
210
 
@@ -233,7 +249,7 @@ export interface Enrollment {
233
249
  was_created_by_order: boolean;
234
250
  created_on: string;
235
251
  orders: OrderEnrollment[];
236
- product_relations: CourseProductRelation[];
252
+ offerings: Offering[];
237
253
  certificate_id: Nullable<string>;
238
254
  }
239
255
  export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj is Enrollment => {
@@ -247,7 +263,7 @@ export const isEnrollment = (obj: unknown | Enrollment | OpenEdXEnrollment): obj
247
263
  'was_created_by_order' in obj &&
248
264
  'created_on' in obj &&
249
265
  'orders' in obj &&
250
- 'product_relations' in obj &&
266
+ 'offerings' in obj &&
251
267
  'certificate_id' in obj
252
268
  );
253
269
  };
@@ -325,7 +341,6 @@ export interface Order {
325
341
  enrollment: Nullable<EnrollmentLight>;
326
342
  organization_id: Organization['id'];
327
343
  organization: Organization;
328
- order_group_id?: OrderGroup['id'];
329
344
  payment_schedule?: PaymentSchedule;
330
345
  credit_card_id?: CreditCard['id'];
331
346
  }
@@ -398,18 +413,11 @@ export interface NestedCourseOrder {
398
413
 
399
414
  export interface CourseOrderResourceQuery extends PaginatedResourceQuery {
400
415
  course_id?: CourseListItem['id'];
401
- course_product_relation_id?: CourseProductRelation['id'];
416
+ offering_id?: Offering['id'];
402
417
  organization_id?: Organization['id'];
403
418
  product_id?: Product['id'];
404
419
  }
405
420
 
406
- export interface OrderGroup {
407
- id: string;
408
- is_active: boolean;
409
- nb_seats: number;
410
- nb_available_seats: number;
411
- }
412
-
413
421
  export enum CreditCardBrand {
414
422
  MASTERCARD = 'mastercard',
415
423
  MAESTRO = 'maestro',
@@ -481,7 +489,6 @@ export interface AddressCreationPayload extends Omit<Address, 'id' | 'is_main'>
481
489
 
482
490
  interface AbstractOrderProductCreationPayload {
483
491
  product_id: Product['id'];
484
- order_group_id?: OrderGroup['id'];
485
492
  billing_address: Omit<Address, 'id' | 'is_main'>;
486
493
  has_waived_withdrawal_right: boolean;
487
494
  }
@@ -541,8 +548,8 @@ export interface CourseProductQueryFilters extends ResourcesQuery {
541
548
  id?: Product['id'];
542
549
  course_id?: CourseListItem['id'];
543
550
  }
544
- export interface CourseProductRelationQueryFilters extends PaginatedResourceQuery {
545
- id?: CourseProductRelation['id'];
551
+ export interface OfferingQueryFilters extends PaginatedResourceQuery {
552
+ id?: Offering['id'];
546
553
  organization_id?: Organization['id'];
547
554
  product_type?: ProductType;
548
555
  query?: string;
@@ -555,7 +562,7 @@ export enum ContractState {
555
562
  }
556
563
  export interface ContractResourceQuery extends PaginatedResourceQuery {
557
564
  organization_id?: Organization['id'];
558
- course_product_relation_id?: CourseProductRelation['id'];
565
+ offering_id?: Offering['id'];
559
566
  contract_ids?: Contract['id'][];
560
567
  signature_state?: ContractState;
561
568
  }
@@ -563,7 +570,7 @@ export interface ContractResourceQuery extends PaginatedResourceQuery {
563
570
  export interface OrganizationContractSignatureLinksFilters {
564
571
  contracts_ids?: string[];
565
572
  organization_id: Organization['id'];
566
- course_product_relation_ids?: CourseProductRelation['id'][];
573
+ offering_ids?: Offering['id'][];
567
574
  }
568
575
 
569
576
  export interface ContractInvitationLinkResponse {
@@ -657,10 +664,10 @@ interface APIUser {
657
664
  check: (id: string) => Promise<Response>;
658
665
  create: ({
659
666
  organization_id,
660
- course_product_relation_id,
667
+ offering_id,
661
668
  }: {
662
669
  organization_id?: Organization['id'];
663
- course_product_relation_id?: CourseProductRelation['id'];
670
+ offering_id?: Offering['id'];
664
671
  }) => Promise<{ url: string }>;
665
672
  get: (id: string) => Promise<File>;
666
673
  };
@@ -676,7 +683,7 @@ export interface API {
676
683
  ? Promise<Nullable<CourseListItem>>
677
684
  : Promise<PaginatedResponse<CourseListItem>>;
678
685
  products: {
679
- get(filters?: CourseProductQueryFilters): Promise<Nullable<CourseProductRelation>>;
686
+ get(filters?: CourseProductQueryFilters): Promise<Nullable<Offering>>;
680
687
  paymentSchedule: {
681
688
  get(filters?: CourseProductQueryFilters): Promise<Nullable<PaymentSchedule>>;
682
689
  };
@@ -709,12 +716,12 @@ export interface API {
709
716
  filters?: CourseRunFilters,
710
717
  ): CourseRunFilters extends { id: string } ? Promise<Nullable<CourseRun>> : Promise<CourseRun>;
711
718
  };
712
- courseProductRelations: {
719
+ offerings: {
713
720
  get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
714
721
  filters?: Filters,
715
722
  ): Filters extends { id: string }
716
- ? Promise<Nullable<CourseProductRelation>>
717
- : Promise<PaginatedResponse<CourseProductRelationLight>>;
723
+ ? Promise<Nullable<Offering>>
724
+ : Promise<PaginatedResponse<OfferingLight>>;
718
725
  };
719
726
  contractDefinitions: {
720
727
  previewTemplate(id: string): Promise<File>;
package/js/types/index.ts CHANGED
@@ -35,11 +35,15 @@ export interface CourseRun {
35
35
  title?: string;
36
36
  snapshot?: string;
37
37
  display_mode: CourseRunDisplayMode;
38
- price?: number;
38
+ price?: Nullable<number>;
39
39
  price_currency?: string;
40
40
  offer?: string;
41
- certificate_price?: number;
41
+ certificate_price?: Nullable<number>;
42
42
  certificate_offer?: string;
43
+ discounted_price: Nullable<number>;
44
+ discount: Nullable<string>;
45
+ certificate_discounted_price: Nullable<number>;
46
+ certificate_discount: Nullable<string>;
43
47
  }
44
48
 
45
49
  export enum Priority {
@@ -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
+ Offering,
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
+ offerings: OfferingFactory().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(),
@@ -206,6 +205,10 @@ export const CredentialProductFactory = factory((): CredentialProduct => {
206
205
  remaining_order_count: faker.number.int({ min: 1, max: 100 }),
207
206
  state: CourseStateFactory().one(),
208
207
  instructions: null,
208
+ discounted_price: null,
209
+ discount: null,
210
+ certificate_discounted_price: null,
211
+ certificate_discount: null,
209
212
  };
210
213
  });
211
214
 
@@ -294,25 +297,6 @@ export const CourseLightFactory = factory((): CourseLight => {
294
297
  };
295
298
  });
296
299
 
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
300
  export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
317
301
  return {
318
302
  id: faker.string.uuid(),
@@ -329,15 +313,25 @@ export const NestedCourseOrderFactory = factory((): NestedCourseOrder => {
329
313
  };
330
314
  });
331
315
 
332
- export const CourseProductRelationFactory = factory((): CourseProductRelation => {
316
+ export const OfferingFactory = factory((): Offering => {
333
317
  return {
334
318
  id: faker.string.uuid(),
335
319
  created_on: faker.date.past().toISOString(),
336
320
  course: CourseFactory().one(),
337
321
  product: ProductFactory().one(),
338
322
  organizations: OrganizationFactory().many(1),
339
- order_groups: [],
340
323
  is_withdrawable: true,
324
+ rules: {
325
+ discounted_price: null,
326
+ discount_rate: null,
327
+ discount_amount: null,
328
+ discount_start: null,
329
+ discount_end: null,
330
+ description: null,
331
+ nb_available_seats: null,
332
+ has_seat_limit: false,
333
+ has_seats_left: true,
334
+ },
341
335
  };
342
336
  });
343
337