richie-education 2.28.1 → 2.28.2-dev23
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/js/components/AddressesManagement/AddressForm/validationSchema.spec.ts +2 -2
- package/js/components/PaymentInterfaces/LyraPopIn.tsx +10 -3
- package/js/components/PaymentInterfaces/types.ts +1 -1
- package/js/components/PurchaseButton/index.spec.tsx +9 -9
- package/js/components/PurchaseButton/index.tsx +2 -1
- package/js/components/SaleTunnel/GenericPaymentButton/index.tsx +13 -3
- package/js/components/SaleTunnel/hooks/useTerms.tsx +2 -2
- package/js/components/SaleTunnel/index.credential.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process.spec.tsx +11 -4
- package/js/components/SaleTunnel/index.spec.tsx +2 -2
- package/js/components/SaleTunnel/index.stories.tsx +3 -2
- package/js/components/SaleTunnel/index.tsx +2 -2
- package/js/types/commonDataProps.ts +0 -1
- package/js/types/index.ts +6 -0
- package/js/utils/CourseRunHelper/index.spec.ts +35 -0
- package/js/utils/CourseRunHelper/index.ts +13 -0
- package/js/utils/react-query/useSessionQuery/index.ts +1 -1
- package/js/utils/test/factories/richie.ts +16 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +3 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +32 -30
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +5 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +3 -2
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.login.spec.tsx +5 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.logout.spec.tsx +5 -3
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.tsx +2 -2
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusAsideList/index.tsx +11 -4
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +20 -9
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +111 -0
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +311 -15
- package/js/widgets/SyllabusCourseRunsList/index.tsx +24 -8
- package/package.json +44 -44
- package/tsconfig.json +1 -1
|
@@ -15,7 +15,7 @@ describe('validationSchema', () => {
|
|
|
15
15
|
first_name: faker.person.firstName(),
|
|
16
16
|
last_name: faker.person.lastName(),
|
|
17
17
|
postcode: faker.location.zipCode(),
|
|
18
|
-
title: faker.lorem.word(),
|
|
18
|
+
title: faker.lorem.word({ length: { min: 2, max: 15 } }),
|
|
19
19
|
save: false,
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -130,7 +130,7 @@ describe('validationSchema', () => {
|
|
|
130
130
|
result.current.setValue('first_name', faker.person.firstName());
|
|
131
131
|
result.current.setValue('last_name', faker.person.lastName());
|
|
132
132
|
result.current.setValue('postcode', faker.location.zipCode());
|
|
133
|
-
result.current.setValue('title', faker.lorem.word());
|
|
133
|
+
result.current.setValue('title', faker.lorem.word({ length: { min: 2, max: 15 } }));
|
|
134
134
|
result.current.trigger();
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -25,11 +25,13 @@ const LyraPopIn = ({
|
|
|
25
25
|
const intl = useIntl();
|
|
26
26
|
const shouldAbort = useRef<Boolean>(true);
|
|
27
27
|
|
|
28
|
-
const handleError = (error?: Error) => {
|
|
29
|
-
if (error) handle(`[LyraPopIn] - ${error}`);
|
|
28
|
+
const handleError = (error?: Error | string) => {
|
|
29
|
+
if (error && typeof error === 'string') handle(`[LyraPopIn] - ${error}`);
|
|
30
30
|
|
|
31
31
|
if (shouldAbort.current) {
|
|
32
32
|
onError(PaymentErrorMessageId.ERROR_ABORTING);
|
|
33
|
+
} else if (typeof error === 'string') {
|
|
34
|
+
onError(error);
|
|
33
35
|
} else {
|
|
34
36
|
onError(PaymentErrorMessageId.ERROR_DEFAULT);
|
|
35
37
|
}
|
|
@@ -95,8 +97,13 @@ const LyraPopIn = ({
|
|
|
95
97
|
// Do not close the pop-in if the error is a invalid data error (CLIENT_3XX).
|
|
96
98
|
// https://docs.lyra.com/fr/rest/V4.0/javascript/features/js_error_management.html#client004
|
|
97
99
|
if (!error.errorCode.startsWith('CLIENT_3')) {
|
|
100
|
+
shouldAbort.current = false;
|
|
98
101
|
await KR.closePopin(formId);
|
|
99
|
-
|
|
102
|
+
let errorMessages = error.errorMessage;
|
|
103
|
+
if (error.detailedErrorMessage) {
|
|
104
|
+
errorMessages += `: ${error.detailedErrorMessage}`;
|
|
105
|
+
}
|
|
106
|
+
handleError(errorMessages);
|
|
100
107
|
}
|
|
101
108
|
};
|
|
102
109
|
|
|
@@ -42,5 +42,5 @@ export type Payment = DummyPayment | PayplugPayment | LyraPayment;
|
|
|
42
42
|
|
|
43
43
|
export type PaymentInterfaceProps<P extends Payment = Payment> = P & {
|
|
44
44
|
onSuccess: () => void;
|
|
45
|
-
onError: (messageId: PaymentErrorMessageId) => void;
|
|
45
|
+
onError: (messageId: string | PaymentErrorMessageId) => void;
|
|
46
46
|
};
|
|
@@ -6,12 +6,12 @@ import userEvent from '@testing-library/user-event';
|
|
|
6
6
|
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
7
7
|
import {
|
|
8
8
|
CourseStateFactory,
|
|
9
|
+
PacedCourseFactory,
|
|
9
10
|
UserFactory,
|
|
10
11
|
RichieContextFactory as mockRichieContextFactory,
|
|
11
12
|
} from 'utils/test/factories/richie';
|
|
12
13
|
import {
|
|
13
14
|
CertificateProductFactory,
|
|
14
|
-
CourseLightFactory,
|
|
15
15
|
EnrollmentFactory,
|
|
16
16
|
ProductFactory,
|
|
17
17
|
} from 'utils/test/factories/joanie';
|
|
@@ -89,7 +89,7 @@ describe('PurchaseButton', () => {
|
|
|
89
89
|
<PurchaseButton
|
|
90
90
|
product={product}
|
|
91
91
|
disabled={false}
|
|
92
|
-
course={
|
|
92
|
+
course={PacedCourseFactory({ code: '00000' }).one()}
|
|
93
93
|
/>
|
|
94
94
|
</Wrapper>,
|
|
95
95
|
);
|
|
@@ -112,7 +112,7 @@ describe('PurchaseButton', () => {
|
|
|
112
112
|
<PurchaseButton
|
|
113
113
|
product={product}
|
|
114
114
|
disabled={false}
|
|
115
|
-
course={
|
|
115
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
116
116
|
/>
|
|
117
117
|
</Wrapper>,
|
|
118
118
|
);
|
|
@@ -146,7 +146,7 @@ describe('PurchaseButton', () => {
|
|
|
146
146
|
<PurchaseButton
|
|
147
147
|
product={product}
|
|
148
148
|
disabled={false}
|
|
149
|
-
course={
|
|
149
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
150
150
|
/>
|
|
151
151
|
</Wrapper>,
|
|
152
152
|
);
|
|
@@ -181,7 +181,7 @@ describe('PurchaseButton', () => {
|
|
|
181
181
|
<PurchaseButton
|
|
182
182
|
product={product}
|
|
183
183
|
disabled={false}
|
|
184
|
-
course={
|
|
184
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
185
185
|
/>
|
|
186
186
|
</Wrapper>,
|
|
187
187
|
);
|
|
@@ -214,7 +214,7 @@ describe('PurchaseButton', () => {
|
|
|
214
214
|
<PurchaseButton
|
|
215
215
|
product={product}
|
|
216
216
|
disabled={false}
|
|
217
|
-
course={
|
|
217
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
218
218
|
/>
|
|
219
219
|
</Wrapper>,
|
|
220
220
|
);
|
|
@@ -251,7 +251,7 @@ describe('PurchaseButton', () => {
|
|
|
251
251
|
<PurchaseButton
|
|
252
252
|
product={product}
|
|
253
253
|
disabled={false}
|
|
254
|
-
course={
|
|
254
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
255
255
|
/>
|
|
256
256
|
</Wrapper>,
|
|
257
257
|
);
|
|
@@ -384,7 +384,7 @@ describe('PurchaseButton', () => {
|
|
|
384
384
|
<PurchaseButton
|
|
385
385
|
product={product}
|
|
386
386
|
disabled={false}
|
|
387
|
-
course={
|
|
387
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
388
388
|
/>
|
|
389
389
|
</Wrapper>,
|
|
390
390
|
);
|
|
@@ -414,7 +414,7 @@ describe('PurchaseButton', () => {
|
|
|
414
414
|
<PurchaseButton
|
|
415
415
|
product={product}
|
|
416
416
|
disabled={true}
|
|
417
|
-
course={
|
|
417
|
+
course={PacedCourseFactory({ code: courseCode }).one()}
|
|
418
418
|
/>
|
|
419
419
|
</Wrapper>,
|
|
420
420
|
);
|
|
@@ -7,6 +7,7 @@ import * as Joanie from 'types/Joanie';
|
|
|
7
7
|
import { isOpenedCourseRunCertificate, isOpenedCourseRunCredential } from 'utils/CourseRuns';
|
|
8
8
|
import { SaleTunnel, SaleTunnelProps } from 'components/SaleTunnel';
|
|
9
9
|
import { Organization } from 'types/Joanie';
|
|
10
|
+
import { PacedCourse } from 'types';
|
|
10
11
|
|
|
11
12
|
const messages = defineMessages({
|
|
12
13
|
loginToPurchase: {
|
|
@@ -52,7 +53,7 @@ interface PurchaseButtonPropsBase {
|
|
|
52
53
|
|
|
53
54
|
interface CredentialPurchaseButtonProps extends PurchaseButtonPropsBase {
|
|
54
55
|
product: Joanie.CredentialProduct;
|
|
55
|
-
course:
|
|
56
|
+
course: PacedCourse;
|
|
56
57
|
enrollment?: undefined;
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -97,7 +97,7 @@ export const GenericPaymentButton = ({ buildOrderPayload }: Props) => {
|
|
|
97
97
|
const { methods: orderMethods } = useOrders(undefined, { enabled: false });
|
|
98
98
|
const [payment, setPayment] = useState<PaymentInfo>();
|
|
99
99
|
const [state, setState] = useState<ComponentStates>(ComponentStates.IDLE);
|
|
100
|
-
const [error, setError] = useState<PaymentErrorMessageId>();
|
|
100
|
+
const [error, setError] = useState<PaymentErrorMessageId | string>();
|
|
101
101
|
const hasPaymentId = (p: Maybe<Payment>): p is Extract<Payment, PaymentWithId> => {
|
|
102
102
|
return Boolean(p?.hasOwnProperty('payment_id'));
|
|
103
103
|
};
|
|
@@ -256,7 +256,9 @@ export const GenericPaymentButton = ({ buildOrderPayload }: Props) => {
|
|
|
256
256
|
checkOrderValidity();
|
|
257
257
|
};
|
|
258
258
|
|
|
259
|
-
const handleError = (
|
|
259
|
+
const handleError = (
|
|
260
|
+
messageId: PaymentErrorMessageId | string = PaymentErrorMessageId.ERROR_DEFAULT,
|
|
261
|
+
) => {
|
|
260
262
|
setState(ComponentStates.ERROR);
|
|
261
263
|
setError(messageId);
|
|
262
264
|
};
|
|
@@ -274,6 +276,8 @@ export const GenericPaymentButton = ({ buildOrderPayload }: Props) => {
|
|
|
274
276
|
.catch(() => {
|
|
275
277
|
handleError();
|
|
276
278
|
});
|
|
279
|
+
} else if (error && !messages.hasOwnProperty(error)) {
|
|
280
|
+
orderMethods.invalidate();
|
|
277
281
|
} else if (state === ComponentStates.ERROR) {
|
|
278
282
|
setPayment(undefined);
|
|
279
283
|
}
|
|
@@ -320,7 +324,13 @@ export const GenericPaymentButton = ({ buildOrderPayload }: Props) => {
|
|
|
320
324
|
)}
|
|
321
325
|
{state === ComponentStates.ERROR && (
|
|
322
326
|
<p className="payment-button__error" id="sale-tunnel-payment-error" tabIndex={-1}>
|
|
323
|
-
|
|
327
|
+
{!error || messages.hasOwnProperty(error) ? (
|
|
328
|
+
<FormattedMessage
|
|
329
|
+
{...messages[(error as PaymentErrorMessageId) || PaymentErrorMessageId.ERROR_DEFAULT]}
|
|
330
|
+
/>
|
|
331
|
+
) : (
|
|
332
|
+
error
|
|
333
|
+
)}
|
|
324
334
|
</p>
|
|
325
335
|
)}
|
|
326
336
|
</>
|
|
@@ -29,8 +29,8 @@ export const useTerms = ({
|
|
|
29
29
|
error,
|
|
30
30
|
}: {
|
|
31
31
|
product: Product;
|
|
32
|
-
onError: (error: PaymentErrorMessageId) => void;
|
|
33
|
-
error?: PaymentErrorMessageId;
|
|
32
|
+
onError: (error: PaymentErrorMessageId | string) => void;
|
|
33
|
+
error?: PaymentErrorMessageId | string;
|
|
34
34
|
}) => {
|
|
35
35
|
const intl = useIntl();
|
|
36
36
|
const [termsAccepted, setTermsAccepted] = useState(false);
|
|
@@ -2,12 +2,12 @@ import fetchMock from 'fetch-mock';
|
|
|
2
2
|
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import {
|
|
4
4
|
RichieContextFactory as mockRichieContextFactory,
|
|
5
|
+
PacedCourseFactory,
|
|
5
6
|
UserFactory,
|
|
6
7
|
} from 'utils/test/factories/richie';
|
|
7
8
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
8
9
|
import {
|
|
9
10
|
AddressFactory,
|
|
10
|
-
CourseFactory,
|
|
11
11
|
CredentialOrderWithPaymentFactory,
|
|
12
12
|
CredentialProductFactory,
|
|
13
13
|
OrderGroupFactory,
|
|
@@ -86,7 +86,7 @@ describe('SaleTunnel / Credential', () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('should create an order with an order group', async () => {
|
|
89
|
-
const course =
|
|
89
|
+
const course = PacedCourseFactory().one();
|
|
90
90
|
const product = CredentialProductFactory().one();
|
|
91
91
|
const orderGroup = OrderGroupFactory().one();
|
|
92
92
|
const billingAddress: Joanie.Address = AddressFactory({ is_main: true }).one();
|
|
@@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
5
5
|
import countries from 'i18n-iso-countries';
|
|
6
6
|
import {
|
|
7
7
|
RichieContextFactory as mockRichieContextFactory,
|
|
8
|
+
PacedCourseFactory,
|
|
8
9
|
UserFactory,
|
|
9
10
|
} from 'utils/test/factories/richie';
|
|
10
11
|
import { render } from 'utils/test/render';
|
|
@@ -12,8 +13,8 @@ import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
|
12
13
|
import CourseProductItem from 'widgets/SyllabusCourseRunsList/components/CourseProductItem';
|
|
13
14
|
import {
|
|
14
15
|
AddressFactory,
|
|
15
|
-
CourseProductRelationFactory,
|
|
16
16
|
CredentialOrderWithPaymentFactory,
|
|
17
|
+
CourseProductRelationFactory,
|
|
17
18
|
} from 'utils/test/factories/joanie';
|
|
18
19
|
import { ACTIVE_ORDER_STATES, CourseRun } from 'types/Joanie';
|
|
19
20
|
import { Priority } from 'types';
|
|
@@ -101,9 +102,15 @@ describe('SaleTunnel', () => {
|
|
|
101
102
|
[],
|
|
102
103
|
);
|
|
103
104
|
|
|
104
|
-
render(
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
render(
|
|
106
|
+
<CourseProductItem
|
|
107
|
+
productId={product.id}
|
|
108
|
+
course={PacedCourseFactory({ id: course.id, code: course.code }).one()}
|
|
109
|
+
/>,
|
|
110
|
+
{
|
|
111
|
+
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
112
|
+
},
|
|
113
|
+
);
|
|
107
114
|
|
|
108
115
|
// Wait for product information to be fetched
|
|
109
116
|
await screen.findByRole('heading', { level: 3, name: product.title });
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
CertificateOrderWithOneClickPaymentFactory,
|
|
9
9
|
CertificateOrderWithPaymentFactory,
|
|
10
10
|
CertificateProductFactory,
|
|
11
|
-
CourseFactory,
|
|
12
11
|
CredentialOrderWithOneClickPaymentFactory,
|
|
13
12
|
CredentialOrderWithPaymentFactory,
|
|
14
13
|
CredentialProductFactory,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
} from 'utils/test/factories/joanie';
|
|
18
17
|
import {
|
|
19
18
|
RichieContextFactory as mockRichieContextFactory,
|
|
19
|
+
PacedCourseFactory,
|
|
20
20
|
UserFactory,
|
|
21
21
|
} from 'utils/test/factories/richie';
|
|
22
22
|
import { render } from 'utils/test/render';
|
|
@@ -70,7 +70,7 @@ describe.each([
|
|
|
70
70
|
({ productType, ProductFactory, OrderWithOneClickPaymentFactory, OrderWithPaymentFactory }) => {
|
|
71
71
|
let nbApiCalls: number;
|
|
72
72
|
|
|
73
|
-
const course =
|
|
73
|
+
const course = PacedCourseFactory().one();
|
|
74
74
|
const enrollment =
|
|
75
75
|
productType === ProductType.CERTIFICATE ? EnrollmentFactory().one() : undefined;
|
|
76
76
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StoryObj, Meta } from '@storybook/react';
|
|
2
2
|
import { BaseJoanieAppWrapper } from 'utils/test/wrappers/BaseJoanieAppWrapper';
|
|
3
|
-
import {
|
|
3
|
+
import { ProductFactory } from 'utils/test/factories/joanie';
|
|
4
|
+
import { PacedCourseFactory } from 'utils/test/factories/richie';
|
|
4
5
|
import { SaleTunnel, SaleTunnelProps } from './index';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
@@ -10,7 +11,7 @@ export default {
|
|
|
10
11
|
isOpen: true,
|
|
11
12
|
product: ProductFactory().one(),
|
|
12
13
|
onClose: () => {},
|
|
13
|
-
course:
|
|
14
|
+
course: PacedCourseFactory().one(),
|
|
14
15
|
// enrollment?: Enrollment;
|
|
15
16
|
// product: CredentialProduct | CertificateProduct;
|
|
16
17
|
// orderGroup?: OrderGroup;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ModalProps } from '@openfun/cunningham-react';
|
|
2
2
|
import {
|
|
3
3
|
CertificateProduct,
|
|
4
|
-
CourseLight,
|
|
5
4
|
CredentialProduct,
|
|
6
5
|
Enrollment,
|
|
7
6
|
Order,
|
|
@@ -12,12 +11,13 @@ import {
|
|
|
12
11
|
} from 'types/Joanie';
|
|
13
12
|
import { CredentialSaleTunnel } from 'components/SaleTunnel/CredentialSaleTunnel';
|
|
14
13
|
import { CertificateSaleTunnel } from 'components/SaleTunnel/CertificateSaleTunnel';
|
|
14
|
+
import { PacedCourse } from 'types';
|
|
15
15
|
|
|
16
16
|
export interface SaleTunnelProps extends Pick<ModalProps, 'isOpen' | 'onClose'> {
|
|
17
17
|
product: Product;
|
|
18
18
|
organizations?: Organization[];
|
|
19
19
|
|
|
20
|
-
course?:
|
|
20
|
+
course?: PacedCourse;
|
|
21
21
|
enrollment?: Enrollment;
|
|
22
22
|
orderGroup?: OrderGroup;
|
|
23
23
|
onFinish?: (order: Order) => void;
|
package/js/types/index.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CourseRun, Priority } from 'types';
|
|
2
|
+
import { CourseRunHelper } from 'utils/CourseRunHelper/index';
|
|
3
|
+
import { CourseRunFactoryFromPriority } from 'utils/test/factories/richie';
|
|
4
|
+
|
|
5
|
+
describe('CourseRunHelper', () => {
|
|
6
|
+
it('should return true if all course runs have the same languages', () => {
|
|
7
|
+
const courseRuns: CourseRun[] = [
|
|
8
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
9
|
+
languages: ['en'],
|
|
10
|
+
}).one(),
|
|
11
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_OPEN)({
|
|
12
|
+
languages: ['en'],
|
|
13
|
+
}).one(),
|
|
14
|
+
CourseRunFactoryFromPriority(Priority.FUTURE_OPEN)({
|
|
15
|
+
languages: ['en'],
|
|
16
|
+
}).one(),
|
|
17
|
+
];
|
|
18
|
+
expect(CourseRunHelper.IsAllCourseRunsWithSameLanguages(courseRuns)).toEqual(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should return false if all course runs have different languages', () => {
|
|
22
|
+
const courseRuns: CourseRun[] = [
|
|
23
|
+
CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
24
|
+
languages: ['en'],
|
|
25
|
+
}).one(),
|
|
26
|
+
CourseRunFactoryFromPriority(Priority.ARCHIVED_OPEN)({
|
|
27
|
+
languages: ['fr'],
|
|
28
|
+
}).one(),
|
|
29
|
+
CourseRunFactoryFromPriority(Priority.FUTURE_OPEN)({
|
|
30
|
+
languages: ['it'],
|
|
31
|
+
}).one(),
|
|
32
|
+
];
|
|
33
|
+
expect(CourseRunHelper.IsAllCourseRunsWithSameLanguages(courseRuns)).toEqual(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CourseRun } from 'types';
|
|
2
|
+
|
|
3
|
+
export class CourseRunHelper {
|
|
4
|
+
/**
|
|
5
|
+
* Checks if all given runs of a course have the same languages.
|
|
6
|
+
*/
|
|
7
|
+
static IsAllCourseRunsWithSameLanguages = (courseRuns: CourseRun[]) => {
|
|
8
|
+
const languages = courseRuns[0].languages.sort().join();
|
|
9
|
+
return courseRuns
|
|
10
|
+
.map((courseRun) => courseRun.languages.sort().join())
|
|
11
|
+
.every((runLanguages) => runLanguages === languages);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -45,7 +45,7 @@ export function useSessionQuery<
|
|
|
45
45
|
}, [user, options?.enabled]);
|
|
46
46
|
|
|
47
47
|
const staleTime = useMemo(() => {
|
|
48
|
-
if (options?.staleTime
|
|
48
|
+
if (typeof options?.staleTime === 'number' && options.staleTime >= 0) {
|
|
49
49
|
return options.staleTime;
|
|
50
50
|
}
|
|
51
51
|
return REACT_QUERY_SETTINGS.staleTimes.sessionItems;
|
|
@@ -2,7 +2,14 @@ import { faker } from '@faker-js/faker';
|
|
|
2
2
|
import { User } from 'types/User';
|
|
3
3
|
import { APIBackend } from 'types/api';
|
|
4
4
|
import { CommonDataProps } from 'types/commonDataProps';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CourseRun,
|
|
7
|
+
CourseRunDisplayMode,
|
|
8
|
+
CourseState,
|
|
9
|
+
CourseStateTextEnum,
|
|
10
|
+
PacedCourse,
|
|
11
|
+
Priority,
|
|
12
|
+
} from 'types';
|
|
6
13
|
import { Course } from 'types/Course';
|
|
7
14
|
import { FactoryHelper } from 'utils/test/factories/helper';
|
|
8
15
|
import { factory } from './factories';
|
|
@@ -54,6 +61,14 @@ export const CourseRunFactory = factory<CourseRun>(() => {
|
|
|
54
61
|
};
|
|
55
62
|
});
|
|
56
63
|
|
|
64
|
+
export const PacedCourseFactory = factory((): PacedCourse => {
|
|
65
|
+
return {
|
|
66
|
+
id: faker.string.uuid(),
|
|
67
|
+
code: faker.string.alphanumeric(5),
|
|
68
|
+
is_self_paced: false,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
57
72
|
export const CourseRunFactoryFromPriority = (priority: Priority) => {
|
|
58
73
|
return factory<CourseRun>(() => {
|
|
59
74
|
let courseRun = CourseRunFactory().one();
|
|
@@ -152,7 +167,6 @@ export const RichieContextFactory = factory<CommonDataProps['context']>(() => ({
|
|
|
152
167
|
backend: APIBackend.OPENEDX_HAWTHORN,
|
|
153
168
|
endpoint: 'https://endpoint.test',
|
|
154
169
|
},
|
|
155
|
-
csrftoken: faker.string.alphanumeric(64),
|
|
156
170
|
environment: 'test',
|
|
157
171
|
features: {},
|
|
158
172
|
lms_backends: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FormattedMessage, defineMessages } from 'react-intl';
|
|
2
2
|
import PurchaseButton from 'components/PurchaseButton';
|
|
3
|
-
import {
|
|
3
|
+
import { CourseProductRelation, CredentialProduct, OrderGroup } from 'types/Joanie';
|
|
4
|
+
import { PacedCourse } from 'types';
|
|
4
5
|
|
|
5
6
|
const messages = defineMessages({
|
|
6
7
|
noSeatsAvailable: {
|
|
@@ -22,7 +23,7 @@ other {# remaining seats}
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
interface CourseProductItemFooterProps {
|
|
25
|
-
course:
|
|
26
|
+
course: PacedCourse;
|
|
26
27
|
courseProductRelation: CourseProductRelation;
|
|
27
28
|
canPurchase: boolean;
|
|
28
29
|
orderGroups: OrderGroup[];
|