richie-education 2.33.1-dev9 → 2.34.1-dev2
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 +48 -8
- package/i18n/locales/es-ES.json +48 -8
- package/i18n/locales/fa-IR.json +48 -8
- package/i18n/locales/fr-CA.json +48 -8
- package/i18n/locales/fr-FR.json +48 -8
- package/i18n/locales/ko-KR.json +48 -8
- package/i18n/locales/pt-PT.json +55 -15
- package/i18n/locales/ru-RU.json +48 -8
- package/i18n/locales/vi-VN.json +48 -8
- package/js/api/joanie.ts +5 -0
- package/js/api/lms/dummy.ts +12 -10
- package/js/components/AddressesManagement/index.spec.tsx +1 -1
- package/js/components/AddressesManagement/index.tsx +123 -129
- package/js/components/ContractFrame/AbstractContractFrame.tsx +1 -1
- package/js/components/Icon/index.stories.tsx +1 -1
- package/js/components/PaymentInterfaces/PayplugLightbox.tsx +1 -1
- package/js/components/PaymentScheduleGrid/_styles.scss +6 -5
- package/js/components/PaymentScheduleGrid/index.tsx +16 -0
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +2 -2
- package/js/components/SearchInput/index.spec.tsx +6 -5
- package/js/components/SearchInput/index.tsx +9 -1
- package/js/hooks/useCreditCards/index.spec.tsx +83 -0
- package/js/hooks/useCreditCards/index.ts +53 -1
- package/js/hooks/useCreditCardsManagement.tsx +1 -10
- package/js/hooks/useLearnerCoursesSearch/index.tsx +2 -2
- package/js/pages/DashboardCourses/index.spec.tsx +51 -7
- package/js/pages/DashboardCreditCardsManagement/DashboardCreditCardBox.tsx +3 -5
- package/js/pages/DashboardCreditCardsManagement/_styles.scss +11 -3
- package/js/pages/DashboardCreditCardsManagement/index.spec.tsx +46 -25
- package/js/pages/DashboardCreditCardsManagement/index.tsx +21 -37
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +4 -2
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +1 -1
- package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +8 -5
- 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 +13 -1
- package/js/utils/OrderHelper/index.ts +9 -0
- package/js/utils/errors/HttpError.ts +1 -0
- package/js/utils/test/wrappers/types.ts +2 -2
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +7 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +14 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +3 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/index.tsx +12 -5
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +5 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +10 -8
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +5 -0
- package/js/widgets/Dashboard/components/SearchBar/index.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/SearchResultsCount/index.spec.tsx +1 -1
- package/js/widgets/Dashboard/index.spec.tsx +7 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.tsx +1 -5
- package/package.json +41 -39
- package/scss/vendors/css/cunningham-tokens.css +1 -0
package/i18n/locales/vi-VN.json
CHANGED
|
@@ -507,10 +507,6 @@
|
|
|
507
507
|
"description": "Empty placeholder of the dashboard credit cards management block",
|
|
508
508
|
"message": "You haven't created any credit cards yet."
|
|
509
509
|
},
|
|
510
|
-
"components.DashboardCreditCardsManagement.errorCannotPromoteMain": {
|
|
511
|
-
"description": "Error shown if a user tries to promote a main credit card",
|
|
512
|
-
"message": "Cannot promote main credit card."
|
|
513
|
-
},
|
|
514
510
|
"components.DashboardCreditCardsManagement.header": {
|
|
515
511
|
"description": "Title of the dashboard credit cards management block",
|
|
516
512
|
"message": "Credit cards"
|
|
@@ -627,6 +623,10 @@
|
|
|
627
623
|
"description": "Status shown on the dashboard order item when order is pending for payment",
|
|
628
624
|
"message": "On going"
|
|
629
625
|
},
|
|
626
|
+
"components.DashboardItem.Order.OrderStateLearnerMessage.statusRefunded": {
|
|
627
|
+
"description": "Status shown on the dashboard order item when order is refunded",
|
|
628
|
+
"message": "Refunded"
|
|
629
|
+
},
|
|
630
630
|
"components.DashboardItem.Order.OrderStateLearnerMessage.statusWaitingCounterSignature": {
|
|
631
631
|
"description": "Status shown on the dashboard order item when order is validated with contract's organization signature missing.",
|
|
632
632
|
"message": "On going"
|
|
@@ -675,6 +675,10 @@
|
|
|
675
675
|
"description": "Status shown on the dashboard order item when order is validated with no certificate",
|
|
676
676
|
"message": "On going"
|
|
677
677
|
},
|
|
678
|
+
"components.DashboardItem.Order.OrderStateTeacherMessage.statusRefunded": {
|
|
679
|
+
"description": "Status shown on the dashboard order item when order is refunded",
|
|
680
|
+
"message": "Refunded"
|
|
681
|
+
},
|
|
678
682
|
"components.DashboardItem.Order.OrderStateTeacherMessage.statusWaitingCounterSignature": {
|
|
679
683
|
"description": "Status shown on the dashboard order item when order is validated with contract's organization signature missing.",
|
|
680
684
|
"message": "To be signed"
|
|
@@ -1187,6 +1191,14 @@
|
|
|
1187
1191
|
"description": "Label for screen reader when a credit card is being tokenized.",
|
|
1188
1192
|
"message": "Payment method definition in progress."
|
|
1189
1193
|
},
|
|
1194
|
+
"components.PaymentScheduleGrid.state.canceled": {
|
|
1195
|
+
"description": "Label displayed for canceled payment state",
|
|
1196
|
+
"message": "Canceled"
|
|
1197
|
+
},
|
|
1198
|
+
"components.PaymentScheduleGrid.state.error": {
|
|
1199
|
+
"description": "Label displayed for error payment state. For learner we assume to display `pending`.",
|
|
1200
|
+
"message": "Pending"
|
|
1201
|
+
},
|
|
1190
1202
|
"components.PaymentScheduleGrid.state.paid": {
|
|
1191
1203
|
"description": "Label displayed for paid payment state",
|
|
1192
1204
|
"message": "Paid"
|
|
@@ -1195,6 +1207,10 @@
|
|
|
1195
1207
|
"description": "Label displayed for pending payment state",
|
|
1196
1208
|
"message": "Pending"
|
|
1197
1209
|
},
|
|
1210
|
+
"components.PaymentScheduleGrid.state.refunded": {
|
|
1211
|
+
"description": "Label displayed for refunded payment state",
|
|
1212
|
+
"message": "Refunded"
|
|
1213
|
+
},
|
|
1198
1214
|
"components.PaymentScheduleGrid.state.refused": {
|
|
1199
1215
|
"description": "Label displayed for refused payment state",
|
|
1200
1216
|
"message": "Refused"
|
|
@@ -1527,6 +1543,10 @@
|
|
|
1527
1543
|
"description": "Accessibility text for the search button inside the Search input.",
|
|
1528
1544
|
"message": "Search"
|
|
1529
1545
|
},
|
|
1546
|
+
"components.SearchInput.label": {
|
|
1547
|
+
"description": "Accessibility text for the search input label.",
|
|
1548
|
+
"message": "Search"
|
|
1549
|
+
},
|
|
1530
1550
|
"components.SearchSuggestField.searchFieldPlaceholder": {
|
|
1531
1551
|
"description": "Placeholder text displayed in the search field when it is empty.",
|
|
1532
1552
|
"message": "Search for courses, organizations, categories"
|
|
@@ -1611,6 +1631,10 @@
|
|
|
1611
1631
|
"description": "Course date of an opened course run block",
|
|
1612
1632
|
"message": "From {startDate} {endDate, select, undefined {} other {to {endDate}}}"
|
|
1613
1633
|
},
|
|
1634
|
+
"components.SyllabusCourseRun.enrollNow": {
|
|
1635
|
+
"description": "CTA for users to enroll on ongoing of future open course.",
|
|
1636
|
+
"message": "Enroll now"
|
|
1637
|
+
},
|
|
1614
1638
|
"components.SyllabusCourseRun.enrollment": {
|
|
1615
1639
|
"description": "Title of the enrollment dates section of an opened course run block",
|
|
1616
1640
|
"message": "Enrollment"
|
|
@@ -1623,10 +1647,18 @@
|
|
|
1623
1647
|
"description": "Title of the languages section of an opened course run block",
|
|
1624
1648
|
"message": "Languages"
|
|
1625
1649
|
},
|
|
1650
|
+
"components.SyllabusCourseRun.studyNow": {
|
|
1651
|
+
"description": "CTA for users to enroll on archived course.",
|
|
1652
|
+
"message": "Study now"
|
|
1653
|
+
},
|
|
1626
1654
|
"components.SyllabusCourseRunCompacted.course": {
|
|
1627
1655
|
"description": "Title of the course dates section of an opened course run block",
|
|
1628
1656
|
"message": "Course"
|
|
1629
1657
|
},
|
|
1658
|
+
"components.SyllabusCourseRunCompacted.enrollNow": {
|
|
1659
|
+
"description": "CTA for users to enroll on ongoing of future open course.",
|
|
1660
|
+
"message": "Enroll now"
|
|
1661
|
+
},
|
|
1630
1662
|
"components.SyllabusCourseRunCompacted.languages": {
|
|
1631
1663
|
"description": "Title of the languages section of an opened course run block",
|
|
1632
1664
|
"message": "Languages"
|
|
@@ -1639,6 +1671,10 @@
|
|
|
1639
1671
|
"description": "Self paced course run block with no end date",
|
|
1640
1672
|
"message": "Available"
|
|
1641
1673
|
},
|
|
1674
|
+
"components.SyllabusCourseRunCompacted.studyNow": {
|
|
1675
|
+
"description": "CTA for users to enroll on archived course.",
|
|
1676
|
+
"message": "Study now"
|
|
1677
|
+
},
|
|
1642
1678
|
"components.SyllabusCourseRunsList.multipleOpenedCourseRuns": {
|
|
1643
1679
|
"description": "Message displayed when there are multiple opened course runs on a syllabus",
|
|
1644
1680
|
"message": "{count} course runs are currently open for this course"
|
|
@@ -1955,6 +1991,10 @@
|
|
|
1955
1991
|
"description": "Error message shown to the user when course fetch request fails.",
|
|
1956
1992
|
"message": "An error occurred while fetching course. Please retry later."
|
|
1957
1993
|
},
|
|
1994
|
+
"hooks.useCreditCards.errorCannotDelete": {
|
|
1995
|
+
"description": "Error message shown to the user when trying to delete a credit card that is used to pay at least order.",
|
|
1996
|
+
"message": "Cannot delete the credit card •••• •••• •••• {last_numbers} because it is used to pay at least one of your order."
|
|
1997
|
+
},
|
|
1958
1998
|
"hooks.useCreditCards.errorDelete": {
|
|
1959
1999
|
"description": "Error message shown to the user when credit card deletion request fails.",
|
|
1960
2000
|
"message": "An error occurred while deleting the credit card. Please retry later."
|
|
@@ -1963,6 +2003,10 @@
|
|
|
1963
2003
|
"description": "Error message shown to the user when no credit cards matches.",
|
|
1964
2004
|
"message": "Cannot find the credit card"
|
|
1965
2005
|
},
|
|
2006
|
+
"hooks.useCreditCards.errorPromote": {
|
|
2007
|
+
"description": "Error message shown to the user when promoting a credit card fails.",
|
|
2008
|
+
"message": "Cannot set the credit card as default"
|
|
2009
|
+
},
|
|
1966
2010
|
"hooks.useCreditCards.errorSelect": {
|
|
1967
2011
|
"description": "Error message shown to the user when credit cards fetch request fails.",
|
|
1968
2012
|
"message": "An error occurred while fetching credit cards. Please retry later."
|
|
@@ -1979,10 +2023,6 @@
|
|
|
1979
2023
|
"description": "Confirmation message shown to the user when he wants to delete a credit card",
|
|
1980
2024
|
"message": "Are you sure you want to delete the credit card? ⚠️ You cannot undo this change after."
|
|
1981
2025
|
},
|
|
1982
|
-
"hooks.useCreditCardsManagement.errorCannotRemoveMain": {
|
|
1983
|
-
"description": "Error shown if a user tries to delete a main credit card",
|
|
1984
|
-
"message": "Cannot remove main credit card."
|
|
1985
|
-
},
|
|
1986
2026
|
"hooks.useDashboardAddressForm.isMainInputLabel": {
|
|
1987
2027
|
"description": "Label of the \"is_main\" input",
|
|
1988
2028
|
"message": "Use this address as default"
|
package/js/api/joanie.ts
CHANGED
|
@@ -83,6 +83,7 @@ export const getRoutes = () => {
|
|
|
83
83
|
update: `${baseUrl}/credit-cards/:id/`,
|
|
84
84
|
delete: `${baseUrl}/credit-cards/:id/`,
|
|
85
85
|
tokenize: `${baseUrl}/credit-cards/tokenize-card/`,
|
|
86
|
+
promote: `${baseUrl}/credit-cards/:id/promote/`,
|
|
86
87
|
},
|
|
87
88
|
addresses: {
|
|
88
89
|
get: `${baseUrl}/addresses/:id/`,
|
|
@@ -242,6 +243,10 @@ const API = (): Joanie.API => {
|
|
|
242
243
|
}).then(checkStatus),
|
|
243
244
|
tokenize: async () =>
|
|
244
245
|
fetchWithJWT(ROUTES.user.creditCards.tokenize, { method: 'POST' }).then(checkStatus),
|
|
246
|
+
promote: async (id) =>
|
|
247
|
+
fetchWithJWT(ROUTES.user.creditCards.promote.replace(':id', id), {
|
|
248
|
+
method: 'PATCH',
|
|
249
|
+
}).then(checkStatus),
|
|
245
250
|
},
|
|
246
251
|
addresses: {
|
|
247
252
|
get: (id?: string) => {
|
package/js/api/lms/dummy.ts
CHANGED
|
@@ -25,24 +25,26 @@ type JWTPayload = {
|
|
|
25
25
|
username: string;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
/* All JWT tokens will expire the
|
|
28
|
+
/* All JWT tokens will expire the 02 Feb 2026 ! */
|
|
29
29
|
const JOANIE_DEV_DEMO_USER_JWT_TOKENS = {
|
|
30
|
-
admin:
|
|
31
|
-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMzOTI4MjE0LCJpYXQiOjE3MDIzNjU1MjMsImp0aSI6IjRhMzQxZWVmMmVhOTRkNGFiMzQ5OThkOWE4ZDM5MTI0IiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsImxhbmd1YWdlIjoiZW4tdXMiLCJ1c2VybmFtZSI6ImFkbWluIiwiZnVsbF9uYW1lIjoiIn0.rT8nymp8f4T7tIIXO-M5-ahXBwxoDNVqtaZIrb_GHuk',
|
|
32
30
|
user0:
|
|
33
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
31
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6ImQwZmU1Zjg5ZjFhYTQ4YmM5NDhmNWU4ODFkNTNhNTU2IiwiZW1haWwiOiJwc21pdGhAZXhhbXBsZS5vcmciLCJsYW5ndWFnZSI6ImVuLXVzIiwidXNlcm5hbWUiOiJ1c2VyMCIsImZ1bGxfbmFtZSI6Ik90aGVyIE93bmVyIn0.eCawfaCzpO7U7iUPC1TE_XYDiRjq_crI93GqE8Fj8zc',
|
|
34
32
|
user1:
|
|
35
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
33
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6ImIwYjk3YjZkZjFlMzRkMTg4NjFiMGFhMjcxYWI0YWU1IiwiZW1haWwiOiJzYW1wc29uYW5uYUBleGFtcGxlLm9yZyIsImxhbmd1YWdlIjoiZW4tdXMiLCJ1c2VybmFtZSI6InVzZXIxIiwiZnVsbF9uYW1lIjoiT3RoZXIgT3duZXIifQ.yd46_63iuw19zmzH8aVNRAVAvAE4VGH8W8BjmFs6PPU',
|
|
36
34
|
user2:
|
|
37
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
35
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6ImNmYzY2OTNmY2Q5ZTRlZGViM2Y2NzU1MTZhNDIzMTdiIiwiZW1haWwiOiJsb3BlemFtYmVyQGV4YW1wbGUub3JnIiwibGFuZ3VhZ2UiOiJlbi11cyIsInVzZXJuYW1lIjoidXNlcjIiLCJmdWxsX25hbWUiOiJPdGhlciBPd25lciJ9.TlFILOXY-wK29M_BUgDKgjdOovSfEIlw5cNXed6ZV3w',
|
|
38
36
|
user3:
|
|
39
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
37
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6IjA4ZTcxZGJjYWIyMDRjMmZhZjgyMDVjZTRiNTliMjZiIiwiZW1haWwiOiJsb25nZWxpemFiZXRoQGV4YW1wbGUub3JnIiwibGFuZ3VhZ2UiOiJlbi11cyIsInVzZXJuYW1lIjoidXNlcjMiLCJmdWxsX25hbWUiOiJPdGhlciBPd25lciJ9.8NxYyjc567lO2Yc7me-TQr8PNvKqB5VLRzHd1Z4vA4U',
|
|
40
38
|
user4:
|
|
41
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
39
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6ImVmZGRkM2Q0YTdmZDQ4ZmFhYmZkM2Q2OTI4YzMwM2U4IiwiZW1haWwiOiJqb25lc2plbm5pZmVyQGV4YW1wbGUub3JnIiwibGFuZ3VhZ2UiOiJlbi11cyIsInVzZXJuYW1lIjoidXNlcjQiLCJmdWxsX25hbWUiOiJPdGhlciBPd25lciJ9.Wn5CKuNPn0s4B_76Mxd3zTKqdUMaZGV456bhZ-fDe-o',
|
|
42
40
|
organization_owner:
|
|
43
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
41
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6ImRiYmU2ZGExZjhmNDQzNDA4N2U2NzQ0YTIzM2JmNjFiIiwiZW1haWwiOiJkZXZlbG9wZXIrb3JnYW5pemF0aW9uX293bmVyQGV4YW1wbGUuY29tIiwibGFuZ3VhZ2UiOiJlbi11cyIsInVzZXJuYW1lIjoib3JnYW5pemF0aW9uX293bmVyIiwiZnVsbF9uYW1lIjoiT3JnYSBPd25lciJ9.a6QjOAOxCw7ZFKvg8OCcUaW8Xhbmfuqy3cwIqUCPfzE',
|
|
44
42
|
student_user:
|
|
45
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
43
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6IjNhMGExYjM0OWEwNDQxNTg5ODU4NGUwZjMwNTc5M2EwIiwiZW1haWwiOiJkZXZlbG9wZXIrc3R1ZGVudF91c2VyQGV4YW1wbGUuY29tIiwibGFuZ3VhZ2UiOiJmci1mciIsInVzZXJuYW1lIjoic3R1ZGVudF91c2VyIiwiZnVsbF9uYW1lIjoiXHUwMGM5dHVkaWFudCJ9.3VvjPXwtuNA684hSIem3X2uFD-4WH8fipVDXMsi1cAc',
|
|
44
|
+
second_student_user:
|
|
45
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6Ijg5ZDIyNDJjODRkODRiNThiZWVkYjg1NmU2MGNiM2FiIiwiZW1haWwiOiJkZXZlbG9wZXIrc2Vjb25kX3N0dWRlbnRfdXNlckBleGFtcGxlLmNvbSIsImxhbmd1YWdlIjoiZnItZnIiLCJ1c2VybmFtZSI6InNlY29uZF9zdHVkZW50X3VzZXIiLCJmdWxsX25hbWUiOiJcdTAwYzl0dWRpYW50IDAwMiJ9.p5p4Ku0w8mHortWW9TYHTJgORF9wnfCpq-6pvBRjU0Y',
|
|
46
|
+
admin:
|
|
47
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMjIzOTY5LCJpYXQiOjE3Mzg2ODc5NjksImp0aSI6Ijk4M2UzNmI5MTUzODQ2Mjg4ZGMxNWNjOTAwNDgwMDA4IiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsImxhbmd1YWdlIjoiZW4tdXMiLCJ1c2VybmFtZSI6ImFkbWluIiwiZnVsbF9uYW1lIjoiIn0.VuSqfh4l0vtIDSdkEgCNyciiOhlFlMAsf5u5snm2Avw',
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
export type DevDemoUser = keyof typeof JOANIE_DEV_DEMO_USER_JWT_TOKENS;
|
|
@@ -6,7 +6,7 @@ import fetchMock from 'fetch-mock';
|
|
|
6
6
|
import { IntlProvider } from 'react-intl';
|
|
7
7
|
import countries from 'i18n-iso-countries';
|
|
8
8
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
9
|
-
import { PropsWithChildren } from 'react';
|
|
9
|
+
import React, { PropsWithChildren } from 'react';
|
|
10
10
|
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
11
11
|
import userEvent, { UserEvent } from '@testing-library/user-event';
|
|
12
12
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Children,
|
|
1
|
+
import { Children, useEffect, useState, RefAttributes } from 'react';
|
|
2
2
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
3
3
|
import { Button } from '@openfun/cunningham-react';
|
|
4
4
|
import AddressForm, { type AddressFormValues } from 'components/AddressesManagement/AddressForm';
|
|
@@ -7,7 +7,6 @@ import { Icon, IconTypeEnum } from 'components/Icon';
|
|
|
7
7
|
import RegisteredAddress from 'components/RegisteredAddress';
|
|
8
8
|
import { useAddressesManagement } from 'hooks/useAddressesManagement';
|
|
9
9
|
import type * as Joanie from 'types/Joanie';
|
|
10
|
-
import { Address } from 'types/Joanie';
|
|
11
10
|
import { Maybe } from 'types/utils';
|
|
12
11
|
|
|
13
12
|
// constant used as `address.id` for local address
|
|
@@ -106,147 +105,142 @@ export const messages = defineMessages({
|
|
|
106
105
|
},
|
|
107
106
|
});
|
|
108
107
|
|
|
109
|
-
interface AddressesManagementProps {
|
|
108
|
+
interface AddressesManagementProps extends RefAttributes<HTMLDivElement> {
|
|
110
109
|
handleClose: () => void;
|
|
111
110
|
selectAddress: (address: Joanie.Address) => void;
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const AddressesManagement =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
} = useAddressesManagement();
|
|
113
|
+
const AddressesManagement = ({ handleClose, selectAddress, ref }: AddressesManagementProps) => {
|
|
114
|
+
const intl = useIntl();
|
|
115
|
+
const [editedAddress, setEditedAddress] = useState<Maybe<Joanie.Address>>();
|
|
116
|
+
const {
|
|
117
|
+
methods: { setError, create, update, remove, promote },
|
|
118
|
+
states: { error },
|
|
119
|
+
...addresses
|
|
120
|
+
} = useAddressesManagement();
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Sort addresses ascending by title according to the locale
|
|
124
|
+
*
|
|
125
|
+
* @param {Joanie.Address} a
|
|
126
|
+
* @param {Joanie.Address} b
|
|
127
|
+
* @returns {Joanie.Address[]} Sorted addresses ascending by title
|
|
128
|
+
*/
|
|
129
|
+
const sortAddressByTitleAsc = (a: Joanie.Address, b: Joanie.Address) => {
|
|
130
|
+
return a.title.localeCompare(b.title, [intl.locale, intl.defaultLocale]);
|
|
131
|
+
};
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
/**
|
|
134
|
+
* update `selectedAddress` state with the address provided
|
|
135
|
+
* then close the address management form
|
|
136
|
+
*
|
|
137
|
+
* @param {Joanie.Address} address
|
|
138
|
+
*/
|
|
139
|
+
const handleSelect = (address: Joanie.Address) => {
|
|
140
|
+
setError(undefined);
|
|
141
|
+
selectAddress(address);
|
|
142
|
+
handleClose();
|
|
143
|
+
};
|
|
146
144
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Create a new address according to form values
|
|
147
|
+
* then update `selectedAddress` state with this new one.
|
|
148
|
+
* If `save` checkbox input is checked, the address is persisted
|
|
149
|
+
* otherwise it is only stored through the `selectedAddress` state.
|
|
150
|
+
*
|
|
151
|
+
* @param {AddressFormValues} formValues address fields to update
|
|
152
|
+
*/
|
|
153
|
+
const handleCreate = async ({ save, ...address }: AddressFormValues) => {
|
|
154
|
+
if (save) {
|
|
155
|
+
await create(address, { onSuccess: handleSelect });
|
|
156
|
+
} else {
|
|
157
|
+
handleSelect({
|
|
158
|
+
id: LOCAL_BILLING_ADDRESS_ID,
|
|
159
|
+
is_main: false,
|
|
160
|
+
...address,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
166
164
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Update the `editedAddress` with new values provided as argument
|
|
167
|
+
* then clear `editedAddress` state if request succeeded.
|
|
168
|
+
*
|
|
169
|
+
* @param {AddressFormValues} formValues address fields to update
|
|
170
|
+
*/
|
|
171
|
+
const handleUpdate = async ({ save, ...newAddress }: AddressFormValues) => {
|
|
172
|
+
update(
|
|
173
|
+
{
|
|
174
|
+
...editedAddress!,
|
|
175
|
+
...newAddress,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
onSuccess: () => setEditedAddress(undefined),
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
};
|
|
184
182
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
setError(undefined);
|
|
185
|
+
if (editedAddress) {
|
|
186
|
+
document.querySelector<HTMLElement>('[name="address-form"] input')?.focus();
|
|
187
|
+
}
|
|
188
|
+
}, [editedAddress]);
|
|
191
189
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<header>
|
|
207
|
-
<h2 className="h5">
|
|
208
|
-
<FormattedMessage {...messages.registeredAddresses} />
|
|
209
|
-
</h2>
|
|
210
|
-
</header>
|
|
211
|
-
<ul className="registered-addresses-list">
|
|
212
|
-
{Children.toArray(
|
|
213
|
-
addresses.items
|
|
214
|
-
.sort(sortAddressByTitleAsc)
|
|
215
|
-
.map((address) => (
|
|
216
|
-
<RegisteredAddress
|
|
217
|
-
address={address}
|
|
218
|
-
edit={setEditedAddress}
|
|
219
|
-
promote={promote}
|
|
220
|
-
remove={remove}
|
|
221
|
-
select={handleSelect}
|
|
222
|
-
/>
|
|
223
|
-
)),
|
|
224
|
-
)}
|
|
225
|
-
</ul>
|
|
226
|
-
</section>
|
|
227
|
-
) : null}
|
|
228
|
-
<section className={`address-form ${editedAddress ? 'address-form--highlighted' : ''}`}>
|
|
190
|
+
return (
|
|
191
|
+
<div className="AddressesManagement" ref={ref}>
|
|
192
|
+
<Button
|
|
193
|
+
className="AddressesManagement__closeButton"
|
|
194
|
+
color="tertiary"
|
|
195
|
+
size="small"
|
|
196
|
+
onClick={handleClose}
|
|
197
|
+
>
|
|
198
|
+
<Icon name={IconTypeEnum.CHEVRON_LEFT_OUTLINE} className="button__icon" />
|
|
199
|
+
<FormattedMessage {...messages.closeButton} />
|
|
200
|
+
</Button>
|
|
201
|
+
{error && <Banner message={error} type={BannerType.ERROR} rounded />}
|
|
202
|
+
{addresses.items.length > 0 ? (
|
|
203
|
+
<section className="address-registered">
|
|
229
204
|
<header>
|
|
230
205
|
<h2 className="h5">
|
|
231
|
-
{
|
|
232
|
-
<FormattedMessage
|
|
233
|
-
{...messages.editAddress}
|
|
234
|
-
values={{ title: editedAddress.title }}
|
|
235
|
-
/>
|
|
236
|
-
) : (
|
|
237
|
-
<FormattedMessage {...messages.addAddress} />
|
|
238
|
-
)}
|
|
206
|
+
<FormattedMessage {...messages.registeredAddresses} />
|
|
239
207
|
</h2>
|
|
240
208
|
</header>
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
209
|
+
<ul className="registered-addresses-list">
|
|
210
|
+
{Children.toArray(
|
|
211
|
+
addresses.items
|
|
212
|
+
.sort(sortAddressByTitleAsc)
|
|
213
|
+
.map((address) => (
|
|
214
|
+
<RegisteredAddress
|
|
215
|
+
address={address}
|
|
216
|
+
edit={setEditedAddress}
|
|
217
|
+
promote={promote}
|
|
218
|
+
remove={remove}
|
|
219
|
+
select={handleSelect}
|
|
220
|
+
/>
|
|
221
|
+
)),
|
|
222
|
+
)}
|
|
223
|
+
</ul>
|
|
246
224
|
</section>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
225
|
+
) : null}
|
|
226
|
+
<section className={`address-form ${editedAddress ? 'address-form--highlighted' : ''}`}>
|
|
227
|
+
<header>
|
|
228
|
+
<h2 className="h5">
|
|
229
|
+
{editedAddress ? (
|
|
230
|
+
<FormattedMessage {...messages.editAddress} values={{ title: editedAddress.title }} />
|
|
231
|
+
) : (
|
|
232
|
+
<FormattedMessage {...messages.addAddress} />
|
|
233
|
+
)}
|
|
234
|
+
</h2>
|
|
235
|
+
</header>
|
|
236
|
+
<AddressForm
|
|
237
|
+
address={editedAddress}
|
|
238
|
+
handleReset={() => setEditedAddress(undefined)}
|
|
239
|
+
onSubmit={editedAddress ? handleUpdate : handleCreate}
|
|
240
|
+
/>
|
|
241
|
+
</section>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
251
245
|
|
|
252
246
|
export default AddressesManagement;
|
|
@@ -134,7 +134,7 @@ const ContractFrameContent = ({
|
|
|
134
134
|
const [signatureType, setSignatureType] = useState<SignatureType>();
|
|
135
135
|
const [invitationLink, setInvitationLink] = useState<Maybe<string>>();
|
|
136
136
|
const [error, setError] = useState<Maybe<string>>();
|
|
137
|
-
const timeoutRef = useRef<NodeJS.Timeout>();
|
|
137
|
+
const timeoutRef = useRef<NodeJS.Timeout>(undefined);
|
|
138
138
|
|
|
139
139
|
const setErrored = (e: string) => {
|
|
140
140
|
setStep(ContractSteps.ERROR);
|
|
@@ -29,7 +29,7 @@ type IconContainerProps = {
|
|
|
29
29
|
};
|
|
30
30
|
const IconContainer = ({ name, enumKey }: IconContainerProps) => {
|
|
31
31
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
32
|
-
const timeoutRef = useRef<NodeJS.Timeout>();
|
|
32
|
+
const timeoutRef = useRef<NodeJS.Timeout>(undefined);
|
|
33
33
|
const ENUM_NAME = 'IconTypeEnum';
|
|
34
34
|
|
|
35
35
|
const styleContainer: CSSProperties = {
|
|
@@ -17,7 +17,7 @@ const PayplugLightbox = ({
|
|
|
17
17
|
onError,
|
|
18
18
|
...props
|
|
19
19
|
}: PaymentInterfaceProps<PayplugPayment>) => {
|
|
20
|
-
const ref = useRef<ReturnType<typeof setTimeout>>();
|
|
20
|
+
const ref = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
21
21
|
|
|
22
22
|
/** type guard to check if the payment is a payment one click */
|
|
23
23
|
const isPaidPayment = (p: PayplugPayment) => p?.is_paid === true;
|
|
@@ -22,9 +22,10 @@
|
|
|
22
22
|
font-weight: var(--c--theme--font--weight--semibold);
|
|
23
23
|
font-size: rem-calc(12px);
|
|
24
24
|
|
|
25
|
-
&--
|
|
26
|
-
|
|
27
|
-
color: var(--c--theme--colors--
|
|
25
|
+
&--canceled,
|
|
26
|
+
&--refunded {
|
|
27
|
+
color: var(--c--theme--colors--white);
|
|
28
|
+
background-color: var(--c--theme--colors--grey59);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
&--paid {
|
|
@@ -37,8 +38,8 @@
|
|
|
37
38
|
color: var(--c--theme--colors--white);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
&--
|
|
41
|
-
&--
|
|
41
|
+
&--refused,
|
|
42
|
+
&--error {
|
|
42
43
|
background-color: var(--c--theme--colors--firebrick6);
|
|
43
44
|
color: var(--c--theme--colors--white);
|
|
44
45
|
}
|
|
@@ -22,6 +22,12 @@ export const stateMessages = defineMessages({
|
|
|
22
22
|
defaultMessage: 'Pending',
|
|
23
23
|
description: 'Label displayed for pending payment state',
|
|
24
24
|
},
|
|
25
|
+
[PaymentScheduleState.ERROR]: {
|
|
26
|
+
id: 'components.PaymentScheduleGrid.state.error',
|
|
27
|
+
defaultMessage: 'Pending',
|
|
28
|
+
description:
|
|
29
|
+
'Label displayed for error payment state. For learner we assume to display `pending`.',
|
|
30
|
+
},
|
|
25
31
|
[PaymentScheduleState.PAID]: {
|
|
26
32
|
id: 'components.PaymentScheduleGrid.state.paid',
|
|
27
33
|
defaultMessage: 'Paid',
|
|
@@ -32,6 +38,16 @@ export const stateMessages = defineMessages({
|
|
|
32
38
|
defaultMessage: 'Refused',
|
|
33
39
|
description: 'Label displayed for refused payment state',
|
|
34
40
|
},
|
|
41
|
+
[PaymentScheduleState.CANCELED]: {
|
|
42
|
+
id: 'components.PaymentScheduleGrid.state.canceled',
|
|
43
|
+
defaultMessage: 'Canceled',
|
|
44
|
+
description: 'Label displayed for canceled payment state',
|
|
45
|
+
},
|
|
46
|
+
[PaymentScheduleState.REFUNDED]: {
|
|
47
|
+
id: 'components.PaymentScheduleGrid.state.refunded',
|
|
48
|
+
defaultMessage: 'Refunded',
|
|
49
|
+
description: 'Label displayed for refunded payment state',
|
|
50
|
+
},
|
|
35
51
|
});
|
|
36
52
|
|
|
37
53
|
export const PaymentScheduleGrid = ({ schedule }: Props) => {
|
|
@@ -47,7 +47,7 @@ const messages = defineMessages({
|
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
const SaleTunnelSavePaymentMethod = () => {
|
|
50
|
-
const initialCreditCards = useRef<CreditCard[]>();
|
|
50
|
+
const initialCreditCards = useRef<CreditCard[]>([]);
|
|
51
51
|
const [shouldPoll, setShouldPoll] = useState(false);
|
|
52
52
|
const [payment, setPayment] = useState<Payment>();
|
|
53
53
|
const [error, setError] = useState<string>();
|
|
@@ -71,7 +71,7 @@ const SaleTunnelSavePaymentMethod = () => {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const waitForNewCreditCard = () => {
|
|
74
|
-
const initialIds = initialCreditCards.current
|
|
74
|
+
const initialIds = initialCreditCards.current.map((cc) => cc.id);
|
|
75
75
|
const newCard = creditCardsQuery.items.find((cc) => !initialIds.includes(cc.id));
|
|
76
76
|
|
|
77
77
|
if (!newCard) return;
|
|
@@ -10,14 +10,15 @@ describe('<SearchInput />', () => {
|
|
|
10
10
|
|
|
11
11
|
const inputProps = {};
|
|
12
12
|
|
|
13
|
-
it('renders with the input field and button', () => {
|
|
14
|
-
const { container,
|
|
13
|
+
it('renders with the input field, label and button', () => {
|
|
14
|
+
const { container, getByRole, getByLabelText } = render(
|
|
15
15
|
<IntlProvider locale="en">
|
|
16
16
|
<SearchInput context={contextProps} inputProps={inputProps} />
|
|
17
17
|
</IntlProvider>,
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
getByRole('button', { name: 'Search' });
|
|
21
|
+
getByLabelText('Search');
|
|
21
22
|
// NB: we're searching the DOM as labelling and a11y for this input are handled by `react-autosuggest`
|
|
22
23
|
expect(container.querySelector('input')).not.toBeNull();
|
|
23
24
|
expect(container.innerHTML).toContain('icon-magnifying-glass');
|
|
@@ -26,13 +27,13 @@ describe('<SearchInput />', () => {
|
|
|
26
27
|
it('triggers the passed callback on click', () => {
|
|
27
28
|
const callback = jest.fn();
|
|
28
29
|
|
|
29
|
-
const {
|
|
30
|
+
const { getByRole } = render(
|
|
30
31
|
<IntlProvider locale="en">
|
|
31
32
|
<SearchInput context={contextProps} inputProps={inputProps} onClick={callback} />
|
|
32
33
|
</IntlProvider>,
|
|
33
34
|
);
|
|
34
35
|
|
|
35
|
-
fireEvent.click(
|
|
36
|
+
fireEvent.click(getByRole('button', { name: 'Search' }));
|
|
36
37
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
37
38
|
});
|
|
38
39
|
});
|