richie-education 3.1.3-dev8 → 3.2.1-dev1
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.
- package/.storybook/__mocks__/utils/context.ts +4 -0
- package/i18n/locales/ar-SA.json +30 -10
- package/i18n/locales/es-ES.json +30 -10
- package/i18n/locales/fa-IR.json +30 -10
- package/i18n/locales/fr-CA.json +31 -11
- package/i18n/locales/fr-FR.json +32 -12
- package/i18n/locales/ko-KR.json +30 -10
- package/i18n/locales/pt-PT.json +30 -10
- package/i18n/locales/ru-RU.json +30 -10
- package/i18n/locales/vi-VN.json +30 -10
- package/js/api/joanie.ts +8 -8
- package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +11 -19
- package/js/components/ContractFrame/OrganizationContractFrame.tsx +4 -4
- package/js/components/CourseGlimpse/CourseGlimpseFooter.tsx +30 -5
- package/js/components/CourseGlimpse/index.spec.tsx +18 -0
- package/js/components/CourseGlimpse/index.stories.tsx +75 -4
- package/js/components/CourseGlimpse/index.tsx +4 -0
- package/js/components/CourseGlimpse/utils.ts +35 -30
- package/js/components/CourseGlimpseList/utils.ts +2 -2
- package/js/components/PurchaseButton/index.tsx +3 -3
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +6 -3
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +9 -7
- package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -3
- package/js/components/SaleTunnel/index.spec.tsx +131 -64
- package/js/components/SaleTunnel/index.stories.tsx +17 -2
- package/js/components/SaleTunnel/index.tsx +2 -2
- package/js/components/TeacherDashboardCourseList/index.spec.tsx +3 -3
- package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
- package/js/hooks/useContractArchive/index.ts +3 -3
- package/js/hooks/useCourseProductUnion/index.spec.tsx +16 -18
- package/js/hooks/useCourseProductUnion/index.ts +7 -7
- package/js/hooks/useCourseProducts.ts +4 -8
- package/js/hooks/useDefaultOrganizationId/index.tsx +4 -7
- package/js/hooks/useOffering/index.ts +32 -0
- package/js/hooks/useTeacherCoursesSearch/index.tsx +2 -2
- package/js/hooks/useTeacherPendingContractsCount/index.ts +4 -4
- package/js/pages/DashboardCourses/index.spec.tsx +14 -14
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +11 -14
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +4 -9
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +10 -13
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +20 -28
- package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +8 -11
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +6 -6
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +7 -7
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +5 -5
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +21 -28
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +13 -17
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +11 -13
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +6 -6
- package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +3 -3
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +16 -16
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +4 -4
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.spec.tsx +21 -21
- package/js/pages/TeacherDashboardCourseLearnersLayout/hooks/useCourseLearnersFilters/index.ts +5 -10
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.spec.tsx +61 -79
- package/js/pages/TeacherDashboardCourseLearnersLayout/index.tsx +1 -1
- package/js/pages/TeacherDashboardCoursesLoader/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +11 -11
- package/js/pages/TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx +7 -7
- package/js/pages/TeacherDashboardTraining/index.spec.tsx +21 -29
- package/js/pages/TeacherDashboardTraining/index.tsx +12 -16
- package/js/settings/index.ts +1 -0
- package/js/settings/settings.prod.ts +1 -0
- package/js/translations/ar-SA.json +1 -1
- package/js/translations/es-ES.json +1 -1
- package/js/translations/fa-IR.json +1 -1
- package/js/translations/fr-CA.json +1 -1
- package/js/translations/fr-FR.json +1 -1
- package/js/translations/ko-KR.json +1 -1
- package/js/translations/pt-PT.json +1 -1
- package/js/translations/ru-RU.json +1 -1
- package/js/translations/vi-VN.json +1 -1
- package/js/types/Course.ts +4 -0
- package/js/types/Joanie.ts +31 -22
- package/js/types/index.ts +6 -2
- package/js/utils/test/factories/joanie.ts +18 -11
- package/js/utils/test/factories/richie.ts +10 -2
- package/js/utils/test/mockCourseProductWithOrder.ts +4 -4
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +3 -3
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +4 -4
- package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +1 -1
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +23 -28
- package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +4 -8
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +17 -24
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.tsx +18 -21
- package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +4 -4
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +3 -7
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +4 -4
- package/js/widgets/Slider/components/SlidePanel.tsx +9 -0
- package/js/widgets/Slider/index.stories.tsx +53 -0
- package/js/widgets/Slider/index.tsx +21 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +10 -14
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/_styles.scss +8 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/CourseRunList.tsx +2 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCourseRuns/_styles.scss +9 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +116 -75
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +6 -4
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +29 -30
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.stories.tsx +81 -0
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +36 -2
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +36 -2
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +58 -8
- package/package.json +2 -1
- package/scss/colors/_theme.scss +3 -0
- package/scss/components/templates/richie/slider/_slider.scss +19 -0
- package/scss/objects/_blogpost_glimpses.scss +5 -0
- package/scss/objects/_course_glimpses.scss +16 -0
- package/js/hooks/useCourseProductRelation/index.ts +0 -44
|
@@ -67,15 +67,15 @@ export const DashboardItemOrder = ({
|
|
|
67
67
|
}: DashboardItemOrderProps) => {
|
|
68
68
|
const { course } = order;
|
|
69
69
|
const intl = useIntl();
|
|
70
|
-
const { item:
|
|
70
|
+
const { item: offering } = useCourseProduct({
|
|
71
71
|
product_id: order.product_id,
|
|
72
72
|
course_id: course.code,
|
|
73
73
|
});
|
|
74
|
-
const { product } =
|
|
74
|
+
const { product } = offering || {};
|
|
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(
|
|
78
|
+
const isProductPurchasable = ProductHelper.isPurchasable(offering?.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
|
|
60
|
+
// learner dashboard course page do one call to offering 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:
|
|
97
|
+
const { item: offering, 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={
|
|
122
|
-
course={
|
|
123
|
-
isWithdrawable={
|
|
121
|
+
product={offering.product as CredentialProduct}
|
|
122
|
+
course={offering.course}
|
|
123
|
+
isWithdrawable={offering.is_withdrawable}
|
|
124
124
|
/>
|
|
125
125
|
</>
|
|
126
126
|
);
|
package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx
CHANGED
|
@@ -28,7 +28,7 @@ describe('<ContractNavLink />', () => {
|
|
|
28
28
|
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', []);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('should render a ContractNavLink with route and label when neither organizationId and
|
|
31
|
+
it('should render a ContractNavLink with route and label when neither organizationId and offeringId are given', () => {
|
|
32
32
|
const link: MenuLink = {
|
|
33
33
|
to: '/dummy/url/',
|
|
34
34
|
label: 'My contract navigation link',
|
|
@@ -45,31 +45,31 @@ describe('<ContractNavLink />', () => {
|
|
|
45
45
|
it.each([
|
|
46
46
|
{
|
|
47
47
|
organizationId: faker.string.uuid(),
|
|
48
|
-
|
|
48
|
+
offeringId: undefined,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
organizationId: faker.string.uuid(),
|
|
52
|
-
|
|
52
|
+
offeringId: faker.string.uuid(),
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
organizationId: undefined,
|
|
56
|
-
|
|
56
|
+
offeringId: faker.string.uuid(),
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
organizationId: undefined,
|
|
60
|
-
|
|
60
|
+
offeringId: undefined,
|
|
61
61
|
},
|
|
62
62
|
])(
|
|
63
|
-
'should never render Badge for organizationId: $organizationId and courseProductId: $
|
|
64
|
-
async ({ organizationId,
|
|
63
|
+
'should never render Badge for organizationId: $organizationId and courseProductId: $offeringId',
|
|
64
|
+
async ({ organizationId, offeringId }) => {
|
|
65
65
|
let contractQueryParams: ContractResourceQuery = {
|
|
66
66
|
signature_state: ContractState.LEARNER_SIGNED,
|
|
67
67
|
page: 1,
|
|
68
68
|
page_size: PER_PAGE.teacherContractList,
|
|
69
69
|
};
|
|
70
|
-
if (
|
|
70
|
+
if (offeringId) {
|
|
71
71
|
contractQueryParams = {
|
|
72
|
-
|
|
72
|
+
offering_id: offeringId,
|
|
73
73
|
...contractQueryParams,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -94,7 +94,7 @@ describe('<ContractNavLink />', () => {
|
|
|
94
94
|
label: 'My contract navigation link',
|
|
95
95
|
}}
|
|
96
96
|
organizationId={organizationId}
|
|
97
|
-
|
|
97
|
+
offeringId={offeringId}
|
|
98
98
|
/>,
|
|
99
99
|
);
|
|
100
100
|
|
|
@@ -112,25 +112,25 @@ describe('<ContractNavLink />', () => {
|
|
|
112
112
|
// with 1 contracts to sign
|
|
113
113
|
{
|
|
114
114
|
organizationId: faker.string.uuid(),
|
|
115
|
-
|
|
115
|
+
offeringId: undefined,
|
|
116
116
|
nbContractsToSign: 1,
|
|
117
117
|
expectedBadgeCount: 1,
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
organizationId: faker.string.uuid(),
|
|
121
|
-
|
|
121
|
+
offeringId: faker.string.uuid(),
|
|
122
122
|
nbContractsToSign: 1,
|
|
123
123
|
expectedBadgeCount: 1,
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
organizationId: undefined,
|
|
127
|
-
|
|
127
|
+
offeringId: faker.string.uuid(),
|
|
128
128
|
nbContractsToSign: 1,
|
|
129
129
|
expectedBadgeCount: undefined,
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
organizationId: undefined,
|
|
133
|
-
|
|
133
|
+
offeringId: undefined,
|
|
134
134
|
nbContractsToSign: 1,
|
|
135
135
|
expectedBadgeCount: undefined,
|
|
136
136
|
},
|
|
@@ -138,44 +138,39 @@ describe('<ContractNavLink />', () => {
|
|
|
138
138
|
// with 0 contracts to sign
|
|
139
139
|
{
|
|
140
140
|
organizationId: faker.string.uuid(),
|
|
141
|
-
|
|
141
|
+
offeringId: undefined,
|
|
142
142
|
nbContractsToSign: 0,
|
|
143
143
|
expectedBadgeCount: undefined,
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
organizationId: faker.string.uuid(),
|
|
147
|
-
|
|
147
|
+
offeringId: faker.string.uuid(),
|
|
148
148
|
nbContractsToSign: 0,
|
|
149
149
|
expectedBadgeCount: undefined,
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
organizationId: undefined,
|
|
153
|
-
|
|
153
|
+
offeringId: faker.string.uuid(),
|
|
154
154
|
nbContractsToSign: 0,
|
|
155
155
|
expectedBadgeCount: undefined,
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
organizationId: undefined,
|
|
159
|
-
|
|
159
|
+
offeringId: undefined,
|
|
160
160
|
nbContractsToSign: 0,
|
|
161
161
|
expectedBadgeCount: undefined,
|
|
162
162
|
},
|
|
163
163
|
])(
|
|
164
|
-
'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $
|
|
165
|
-
async ({
|
|
166
|
-
nbContractsToSign,
|
|
167
|
-
organizationId,
|
|
168
|
-
courseProductRelationId,
|
|
169
|
-
expectedBadgeCount,
|
|
170
|
-
}) => {
|
|
164
|
+
'should render Badge (count: $expectedBadgeCount) for nb contracts to sign: $nbContractsToSign, organizationId: $organizationId and courseProductId: $offeringId',
|
|
165
|
+
async ({ nbContractsToSign, organizationId, offeringId, expectedBadgeCount }) => {
|
|
171
166
|
let contractQueryParams: ContractResourceQuery = {
|
|
172
167
|
signature_state: ContractState.LEARNER_SIGNED,
|
|
173
168
|
page: 1,
|
|
174
169
|
page_size: PER_PAGE.teacherContractList,
|
|
175
170
|
};
|
|
176
|
-
if (
|
|
171
|
+
if (offeringId) {
|
|
177
172
|
contractQueryParams = {
|
|
178
|
-
|
|
173
|
+
offering_id: offeringId,
|
|
179
174
|
...contractQueryParams,
|
|
180
175
|
};
|
|
181
176
|
}
|
|
@@ -199,7 +194,7 @@ describe('<ContractNavLink />', () => {
|
|
|
199
194
|
label: 'My contract navigation link',
|
|
200
195
|
}}
|
|
201
196
|
organizationId={organizationId}
|
|
202
|
-
|
|
197
|
+
offeringId={offeringId}
|
|
203
198
|
/>,
|
|
204
199
|
);
|
|
205
200
|
|
package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createSearchParams } from 'react-router';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { MenuLink } from 'widgets/Dashboard/components/DashboardSidebar';
|
|
4
|
-
import { ContractState,
|
|
4
|
+
import { ContractState, Offering, Organization } from 'types/Joanie';
|
|
5
5
|
import useTeacherPendingContractsCount from 'hooks/useTeacherPendingContractsCount';
|
|
6
6
|
import { ContractActions } from 'utils/AbilitiesHelper/types';
|
|
7
7
|
import useContractAbilities from 'hooks/useContractAbilities';
|
|
@@ -11,18 +11,14 @@ import MenuNavLink from '../MenuNavLink';
|
|
|
11
11
|
interface ContractNavLinkProps {
|
|
12
12
|
link: MenuLink;
|
|
13
13
|
organizationId?: Organization['id'];
|
|
14
|
-
|
|
14
|
+
offeringId?: Offering['id'];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const ContractNavLink = ({
|
|
18
|
-
link,
|
|
19
|
-
organizationId,
|
|
20
|
-
courseProductRelationId,
|
|
21
|
-
}: ContractNavLinkProps) => {
|
|
17
|
+
const ContractNavLink = ({ link, organizationId, offeringId }: ContractNavLinkProps) => {
|
|
22
18
|
const defaultOrganizationId = useDefaultOrganizationId();
|
|
23
19
|
const { contracts: pendingContracts, pendingContractCount } = useTeacherPendingContractsCount({
|
|
24
20
|
organizationId: organizationId || defaultOrganizationId,
|
|
25
|
-
|
|
21
|
+
offeringId,
|
|
26
22
|
});
|
|
27
23
|
const contractAbilities = useContractAbilities(pendingContracts);
|
|
28
24
|
const canSignContracts = contractAbilities.can(ContractActions.SIGN);
|
|
@@ -4,11 +4,7 @@ import { createIntl } from 'react-intl';
|
|
|
4
4
|
import { generatePath } from 'react-router';
|
|
5
5
|
import { CourseListItem } from 'types/Joanie';
|
|
6
6
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
7
|
-
import {
|
|
8
|
-
CourseFactory,
|
|
9
|
-
CourseProductRelationFactory,
|
|
10
|
-
OrganizationFactory,
|
|
11
|
-
} from 'utils/test/factories/joanie';
|
|
7
|
+
import { CourseFactory, OfferingFactory, OrganizationFactory } from 'utils/test/factories/joanie';
|
|
12
8
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
13
9
|
import { render } from 'utils/test/render';
|
|
14
10
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
@@ -65,14 +61,14 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
65
61
|
label: 'course',
|
|
66
62
|
course: CourseFactory().one(),
|
|
67
63
|
organization: undefined,
|
|
68
|
-
|
|
64
|
+
offering: undefined,
|
|
69
65
|
expectedRoutes: [TeacherDashboardPaths.COURSE_GENERAL_INFORMATION],
|
|
70
66
|
},
|
|
71
67
|
{
|
|
72
68
|
label: 'training',
|
|
73
69
|
course: CourseFactory().one(),
|
|
74
70
|
organization: undefined,
|
|
75
|
-
|
|
71
|
+
offering: OfferingFactory().one(),
|
|
76
72
|
expectedRoutes: [
|
|
77
73
|
TeacherDashboardPaths.COURSE_PRODUCT,
|
|
78
74
|
TeacherDashboardPaths.COURSE_PRODUCT_CONTRACTS,
|
|
@@ -83,14 +79,14 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
83
79
|
label: "organization's course",
|
|
84
80
|
course: CourseFactory().one(),
|
|
85
81
|
organization: OrganizationFactory().one(),
|
|
86
|
-
|
|
82
|
+
offering: undefined,
|
|
87
83
|
expectedRoutes: [TeacherDashboardPaths.ORGANIZATION_COURSE_GENERAL_INFORMATION],
|
|
88
84
|
},
|
|
89
85
|
{
|
|
90
86
|
label: "organization's training",
|
|
91
87
|
course: CourseFactory().one(),
|
|
92
88
|
organization: OrganizationFactory().one(),
|
|
93
|
-
|
|
89
|
+
offering: OfferingFactory().one(),
|
|
94
90
|
expectedRoutes: [
|
|
95
91
|
TeacherDashboardPaths.ORGANIZATION_PRODUCT,
|
|
96
92
|
TeacherDashboardPaths.ORGANIZATION_PRODUCT_CONTRACTS,
|
|
@@ -99,20 +95,20 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
99
95
|
},
|
|
100
96
|
])(
|
|
101
97
|
'should display menu items for "$label" route',
|
|
102
|
-
async ({ course, organization,
|
|
98
|
+
async ({ course, organization, offering, expectedRoutes }) => {
|
|
103
99
|
// mock api for organization's training
|
|
104
|
-
if (organization &&
|
|
100
|
+
if (organization && offering) {
|
|
105
101
|
// fetching training's contracts
|
|
106
102
|
nbApiRequest += 1;
|
|
107
103
|
fetchMock.get(
|
|
108
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?
|
|
104
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/contracts/?offering_id=${offering.id}&signature_state=half_signed&page=1&page_size=25`,
|
|
109
105
|
[],
|
|
110
106
|
);
|
|
111
107
|
// fetching organization's training
|
|
112
108
|
nbApiRequest += 1;
|
|
113
109
|
fetchMock.get(
|
|
114
|
-
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/
|
|
115
|
-
|
|
110
|
+
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/offerings/${offering.id}/`,
|
|
111
|
+
offering,
|
|
116
112
|
);
|
|
117
113
|
} else if (organization) {
|
|
118
114
|
// fetching organization's course
|
|
@@ -121,16 +117,13 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
121
117
|
`https://joanie.endpoint/api/v1.0/organizations/${organization.id}/courses/${course.id}/`,
|
|
122
118
|
course,
|
|
123
119
|
);
|
|
124
|
-
} else if (
|
|
120
|
+
} else if (offering) {
|
|
125
121
|
// fetching training
|
|
126
122
|
nbApiRequest += 1;
|
|
127
|
-
fetchMock.get(
|
|
128
|
-
`https://joanie.endpoint/api/v1.0/course-product-relations/${courseProductRelation.id}/`,
|
|
129
|
-
courseProductRelation,
|
|
130
|
-
);
|
|
123
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/`, offering);
|
|
131
124
|
nbApiRequest += 1;
|
|
132
125
|
fetchMock.get(
|
|
133
|
-
`https://joanie.endpoint/api/v1.0/organizations/?
|
|
126
|
+
`https://joanie.endpoint/api/v1.0/organizations/?offering_id=${offering.id}`,
|
|
134
127
|
[],
|
|
135
128
|
);
|
|
136
129
|
} else {
|
|
@@ -142,9 +135,9 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
142
135
|
let routePath = '/:courseId';
|
|
143
136
|
let initialEntry = `/${course.id}`;
|
|
144
137
|
|
|
145
|
-
if (
|
|
146
|
-
routePath += '/:
|
|
147
|
-
initialEntry += `/${
|
|
138
|
+
if (offering) {
|
|
139
|
+
routePath += '/:offeringId';
|
|
140
|
+
initialEntry += `/${offering.id}`;
|
|
148
141
|
}
|
|
149
142
|
if (organization) {
|
|
150
143
|
routePath = '/:organizationId' + routePath;
|
|
@@ -168,7 +161,7 @@ describe('<TeacherDashboardCourseSidebar/>', () => {
|
|
|
168
161
|
generatePath(expectedRoute, {
|
|
169
162
|
organizationId: organization ? organization.id : null,
|
|
170
163
|
courseId: course.id,
|
|
171
|
-
|
|
164
|
+
offeringId: offering ? offering.id : null,
|
|
172
165
|
}),
|
|
173
166
|
);
|
|
174
167
|
});
|
|
@@ -2,12 +2,12 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
|
2
2
|
import { generatePath, useParams } from 'react-router';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { capitalize } from 'lodash-es';
|
|
5
|
+
import { useOffering } from 'hooks/useOffering';
|
|
5
6
|
import { DashboardSidebar, MenuLink } from 'widgets/Dashboard/components/DashboardSidebar';
|
|
6
7
|
import { getDashboardRouteLabel } from 'widgets/Dashboard/utils/dashboardRoutes';
|
|
7
8
|
import { useCourse } from 'hooks/useCourses';
|
|
8
9
|
import { Spinner } from 'components/Spinner';
|
|
9
10
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
10
|
-
import { useCourseProductRelation } from 'hooks/useCourseProductRelation';
|
|
11
11
|
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherDashboardPaths';
|
|
12
12
|
import ContractNavLink from '../DashboardSidebar/components/ContractNavLink';
|
|
13
13
|
import { getMenuRoutes } from './utils';
|
|
@@ -41,11 +41,11 @@ export const TeacherDashboardCourseSidebar = () => {
|
|
|
41
41
|
const {
|
|
42
42
|
organizationId: routeOrganizationId,
|
|
43
43
|
courseId: routeCourseId,
|
|
44
|
-
|
|
44
|
+
offeringId: routeOfferingId = '',
|
|
45
45
|
} = useParams<{
|
|
46
46
|
organizationId?: string;
|
|
47
47
|
courseId: string;
|
|
48
|
-
|
|
48
|
+
offeringId: string;
|
|
49
49
|
}>();
|
|
50
50
|
|
|
51
51
|
const {
|
|
@@ -54,38 +54,35 @@ export const TeacherDashboardCourseSidebar = () => {
|
|
|
54
54
|
} = useCourse(
|
|
55
55
|
routeCourseId,
|
|
56
56
|
{ organization_id: routeOrganizationId },
|
|
57
|
-
{ enabled: !
|
|
57
|
+
{ enabled: !routeOfferingId },
|
|
58
58
|
);
|
|
59
59
|
|
|
60
60
|
const {
|
|
61
|
-
item:
|
|
62
|
-
states: { fetching:
|
|
63
|
-
} =
|
|
64
|
-
|
|
61
|
+
item: offering,
|
|
62
|
+
states: { fetching: offeringFetching },
|
|
63
|
+
} = useOffering(
|
|
64
|
+
routeOfferingId,
|
|
65
65
|
{
|
|
66
66
|
organization_id: routeOrganizationId,
|
|
67
67
|
},
|
|
68
|
-
{ enabled: !!
|
|
68
|
+
{ enabled: !!routeOfferingId },
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
const fetching = useMemo(
|
|
72
|
-
() => courseFetching ||
|
|
73
|
-
[courseFetching,
|
|
74
|
-
);
|
|
75
|
-
const product = useMemo(
|
|
76
|
-
() => (courseProductRelation ? courseProductRelation.product : undefined),
|
|
77
|
-
[courseProductRelation],
|
|
72
|
+
() => courseFetching || offeringFetching,
|
|
73
|
+
[courseFetching, offeringFetching],
|
|
78
74
|
);
|
|
75
|
+
const product = useMemo(() => (offering ? offering.product : undefined), [offering]);
|
|
79
76
|
const course = useMemo(
|
|
80
|
-
() => (
|
|
81
|
-
[
|
|
77
|
+
() => (offering ? offering.course : singleCourse),
|
|
78
|
+
[offering, singleCourse],
|
|
82
79
|
);
|
|
83
80
|
|
|
84
81
|
const getMenuLinkFromPath = (basePath: TeacherDashboardPaths) => {
|
|
85
82
|
const path = generatePath(basePath, {
|
|
86
83
|
organizationId: routeOrganizationId ?? '',
|
|
87
84
|
courseId: routeCourseId ?? '',
|
|
88
|
-
|
|
85
|
+
offeringId: routeOfferingId ?? '',
|
|
89
86
|
});
|
|
90
87
|
const menuLink: MenuLink = {
|
|
91
88
|
to: path,
|
|
@@ -102,7 +99,7 @@ export const TeacherDashboardCourseSidebar = () => {
|
|
|
102
99
|
<ContractNavLink
|
|
103
100
|
link={menuLink}
|
|
104
101
|
organizationId={routeOrganizationId}
|
|
105
|
-
|
|
102
|
+
offeringId={routeOfferingId}
|
|
106
103
|
/>
|
|
107
104
|
);
|
|
108
105
|
}
|
|
@@ -112,10 +109,10 @@ export const TeacherDashboardCourseSidebar = () => {
|
|
|
112
109
|
const menuLinkList = useMemo(
|
|
113
110
|
() =>
|
|
114
111
|
getMenuRoutes({
|
|
115
|
-
|
|
112
|
+
offeringId: routeOfferingId,
|
|
116
113
|
organizationId: routeOrganizationId,
|
|
117
114
|
}).map(getMenuLinkFromPath),
|
|
118
|
-
[routeOrganizationId,
|
|
115
|
+
[routeOrganizationId, routeOfferingId],
|
|
119
116
|
);
|
|
120
117
|
|
|
121
118
|
return (
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherDashboardPaths';
|
|
2
2
|
|
|
3
3
|
interface GetMenuRoutesArgs {
|
|
4
|
-
|
|
4
|
+
offeringId?: string;
|
|
5
5
|
organizationId?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export const getMenuRoutes = ({
|
|
8
|
+
export const getMenuRoutes = ({ offeringId, organizationId }: GetMenuRoutesArgs) => {
|
|
9
9
|
if (organizationId) {
|
|
10
|
-
if (
|
|
10
|
+
if (offeringId) {
|
|
11
11
|
return [
|
|
12
12
|
TeacherDashboardPaths.ORGANIZATION_PRODUCT,
|
|
13
13
|
TeacherDashboardPaths.ORGANIZATION_PRODUCT_CONTRACTS,
|
|
@@ -17,7 +17,7 @@ export const getMenuRoutes = ({ courseProductRelationId, organizationId }: GetMe
|
|
|
17
17
|
return [TeacherDashboardPaths.ORGANIZATION_COURSE_GENERAL_INFORMATION];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
if (
|
|
20
|
+
if (offeringId) {
|
|
21
21
|
return [
|
|
22
22
|
TeacherDashboardPaths.COURSE_PRODUCT,
|
|
23
23
|
TeacherDashboardPaths.COURSE_PRODUCT_CONTRACTS,
|
|
@@ -24,9 +24,9 @@ const messages = defineMessages({
|
|
|
24
24
|
export const TeacherDashboardOrganizationSidebar = () => {
|
|
25
25
|
const intl = useIntl();
|
|
26
26
|
const getRouteLabel = getDashboardRouteLabel(intl);
|
|
27
|
-
const { organizationId,
|
|
27
|
+
const { organizationId, offeringId } = useParams<{
|
|
28
28
|
organizationId: string;
|
|
29
|
-
|
|
29
|
+
offeringId?: string;
|
|
30
30
|
}>();
|
|
31
31
|
const {
|
|
32
32
|
item: organization,
|
|
@@ -43,11 +43,7 @@ export const TeacherDashboardOrganizationSidebar = () => {
|
|
|
43
43
|
|
|
44
44
|
if (basePath === TeacherDashboardPaths.ORGANIZATION_CONTRACTS) {
|
|
45
45
|
menuLink.component = (
|
|
46
|
-
<ContractNavLink
|
|
47
|
-
link={menuLink}
|
|
48
|
-
organizationId={organizationId}
|
|
49
|
-
courseProductRelationId={courseProductRelationId}
|
|
50
|
-
/>
|
|
46
|
+
<ContractNavLink link={menuLink} organizationId={organizationId} offeringId={offeringId} />
|
|
51
47
|
);
|
|
52
48
|
}
|
|
53
49
|
|
|
@@ -9,14 +9,14 @@ export enum TeacherDashboardPaths {
|
|
|
9
9
|
ORGANIZATION = `${ROOT}/organizations/:organizationId`,
|
|
10
10
|
ORGANIZATION_CONTRACTS = `${ORGANIZATION}/contracts`,
|
|
11
11
|
ORGANIZATION_COURSES = `${ORGANIZATION}/courses`,
|
|
12
|
-
ORGANIZATION_PRODUCT = `${ORGANIZATION_COURSES}/:courseId/products/:
|
|
12
|
+
ORGANIZATION_PRODUCT = `${ORGANIZATION_COURSES}/:courseId/products/:offeringId`,
|
|
13
13
|
ORGANIZATION_COURSE_CONTRACTS = `${ORGANIZATION_COURSES}/:courseId/contracts`,
|
|
14
|
-
ORGANIZATION_PRODUCT_CONTRACTS = `${ORGANIZATION_COURSES}/:courseId/products/:
|
|
15
|
-
ORGANIZATION_COURSE_PRODUCT_LEARNER_LIST = `${ORGANIZATION_COURSES}/:courseId/products/:
|
|
14
|
+
ORGANIZATION_PRODUCT_CONTRACTS = `${ORGANIZATION_COURSES}/:courseId/products/:offeringId/contracts`,
|
|
15
|
+
ORGANIZATION_COURSE_PRODUCT_LEARNER_LIST = `${ORGANIZATION_COURSES}/:courseId/products/:offeringId/learners`,
|
|
16
16
|
ORGANIZATION_COURSE_GENERAL_INFORMATION = `${ORGANIZATION_COURSES}/:courseId/information`,
|
|
17
17
|
COURSE = `${TEACHER_COURSES}/:courseId`,
|
|
18
18
|
COURSE_GENERAL_INFORMATION = `${COURSE}/information`,
|
|
19
|
-
COURSE_PRODUCT = `${COURSE}/products/:
|
|
19
|
+
COURSE_PRODUCT = `${COURSE}/products/:offeringId`,
|
|
20
20
|
COURSE_PRODUCT_LEARNER_LIST = `${COURSE_PRODUCT}/learners`,
|
|
21
21
|
COURSE_PRODUCT_CONTRACTS = `${COURSE_PRODUCT}/contracts`,
|
|
22
22
|
}
|
|
@@ -20,6 +20,8 @@ type SlidePanelProps = {
|
|
|
20
20
|
activeSlideIndex: number;
|
|
21
21
|
isTransitioning: boolean;
|
|
22
22
|
onBulletClick: (index: number) => void;
|
|
23
|
+
toggleAutoplay: () => void;
|
|
24
|
+
isAutoplaying: boolean;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -31,6 +33,8 @@ const SlidePanel = ({
|
|
|
31
33
|
activeSlideIndex,
|
|
32
34
|
onBulletClick,
|
|
33
35
|
isTransitioning,
|
|
36
|
+
toggleAutoplay,
|
|
37
|
+
isAutoplaying,
|
|
34
38
|
}: SlidePanelProps) => {
|
|
35
39
|
const intl = useIntl();
|
|
36
40
|
const hasSlideContent = slides.some((slide) => slide.content);
|
|
@@ -75,6 +79,11 @@ const SlidePanel = ({
|
|
|
75
79
|
</span>
|
|
76
80
|
</button>
|
|
77
81
|
))}
|
|
82
|
+
<div className="slider__autoplay">
|
|
83
|
+
<button type="button" onClick={toggleAutoplay}>
|
|
84
|
+
{isAutoplaying ? '⏸' : '⏵'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
78
87
|
</div>
|
|
79
88
|
</section>
|
|
80
89
|
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { IntlProvider } from 'react-intl';
|
|
3
|
+
import { Slide } from './types';
|
|
4
|
+
import Slider from '.';
|
|
5
|
+
|
|
6
|
+
const slides: Slide[] = [
|
|
7
|
+
{
|
|
8
|
+
pk: '1',
|
|
9
|
+
title: 'Slide 1',
|
|
10
|
+
content: 'Content for slide 1',
|
|
11
|
+
image: '/static/course_cover_image.jpg',
|
|
12
|
+
link_url: 'https://example.com/1',
|
|
13
|
+
link_open_blank: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pk: '2',
|
|
17
|
+
title: 'Slide 2',
|
|
18
|
+
content: 'Content for slide 2',
|
|
19
|
+
image: '/static/course_cover_image.jpg',
|
|
20
|
+
link_url: 'https://example.com/2',
|
|
21
|
+
link_open_blank: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pk: '3',
|
|
25
|
+
title: 'Slide 3',
|
|
26
|
+
content: 'Content for slide 3',
|
|
27
|
+
image: '/static/course_cover_image.jpg',
|
|
28
|
+
link_url: 'https://example.com/3',
|
|
29
|
+
link_open_blank: true,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
component: Slider,
|
|
35
|
+
title: 'Widgets/Slider',
|
|
36
|
+
decorators: [
|
|
37
|
+
(Story) => (
|
|
38
|
+
<IntlProvider locale="en">
|
|
39
|
+
<Story />
|
|
40
|
+
</IntlProvider>
|
|
41
|
+
),
|
|
42
|
+
],
|
|
43
|
+
} as Meta<typeof Slider>;
|
|
44
|
+
|
|
45
|
+
type Story = StoryObj<typeof Slider>;
|
|
46
|
+
|
|
47
|
+
export const Default: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
pk: 'slider-1',
|
|
50
|
+
title: 'Example Slider',
|
|
51
|
+
slides,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import useEmblaCarousel from 'embla-carousel-react';
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
|
|
4
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
4
5
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
6
|
+
import { SLIDER_SETTINGS } from 'settings';
|
|
5
7
|
import { Slide as SlideType } from './types';
|
|
6
8
|
import SlidePanel from './components/SlidePanel';
|
|
7
9
|
import Slideshow from './components/Slideshow';
|
|
@@ -22,9 +24,24 @@ type SliderProps = {
|
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const Slider = ({ slides, title }: SliderProps) => {
|
|
25
|
-
const
|
|
27
|
+
const autoplay = useRef(Autoplay({ delay: SLIDER_SETTINGS.autoplayDelay }));
|
|
28
|
+
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [
|
|
29
|
+
WheelGesturesPlugin(),
|
|
30
|
+
autoplay.current,
|
|
31
|
+
]);
|
|
26
32
|
const [activeSlideIndex, setActiveSlideIndex] = useState(0);
|
|
27
33
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
34
|
+
const [isAutoplaying, setIsAutoplaying] = useState(true);
|
|
35
|
+
|
|
36
|
+
const toggleAutoplay = () => {
|
|
37
|
+
if (!autoplay.current) return;
|
|
38
|
+
if (isAutoplaying) {
|
|
39
|
+
autoplay.current.stop();
|
|
40
|
+
} else {
|
|
41
|
+
autoplay.current.play();
|
|
42
|
+
}
|
|
43
|
+
setIsAutoplaying(!isAutoplaying);
|
|
44
|
+
};
|
|
28
45
|
|
|
29
46
|
const handleBulletClick = useCallback(
|
|
30
47
|
(index: number) => {
|
|
@@ -101,6 +118,8 @@ const Slider = ({ slides, title }: SliderProps) => {
|
|
|
101
118
|
activeSlideIndex={activeSlideIndex}
|
|
102
119
|
onBulletClick={handleBulletClick}
|
|
103
120
|
isTransitioning={isTransitioning}
|
|
121
|
+
toggleAutoplay={toggleAutoplay}
|
|
122
|
+
isAutoplaying={isAutoplaying}
|
|
104
123
|
/>
|
|
105
124
|
<span className="offscreen" role="presentation" aria-live="polite" aria-atomic="true">
|
|
106
125
|
<FormattedMessage
|