richie-education 3.5.1-dev2 → 3.5.2-dev12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/i18n/locales/ar-SA.json +4 -0
- package/i18n/locales/es-ES.json +4 -0
- package/i18n/locales/fa-IR.json +4 -0
- package/i18n/locales/fr-CA.json +4 -0
- package/i18n/locales/fr-FR.json +5 -1
- package/i18n/locales/ko-KR.json +4 -0
- package/i18n/locales/pt-PT.json +4 -0
- package/i18n/locales/ru-RU.json +4 -0
- package/i18n/locales/vi-VN.json +4 -0
- package/js/api/joanie.ts +15 -0
- package/js/api/lms/dummy.ts +1 -0
- package/js/api/lms/openedx-fonzie-keycloak.spec.ts +7 -10
- package/js/api/lms/openedx-fonzie-keycloak.ts +24 -21
- package/js/components/SaleTunnel/index.spec.tsx +31 -1
- package/js/hooks/useLearnerCoursesSearch/index.tsx +17 -4
- package/js/hooks/useProductOrder/index.spec.tsx +1 -0
- package/js/pages/DashboardCourses/index.spec.tsx +40 -8
- 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/Joanie.ts +18 -1
- package/js/utils/OrderHelper/index.spec.ts +75 -0
- package/js/utils/OrderHelper/index.ts +14 -2
- package/js/utils/test/factories/joanie.ts +10 -0
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +213 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +133 -31
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +22 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +29 -13
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +0 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +12 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +11 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +5 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +12 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +11 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderWithdrawalModal/index.spec.tsx +167 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderWithdrawalModal/index.tsx +272 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.spec.tsx +102 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +84 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +5 -0
- package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +9 -1
- package/js/widgets/Dashboard/index.spec.tsx +0 -1
- package/package.json +1 -1
- package/scss/components/templates/courses/cms/_category_list.scss +5 -0
package/i18n/locales/ar-SA.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/es-ES.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/fa-IR.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/fr-CA.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/fr-FR.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"16uca+": {
|
|
3
3
|
"description": "",
|
|
4
|
-
"message": "
|
|
4
|
+
"message": "Sous \"{value}\""
|
|
5
5
|
},
|
|
6
6
|
"9vqPaF": {
|
|
7
7
|
"description": "",
|
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Sauvegarder"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {place} other {places}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Référence de commande : {id}"
|
package/i18n/locales/ko-KR.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/pt-PT.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submeter"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/ru-RU.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/i18n/locales/vi-VN.json
CHANGED
|
@@ -1587,6 +1587,10 @@
|
|
|
1587
1587
|
"description": "Label of the submit button on openEdx full name form",
|
|
1588
1588
|
"message": "Submit"
|
|
1589
1589
|
},
|
|
1590
|
+
"components.OrganizationQuotesTable.batchOrder.seatsCount": {
|
|
1591
|
+
"description": "Text displayed for seats count with pluralization in batch order",
|
|
1592
|
+
"message": "{seats} {seats, plural, one {seat} other {seats}}"
|
|
1593
|
+
},
|
|
1590
1594
|
"components.OrganizationQuotesTable.batchOrderId": {
|
|
1591
1595
|
"description": "Label for the batch order reference (id)",
|
|
1592
1596
|
"message": "Batch order id: {id}"
|
package/js/api/joanie.ts
CHANGED
|
@@ -101,6 +101,7 @@ export const getRoutes = () => {
|
|
|
101
101
|
submit_for_signature: `${baseUrl}/orders/:id/submit_for_signature/`,
|
|
102
102
|
submit_installment_payment: `${baseUrl}/orders/:id/submit-installment-payment/`,
|
|
103
103
|
set_payment_method: `${baseUrl}/orders/:id/payment-method/`,
|
|
104
|
+
withdraw: `${baseUrl}/orders/:id/withdraw/`,
|
|
104
105
|
},
|
|
105
106
|
batchOrders: {
|
|
106
107
|
get: `${baseUrl}/batch-orders/:id/`,
|
|
@@ -340,6 +341,20 @@ const API = (): Joanie.API => {
|
|
|
340
341
|
method: 'POST',
|
|
341
342
|
body: JSON.stringify(payload),
|
|
342
343
|
}).then(checkStatus),
|
|
344
|
+
withdraw: async (id) => {
|
|
345
|
+
const response = await fetchWithJWT(ROUTES.user.orders.withdraw.replace(':id', id), {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
});
|
|
348
|
+
if (response.status === HttpStatusCode.UNPROCESSABLE_ENTITY) {
|
|
349
|
+
throw new HttpError(
|
|
350
|
+
response.status,
|
|
351
|
+
response.statusText,
|
|
352
|
+
undefined,
|
|
353
|
+
response.json().catch(() => undefined),
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return checkStatus(response);
|
|
357
|
+
},
|
|
343
358
|
},
|
|
344
359
|
batchOrders: {
|
|
345
360
|
create: async (payload) =>
|
package/js/api/lms/dummy.ts
CHANGED
|
@@ -31,19 +31,16 @@ describe('Fonzie Keycloak API', () => {
|
|
|
31
31
|
jest.clearAllMocks();
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it('
|
|
34
|
+
it('reads the email and full name from the access token claims', async () => {
|
|
35
|
+
// JWT payload: { email: 'john.doe@example.com', full_name: 'John Doe' }
|
|
36
|
+
const accessToken =
|
|
37
|
+
'header.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiZnVsbF9uYW1lIjoiSm9obiBEb2UifQ.signature';
|
|
35
38
|
const user = {
|
|
36
39
|
username: 'test-richie-ncl',
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
const keycloakAccount = {
|
|
40
|
-
firstName: 'John',
|
|
41
|
-
lastName: 'Doe',
|
|
42
|
-
email: 'john.doe@example.com',
|
|
40
|
+
access_token: accessToken,
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
fetchMock.get('https://demo.endpoint.api/api/v1.0/user/me', user);
|
|
46
|
-
fetchMock.get('https://keycloak.test/auth/realms/richie-realm/account/', keycloakAccount);
|
|
47
44
|
|
|
48
45
|
const api = FonzieKeycloakAPIInterface(configuration);
|
|
49
46
|
await expect(api.user.me()).resolves.toEqual({
|
|
@@ -53,14 +50,14 @@ describe('Fonzie Keycloak API', () => {
|
|
|
53
50
|
});
|
|
54
51
|
});
|
|
55
52
|
|
|
56
|
-
it('
|
|
53
|
+
it('does not fail when the access token has no usable claims', async () => {
|
|
57
54
|
const user = {
|
|
58
55
|
username: 'test-richie-ncl',
|
|
59
56
|
full_name: 'n c',
|
|
57
|
+
access_token: 'not-a-jwt',
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
fetchMock.get('https://demo.endpoint.api/api/v1.0/user/me', user);
|
|
63
|
-
fetchMock.get('https://keycloak.test/auth/realms/richie-realm/account/', 500);
|
|
64
61
|
|
|
65
62
|
const api = FonzieKeycloakAPIInterface(configuration);
|
|
66
63
|
await expect(api.user.me()).resolves.toEqual(user);
|
|
@@ -8,8 +8,25 @@ import { OpenEdxApiProfile } from 'types/openEdx';
|
|
|
8
8
|
import { checkStatus } from 'api/utils';
|
|
9
9
|
import { OpenEdxFullNameFormValues } from 'components/OpenEdxFullNameForm';
|
|
10
10
|
import { location } from 'utils/indirection/window';
|
|
11
|
+
import { base64Decode } from 'utils/base64Parser';
|
|
12
|
+
import { Maybe } from 'types/utils';
|
|
11
13
|
import OpenEdxHawthornApiInterface from './openedx-hawthorn';
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Extract claims from the JWT issued by Fonzie. The `user/me` route does not expose
|
|
17
|
+
* the email nor an up to date full name, but the token it returns always carries them.
|
|
18
|
+
*/
|
|
19
|
+
const getTokenClaims = (token: Maybe<string>): Maybe<{ email?: string; full_name?: string }> => {
|
|
20
|
+
if (!token) return undefined;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
24
|
+
return JSON.parse(base64Decode(payload));
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
13
30
|
/**
|
|
14
31
|
*
|
|
15
32
|
* OpenEdX completed by Fonzie API Implementation
|
|
@@ -46,27 +63,13 @@ const API = (APIConf: AuthenticationBackend): APILms => {
|
|
|
46
63
|
me: async () => {
|
|
47
64
|
const user = await ApiInterface.user.me();
|
|
48
65
|
if (!user) return null;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}).then((res) => {
|
|
57
|
-
if (!res.ok) throw new Error(`Keycloak account fetch failed: ${res.status}`);
|
|
58
|
-
return res.json();
|
|
59
|
-
});
|
|
60
|
-
return {
|
|
61
|
-
...user,
|
|
62
|
-
full_name:
|
|
63
|
-
[keycloakAccount.firstName, keycloakAccount.lastName].filter(Boolean).join(' ') ||
|
|
64
|
-
user.full_name,
|
|
65
|
-
email: keycloakAccount.email || user.email,
|
|
66
|
-
};
|
|
67
|
-
} catch {
|
|
68
|
-
return user;
|
|
69
|
-
}
|
|
66
|
+
|
|
67
|
+
const claims = getTokenClaims(user.access_token);
|
|
68
|
+
return {
|
|
69
|
+
...user,
|
|
70
|
+
full_name: user.full_name || claims?.full_name,
|
|
71
|
+
email: user.email ?? claims?.email,
|
|
72
|
+
};
|
|
70
73
|
},
|
|
71
74
|
login: () => {
|
|
72
75
|
const next = encodeURIComponent(location.href);
|
|
@@ -552,7 +552,7 @@ describe.each([
|
|
|
552
552
|
enrollmentDiscounted.offerings[0].product = product;
|
|
553
553
|
fetchMock
|
|
554
554
|
.get(
|
|
555
|
-
`https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollmentDiscounted.id}&product_id=${product.id}&state=pending&state=pending_payment&state=no_payment&state=failed_payment&state=completed&state=draft&state=assigned&state=to_sign&state=signing&state=to_save_payment_method`,
|
|
555
|
+
`https://joanie.endpoint/api/v1.0/orders/?enrollment_id=${enrollmentDiscounted.id}&product_id=${product.id}&state=pending&state=pending_payment&state=no_payment&state=failed_payment&state=completed&state=pending_withdraw&state=draft&state=assigned&state=to_sign&state=signing&state=to_save_payment_method`,
|
|
556
556
|
{
|
|
557
557
|
results: [],
|
|
558
558
|
next: null,
|
|
@@ -706,6 +706,36 @@ describe.each([
|
|
|
706
706
|
screen.getByTestId('walkthrough-banner');
|
|
707
707
|
});
|
|
708
708
|
|
|
709
|
+
it('should render the account name and email from the OpenEdx profile', async () => {
|
|
710
|
+
const product = ProductFactory().one();
|
|
711
|
+
const paymentPlan = PaymentPlanFactory().one();
|
|
712
|
+
fetchMock
|
|
713
|
+
.get(
|
|
714
|
+
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(getFetchOrderQueryParams(product))}`,
|
|
715
|
+
[],
|
|
716
|
+
)
|
|
717
|
+
.get(
|
|
718
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
719
|
+
paymentPlan,
|
|
720
|
+
)
|
|
721
|
+
.get(
|
|
722
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/deep-link/`,
|
|
723
|
+
{},
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
render(<Wrapper product={product} isWithdrawable={true} paymentPlan={paymentPlan} />, {
|
|
727
|
+
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
screen.getByRole('heading', { level: 4, name: 'Account email' });
|
|
731
|
+
|
|
732
|
+
await screen.findByText(openApiEdxProfile.email);
|
|
733
|
+
|
|
734
|
+
screen.getByText(
|
|
735
|
+
'This email will be used to send you confirmation mails, it is the one you created your account with.',
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
709
739
|
it('should show a checkbox to waive withdrawal right if the product is not withdrawable', async () => {
|
|
710
740
|
const product = ProductFactory().one();
|
|
711
741
|
const paymentPlan = PaymentPlanFactory().one();
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router';
|
|
3
|
-
import { Enrollment, CredentialOrder, ProductType,
|
|
3
|
+
import { Enrollment, CredentialOrder, ProductType, OrderState } from 'types/Joanie';
|
|
4
4
|
import { Maybe, Nullable } from 'types/utils';
|
|
5
|
-
import {
|
|
5
|
+
import { OrderHelper } from 'utils/OrderHelper';
|
|
6
|
+
import { isOrder, useOrdersEnrollments } from 'pages/DashboardCourses/useOrdersEnrollments';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Orders canceled following the buyer's withdrawal must stay visible in the dashboard
|
|
10
|
+
* (the learner needs to keep a trace of that legal action), unlike other cancellation
|
|
11
|
+
* causes. The API can't filter on that distinction yet, so we only exclude REFUNDING/
|
|
12
|
+
* REFUNDED server-side and drop the remaining non-withdrawn CANCELED orders here.
|
|
13
|
+
*/
|
|
14
|
+
const isHiddenCanceledOrder = (item: CredentialOrder | Enrollment) =>
|
|
15
|
+
isOrder(item) && OrderHelper.isCanceled(item) && !OrderHelper.isWithdrawn(item);
|
|
6
16
|
|
|
7
17
|
const useLearnerCoursesSearch = () => {
|
|
8
18
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
@@ -23,7 +33,7 @@ const useLearnerCoursesSearch = () => {
|
|
|
23
33
|
query,
|
|
24
34
|
orderFilters: {
|
|
25
35
|
product_type: [ProductType.CREDENTIAL],
|
|
26
|
-
state_exclude:
|
|
36
|
+
state_exclude: [OrderState.REFUNDING, OrderState.REFUNDED],
|
|
27
37
|
},
|
|
28
38
|
});
|
|
29
39
|
|
|
@@ -41,7 +51,10 @@ const useLearnerCoursesSearch = () => {
|
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
if (isNewSearchLoading || data.length > orderAndEnrollmentList?.length) {
|
|
44
|
-
|
|
54
|
+
const visibleData = (data as (CredentialOrder | Enrollment)[]).filter(
|
|
55
|
+
(item) => !isHiddenCanceledOrder(item),
|
|
56
|
+
);
|
|
57
|
+
setOrderAndEnrollmentList(visibleData);
|
|
45
58
|
setCount(currentCount);
|
|
46
59
|
}
|
|
47
60
|
}, [data.length, isLoading, isNewSearchLoading, query]);
|
|
@@ -84,6 +84,7 @@ describe('useProductOrder', () => {
|
|
|
84
84
|
`&state=${OrderState.NO_PAYMENT}` +
|
|
85
85
|
`&state=${OrderState.FAILED_PAYMENT}` +
|
|
86
86
|
`&state=${OrderState.COMPLETED}` +
|
|
87
|
+
`&state=${OrderState.PENDING_WITHDRAW}` +
|
|
87
88
|
`&state=${OrderState.DRAFT}` +
|
|
88
89
|
`&state=${OrderState.ASSIGNED}` +
|
|
89
90
|
`&state=${OrderState.TO_SIGN}` +
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
CredentialOrderFactory,
|
|
11
11
|
} from 'utils/test/factories/joanie';
|
|
12
12
|
import { createTestQueryClient } from 'utils/test/createTestQueryClient';
|
|
13
|
-
import { CourseLight, Offering, Enrollment, CredentialOrder } from 'types/Joanie';
|
|
13
|
+
import { CourseLight, Offering, Enrollment, CredentialOrder, OrderState } from 'types/Joanie';
|
|
14
14
|
import { expectNoSpinner, expectSpinner } from 'utils/test/expectSpinner';
|
|
15
15
|
import { expectBannerError, expectBannerInfo, expectNoBannerInfo } from 'utils/test/expectBanner';
|
|
16
16
|
import { Deferred } from 'utils/test/deferred';
|
|
@@ -105,7 +105,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
105
105
|
fetchMock.get(
|
|
106
106
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
107
107
|
'?product_type=credential' +
|
|
108
|
-
'&state_exclude=canceled' +
|
|
109
108
|
'&state_exclude=refunding' +
|
|
110
109
|
'&state_exclude=refunded' +
|
|
111
110
|
'&page=1' +
|
|
@@ -153,7 +152,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
153
152
|
fetchMock.get(
|
|
154
153
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
155
154
|
'?product_type=credential' +
|
|
156
|
-
'&state_exclude=canceled' +
|
|
157
155
|
'&state_exclude=refunding' +
|
|
158
156
|
'&state_exclude=refunded' +
|
|
159
157
|
'&page=1' +
|
|
@@ -163,7 +161,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
163
161
|
next:
|
|
164
162
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
165
163
|
'?product_type=credential' +
|
|
166
|
-
'&state_exclude=canceled' +
|
|
167
164
|
'&state_exclude=refunding' +
|
|
168
165
|
'&state_exclude=refunded' +
|
|
169
166
|
'&page=2' +
|
|
@@ -175,7 +172,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
175
172
|
fetchMock.get(
|
|
176
173
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
177
174
|
'?product_type=credential' +
|
|
178
|
-
'&state_exclude=canceled' +
|
|
179
175
|
'&state_exclude=refunding' +
|
|
180
176
|
'&state_exclude=refunded' +
|
|
181
177
|
'&page=2' +
|
|
@@ -185,7 +181,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
185
181
|
next:
|
|
186
182
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
187
183
|
'?product_type=credential' +
|
|
188
|
-
'&state_exclude=canceled' +
|
|
189
184
|
'&state_exclude=refunding' +
|
|
190
185
|
'&state_exclude=refunded' +
|
|
191
186
|
'&page=3' +
|
|
@@ -197,7 +192,6 @@ describe('<DashboardCourses/>', () => {
|
|
|
197
192
|
fetchMock.get(
|
|
198
193
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
199
194
|
'?product_type=credential' +
|
|
200
|
-
'&state_exclude=canceled' +
|
|
201
195
|
'&state_exclude=refunding' +
|
|
202
196
|
'&state_exclude=refunded' +
|
|
203
197
|
'&page=3' +
|
|
@@ -271,13 +265,51 @@ describe('<DashboardCourses/>', () => {
|
|
|
271
265
|
expect(loadMoreButton).toBeEnabled();
|
|
272
266
|
}, 15000);
|
|
273
267
|
|
|
268
|
+
it('keeps a withdrawn order visible while filtering out other canceled orders', async () => {
|
|
269
|
+
const activeOrder = CredentialOrderFactory({
|
|
270
|
+
state: OrderState.COMPLETED,
|
|
271
|
+
created_on: '2026-01-03T00:00:00.000Z',
|
|
272
|
+
}).one();
|
|
273
|
+
const withdrawnOrder = CredentialOrderFactory({
|
|
274
|
+
state: OrderState.CANCELED,
|
|
275
|
+
created_on: '2026-01-02T00:00:00.000Z',
|
|
276
|
+
withdrawn_confirmation_at: '2026-01-02T00:00:00.000Z',
|
|
277
|
+
}).one();
|
|
278
|
+
const canceledOrder = CredentialOrderFactory({
|
|
279
|
+
state: OrderState.CANCELED,
|
|
280
|
+
created_on: '2026-01-01T00:00:00.000Z',
|
|
281
|
+
withdrawn_confirmation_at: null,
|
|
282
|
+
}).one();
|
|
283
|
+
const { orders, offerings } = mockOrders([activeOrder, withdrawnOrder, canceledOrder]);
|
|
284
|
+
|
|
285
|
+
fetchMock.get(
|
|
286
|
+
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
287
|
+
'?product_type=credential' +
|
|
288
|
+
'&state_exclude=refunding' +
|
|
289
|
+
'&state_exclude=refunded' +
|
|
290
|
+
'&page=1' +
|
|
291
|
+
`&page_size=${perPage}`,
|
|
292
|
+
{ results: orders, next: null, previous: null, count: orders.length },
|
|
293
|
+
);
|
|
294
|
+
fetchMock.get(
|
|
295
|
+
`https://joanie.endpoint/api/v1.0/enrollments/?was_created_by_order=false&is_active=true&page=1&page_size=${perPage}`,
|
|
296
|
+
{ results: [], next: null, previous: null, count: 0 },
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
render(<DashboardTest initialRoute={LearnerDashboardPaths.COURSES} />, {
|
|
300
|
+
wrapper: BaseJoanieAppWrapper,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await expectNoSpinner('Loading orders and enrollments...');
|
|
304
|
+
await waitFor(() => expectList([activeOrder, withdrawnOrder], offerings));
|
|
305
|
+
});
|
|
306
|
+
|
|
274
307
|
it('shows an error', async () => {
|
|
275
308
|
jest.spyOn(console, 'error').mockImplementation(noop);
|
|
276
309
|
const ordersDeferred = new Deferred();
|
|
277
310
|
fetchMock.get(
|
|
278
311
|
'https://joanie.endpoint/api/v1.0/orders/' +
|
|
279
312
|
'?product_type=credential' +
|
|
280
|
-
'&state_exclude=canceled' +
|
|
281
313
|
'&state_exclude=refunding' +
|
|
282
314
|
'&state_exclude=refunded' +
|
|
283
315
|
'&page=1' +
|