richie-education 3.3.2-dev6 → 3.4.1-dev13
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/.eslintrc.json +0 -3
- package/.storybook/main.js +11 -12
- package/.storybook/preview.tsx +49 -24
- package/cunningham.cjs +31 -0
- package/i18n/compile-translations.js +12 -10
- package/i18n/locales/ar-SA.json +20 -0
- package/i18n/locales/es-ES.json +20 -0
- package/i18n/locales/fa-IR.json +20 -0
- package/i18n/locales/fr-CA.json +20 -0
- package/i18n/locales/fr-FR.json +21 -1
- package/i18n/locales/ko-KR.json +20 -0
- package/i18n/locales/pt-PT.json +42 -22
- package/i18n/locales/ru-RU.json +20 -0
- package/i18n/locales/vi-VN.json +20 -0
- package/jest.config.js +5 -0
- package/js/api/joanie.ts +20 -0
- package/js/api/utils.ts +4 -3
- package/js/components/AddressesManagement/AddressForm/index.stories.tsx +1 -1
- package/js/components/AddressesManagement/AddressForm/index.tsx +4 -3
- package/js/components/AddressesManagement/index.stories.tsx +1 -1
- package/js/components/AddressesManagement/index.tsx +5 -3
- package/js/components/Badge/index.stories.tsx +1 -1
- package/js/components/Badge/index.tsx +1 -1
- package/js/components/Banner/index.stories.tsx +1 -1
- package/js/components/CourseGlimpse/index.stories.tsx +1 -1
- package/js/components/CourseGlimpseList/index.stories.tsx +1 -1
- package/js/components/CreditCardSelector/_styles.scss +2 -2
- package/js/components/CreditCardSelector/index.tsx +11 -3
- package/js/components/DownloadAgreementButton/index.tsx +51 -0
- package/js/components/DownloadBatchOrderSeatsButton/index.spec.tsx +46 -0
- package/js/components/DownloadBatchOrderSeatsButton/index.tsx +80 -0
- package/js/components/DownloadCertificateButton/index.tsx +2 -1
- package/js/components/DownloadContractButton/index.tsx +7 -1
- package/js/components/Form/Form/index.tsx +4 -2
- package/js/components/Icon/index.stories.tsx +2 -1
- package/js/components/Modal/index.stories.tsx +1 -1
- package/js/components/Modal/index.tsx +2 -1
- package/js/components/OpenEdxFullNameForm/index.stories.tsx +1 -1
- package/js/components/OpenEdxFullNameForm/index.tsx +2 -2
- package/js/components/PaymentScheduleGrid/_styles.scss +2 -2
- package/js/components/PurchaseButton/index.stories.tsx +1 -1
- package/js/components/RegisteredAddress/index.stories.tsx +1 -1
- package/js/components/RegisteredAddress/index.tsx +4 -2
- package/js/components/SaleTunnel/AddressSelector/CreateAddressFormModal.tsx +1 -1
- package/js/components/SaleTunnel/AddressSelector/EditAddressFormModal.tsx +1 -1
- package/js/components/SaleTunnel/AddressSelector/index.tsx +4 -2
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +5 -4
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +27 -5
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +1 -1
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +9 -6
- package/js/components/SaleTunnel/_styles.scss +9 -8
- package/js/components/SaleTunnel/index.credential.spec.tsx +50 -1
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +5 -5
- package/js/components/SaleTunnel/index.full-process-b2c.spec.tsx +1 -1
- package/js/components/SaleTunnel/index.stories.tsx +1 -1
- package/js/components/Spinner/index.stories.tsx +1 -1
- package/js/components/Tabs/index.stories.tsx +1 -1
- package/js/components/Tabs/index.tsx +2 -1
- package/js/components/TeacherDashboardCourseList/index.tsx +2 -1
- package/js/hooks/useAddressesManagement.tsx +4 -2
- package/js/hooks/useBatchOrder/index.tsx +21 -1
- package/js/hooks/useCreditCards/index.ts +6 -4
- package/js/hooks/useDashboardAddressForm.tsx +3 -3
- package/js/hooks/useDownloadAgreement/index.spec.tsx +136 -0
- package/js/hooks/useDownloadAgreement/index.tsx +25 -0
- package/js/hooks/useDownloadBatchOrderSeats/index.spec.tsx +132 -0
- package/js/hooks/useDownloadBatchOrderSeats/index.tsx +24 -0
- package/js/hooks/useMatchMedia.ts +1 -1
- package/js/hooks/useResources/useResourcesRoot.ts +1 -1
- package/js/hooks/useUnionResource/index.ts +6 -2
- package/js/hooks/useUnionResource/utils/fetchEntity.ts +1 -0
- package/js/pages/DashboardAddressesManagement/DashboardAddressBox.tsx +3 -3
- package/js/pages/DashboardAddressesManagement/DashboardCreateAddress.stories.tsx +1 -1
- package/js/pages/DashboardAddressesManagement/DashboardCreateAddress.tsx +1 -1
- package/js/pages/DashboardAddressesManagement/DashboardEditAddress.stories.tsx +1 -1
- package/js/pages/DashboardAddressesManagement/DashboardEditAddress.tsx +3 -2
- package/js/pages/DashboardAddressesManagement/index.stories.tsx +1 -1
- package/js/pages/DashboardAddressesManagement/index.tsx +1 -1
- package/js/pages/DashboardBatchOrderLayout/index.spec.tsx +19 -2
- package/js/pages/DashboardCourses/index.tsx +2 -1
- package/js/pages/DashboardCreditCardsManagement/CreditCardBrandLogo.stories.tsx +1 -1
- package/js/pages/DashboardCreditCardsManagement/DashboardCreditCardBox.tsx +3 -3
- package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.stories.tsx +1 -1
- package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.tsx +3 -2
- package/js/pages/DashboardCreditCardsManagement/index.stories.tsx +1 -1
- package/js/pages/DashboardOpenEdxProfile/index.stories.tsx +1 -1
- package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +4 -2
- package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +2 -1
- package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +4 -4
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +8 -9
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +14 -3
- package/js/pages/TeacherDashboardCourseLearnersLayout/components/CourseLearnerDataGrid/index.tsx +6 -1
- package/js/pages/TeacherDashboardCourseLoader/CourseRunList/utils.tsx +2 -1
- package/js/pages/TeacherDashboardOrganizationAgreements/BulkAgreementContractButton.tsx +4 -2
- package/js/pages/TeacherDashboardOrganizationAgreements/SignOrganizationAgreementButton.tsx +2 -1
- package/js/pages/TeacherDashboardOrganizationQuotes/BatchOrderSeatInfoQuote.tsx +112 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/_styles.scss +17 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +7 -4
- package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +8 -4
- package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +39 -26
- 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 +22 -2
- package/js/utils/ProductHelper/index.spec.ts +1 -1
- package/js/utils/StorybookHelper/index.tsx +3 -6
- package/js/utils/cunningham-tokens.ts +1111 -142
- package/js/utils/download.ts +3 -1
- package/js/utils/errors/handle.spec.ts +3 -3
- package/js/utils/react-query/useSessionMutation/index.ts +8 -3
- package/js/utils/test/factories/joanie.ts +16 -2
- package/js/widgets/Dashboard/components/DashboardAvatar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardBox/index.stories.tsx +13 -5
- package/js/widgets/Dashboard/components/DashboardBreadcrumbs/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardBreadcrumbs/index.tsx +3 -2
- package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +13 -2
- package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +12 -4
- package/js/widgets/Dashboard/components/DashboardCard/index.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderAgreementInfo.tsx +72 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/index.tsx +2 -1
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderSeatInfo.spec.tsx +114 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderSeatInfo.tsx +133 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/DashboardBatchOrderSubItems.tsx +17 -1
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/batchOrderSeatInfoMessages.ts +24 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.tsx +16 -3
- package/js/widgets/Dashboard/components/DashboardItem/Certificate/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Contract/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.tsx +8 -4
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderReadonly.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderWritable.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentDetailsModal/_styles.scss +2 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal/index.tsx +2 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +6 -3
- package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +6 -2
- package/js/widgets/Dashboard/components/DashboardItem/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardItem/index.tsx +2 -1
- package/js/widgets/Dashboard/components/DashboardListAvatar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/DashboardSidebar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/ProtectedOutlet/AuthenticatedOutlet.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/ProtectedOutlet/ProtectedOutlet.spec.tsx +1 -1
- package/js/widgets/Dashboard/components/SearchBar/index.tsx +2 -1
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/components/TeacherDashboardProfileSidebar/index.stories.tsx +1 -1
- package/js/widgets/Dashboard/hooks/useRouteInfo/index.spec.tsx +2 -2
- package/js/widgets/Dashboard/index.spec.tsx +1 -1
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +2 -2
- package/js/widgets/Search/components/SearchFilterValueParent/index.stories.tsx +1 -1
- package/js/widgets/Search/components/SearchFiltersPane/index.tsx +2 -1
- package/js/widgets/Slider/index.stories.tsx +1 -1
- package/js/widgets/Slider/index.tsx +7 -6
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCertificateItem/index.stories.tsx +1 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.stories.tsx +1 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +1 -1
- package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.tsx +4 -2
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.stories.tsx +1 -1
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.stories.tsx +41 -0
- package/js/widgets/UserLogin/index.stories.tsx +1 -1
- package/package.json +76 -81
- package/scss/components/_subheader.scss +1 -1
- package/scss/components/templates/richie/slider/_slider.scss +1 -1
- package/scss/objects/_course_glimpses.scss +1 -0
- package/scss/objects/_dashboard.scss +77 -0
- package/scss/trumps/_bootstrap.scss +1 -0
- package/scss/vendors/css/cunningham-tokens.css +1259 -154
- package/scss/vendors/cunningham-tokens.scss +1479 -150
- package/tsconfig.json +1 -1
- package/webpack.config.js +8 -0
package/i18n/locales/ru-RU.json
CHANGED
|
@@ -1911,6 +1911,14 @@
|
|
|
1911
1911
|
"description": "Validate button for the credit card modal",
|
|
1912
1912
|
"message": "Validate"
|
|
1913
1913
|
},
|
|
1914
|
+
"components.SaleTunnel.Information.cpf.buttonLabel": {
|
|
1915
|
+
"description": "Label for the button redirecting to Mon Compte Formation",
|
|
1916
|
+
"message": "Go to Mon Compte Formation"
|
|
1917
|
+
},
|
|
1918
|
+
"components.SaleTunnel.Information.cpf.description": {
|
|
1919
|
+
"description": "Explanatory text for the CPF payment option",
|
|
1920
|
+
"message": "Purchase your training course by using your Personal Training Account (CPF) on Mon Compte Formation."
|
|
1921
|
+
},
|
|
1914
1922
|
"components.SaleTunnel.Information.description": {
|
|
1915
1923
|
"description": "Description of the information section",
|
|
1916
1924
|
"message": "This information will be used for billing"
|
|
@@ -1951,6 +1959,18 @@
|
|
|
1951
1959
|
"description": "Message displayed representing the payment schedule when the order is free.",
|
|
1952
1960
|
"message": "No payment required. This order is fully covered."
|
|
1953
1961
|
},
|
|
1962
|
+
"components.SaleTunnel.Information.paymentMode.classic": {
|
|
1963
|
+
"description": "Label for the classic card payment option",
|
|
1964
|
+
"message": "Credit card payment"
|
|
1965
|
+
},
|
|
1966
|
+
"components.SaleTunnel.Information.paymentMode.cpf": {
|
|
1967
|
+
"description": "Label for the CPF (Mon Compte Formation) payment option",
|
|
1968
|
+
"message": "My Training Account (CPF)"
|
|
1969
|
+
},
|
|
1970
|
+
"components.SaleTunnel.Information.paymentMode.title": {
|
|
1971
|
+
"description": "Title for the payment mode selection section",
|
|
1972
|
+
"message": "Payment method"
|
|
1973
|
+
},
|
|
1954
1974
|
"components.SaleTunnel.Information.paymentSchedule": {
|
|
1955
1975
|
"description": "Title for the payment schedule section",
|
|
1956
1976
|
"message": "Payment schedule"
|
package/i18n/locales/vi-VN.json
CHANGED
|
@@ -1911,6 +1911,14 @@
|
|
|
1911
1911
|
"description": "Validate button for the credit card modal",
|
|
1912
1912
|
"message": "Validate"
|
|
1913
1913
|
},
|
|
1914
|
+
"components.SaleTunnel.Information.cpf.buttonLabel": {
|
|
1915
|
+
"description": "Label for the button redirecting to Mon Compte Formation",
|
|
1916
|
+
"message": "Go to Mon Compte Formation"
|
|
1917
|
+
},
|
|
1918
|
+
"components.SaleTunnel.Information.cpf.description": {
|
|
1919
|
+
"description": "Explanatory text for the CPF payment option",
|
|
1920
|
+
"message": "Purchase your training course by using your Personal Training Account (CPF) on Mon Compte Formation."
|
|
1921
|
+
},
|
|
1914
1922
|
"components.SaleTunnel.Information.description": {
|
|
1915
1923
|
"description": "Description of the information section",
|
|
1916
1924
|
"message": "This information will be used for billing"
|
|
@@ -1951,6 +1959,18 @@
|
|
|
1951
1959
|
"description": "Message displayed representing the payment schedule when the order is free.",
|
|
1952
1960
|
"message": "No payment required. This order is fully covered."
|
|
1953
1961
|
},
|
|
1962
|
+
"components.SaleTunnel.Information.paymentMode.classic": {
|
|
1963
|
+
"description": "Label for the classic card payment option",
|
|
1964
|
+
"message": "Credit card payment"
|
|
1965
|
+
},
|
|
1966
|
+
"components.SaleTunnel.Information.paymentMode.cpf": {
|
|
1967
|
+
"description": "Label for the CPF (Mon Compte Formation) payment option",
|
|
1968
|
+
"message": "My Training Account (CPF)"
|
|
1969
|
+
},
|
|
1970
|
+
"components.SaleTunnel.Information.paymentMode.title": {
|
|
1971
|
+
"description": "Title for the payment mode selection section",
|
|
1972
|
+
"message": "Payment method"
|
|
1973
|
+
},
|
|
1954
1974
|
"components.SaleTunnel.Information.paymentSchedule": {
|
|
1955
1975
|
"description": "Title for the payment schedule section",
|
|
1956
1976
|
"message": "Payment schedule"
|
package/jest.config.js
CHANGED
|
@@ -17,6 +17,10 @@ module.exports = {
|
|
|
17
17
|
transformIgnorePatterns: [
|
|
18
18
|
'node_modules/(?!(' +
|
|
19
19
|
'react-intl' +
|
|
20
|
+
'|@formatjs' +
|
|
21
|
+
'|intl-messageformat' +
|
|
22
|
+
'|@tanstack' +
|
|
23
|
+
'|uuid' +
|
|
20
24
|
'|lodash-es' +
|
|
21
25
|
'|@hookform/resolvers' +
|
|
22
26
|
'|query-string' +
|
|
@@ -25,6 +29,7 @@ module.exports = {
|
|
|
25
29
|
'|filter-obj' +
|
|
26
30
|
'|@openfun/cunningham-react' +
|
|
27
31
|
'|keycloak-js' +
|
|
32
|
+
'|@faker-js/faker' +
|
|
28
33
|
')/)',
|
|
29
34
|
],
|
|
30
35
|
globals: {
|
package/js/api/joanie.ts
CHANGED
|
@@ -107,6 +107,10 @@ export const getRoutes = () => {
|
|
|
107
107
|
submit_for_payment: {
|
|
108
108
|
create: `${baseUrl}/batch-orders/:id/submit-for-payment/`,
|
|
109
109
|
},
|
|
110
|
+
seats: {
|
|
111
|
+
get: `${baseUrl}/batch-orders/:batch_order_id/seats/`,
|
|
112
|
+
},
|
|
113
|
+
seats_export: `${baseUrl}/batch-orders/:id/seats-export/`,
|
|
110
114
|
},
|
|
111
115
|
certificates: {
|
|
112
116
|
download: `${baseUrl}/certificates/:id/download/`,
|
|
@@ -161,6 +165,7 @@ export const getRoutes = () => {
|
|
|
161
165
|
},
|
|
162
166
|
agreements: {
|
|
163
167
|
get: `${baseUrl}/organizations/:organization_id/agreements/:id/`,
|
|
168
|
+
download: `${baseUrl}/organizations/:organization_id/agreements/:id/download/`,
|
|
164
169
|
},
|
|
165
170
|
},
|
|
166
171
|
courses: {
|
|
@@ -354,6 +359,17 @@ const API = (): Joanie.API => {
|
|
|
354
359
|
).then(checkStatus);
|
|
355
360
|
},
|
|
356
361
|
},
|
|
362
|
+
seats: {
|
|
363
|
+
get: async (filters?: Joanie.BatchOrderSeatsQueryFilters) => {
|
|
364
|
+
return fetchWithJWT(buildApiUrl(ROUTES.user.batchOrders.seats.get, filters)).then(
|
|
365
|
+
checkStatus,
|
|
366
|
+
);
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
seats_export: async (id: string): Promise<File> =>
|
|
370
|
+
fetchWithJWT(ROUTES.user.batchOrders.seats_export.replace(':id', id))
|
|
371
|
+
.then(checkStatus)
|
|
372
|
+
.then(getFileFromResponse),
|
|
357
373
|
},
|
|
358
374
|
enrollments: {
|
|
359
375
|
create: async (payload) =>
|
|
@@ -545,6 +561,10 @@ const API = (): Joanie.API => {
|
|
|
545
561
|
method: 'GET',
|
|
546
562
|
}).then(checkStatus);
|
|
547
563
|
},
|
|
564
|
+
download: async (filters: { organization_id: string; id: string }): Promise<File> =>
|
|
565
|
+
fetchWithJWT(buildApiUrl(ROUTES.organizations.agreements.download, filters))
|
|
566
|
+
.then(checkStatus)
|
|
567
|
+
.then(getFileFromResponse),
|
|
548
568
|
},
|
|
549
569
|
},
|
|
550
570
|
courses: {
|
package/js/api/utils.ts
CHANGED
|
@@ -16,11 +16,12 @@ export async function getFileFromResponse(response: Response): Promise<File> {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function getResponseBody(response: Response) {
|
|
19
|
-
|
|
19
|
+
const contentType = (response.headers.get('Content-Type') || '').split(';')[0].trim();
|
|
20
|
+
if (contentType === 'application/json') {
|
|
20
21
|
return response.json();
|
|
21
22
|
}
|
|
22
|
-
const fileType = ['application/pdf', 'application/zip'];
|
|
23
|
-
if (fileType.includes(
|
|
23
|
+
const fileType = ['application/pdf', 'application/zip', 'text/csv'];
|
|
24
|
+
if (fileType.includes(contentType)) {
|
|
24
25
|
return new Promise((resolve) => resolve(response));
|
|
25
26
|
}
|
|
26
27
|
return response.text();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { yupResolver } from '@hookform/resolvers/yup';
|
|
2
2
|
import { Fragment, useEffect } from 'react';
|
|
3
|
-
import { FormProvider, useForm } from 'react-hook-form';
|
|
3
|
+
import { FormProvider, Resolver, useForm } from 'react-hook-form';
|
|
4
4
|
import { FormattedMessage, useIntl } from 'react-intl';
|
|
5
5
|
import { Button, Checkbox } from '@openfun/cunningham-react';
|
|
6
6
|
import { getLocalizedCunninghamErrorProp } from 'components/Form/utils';
|
|
@@ -39,7 +39,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
|
|
|
39
39
|
defaultValues: address || defaultValues,
|
|
40
40
|
mode: 'onBlur',
|
|
41
41
|
reValidateMode: 'onChange',
|
|
42
|
-
resolver: yupResolver(validationSchema)
|
|
42
|
+
resolver: yupResolver(validationSchema) as Resolver<AddressFormValues>,
|
|
43
43
|
});
|
|
44
44
|
const { register, handleSubmit, reset, formState } = form;
|
|
45
45
|
|
|
@@ -140,7 +140,8 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
|
|
|
140
140
|
{address ? (
|
|
141
141
|
<Fragment>
|
|
142
142
|
<Button
|
|
143
|
-
color="
|
|
143
|
+
color="brand"
|
|
144
|
+
variant="tertiary"
|
|
144
145
|
onClick={handleCancel}
|
|
145
146
|
title={intl.formatMessage(messages.cancelTitleButton)}
|
|
146
147
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { StorybookHelper } from 'utils/StorybookHelper';
|
|
3
3
|
import { RichieContextFactory } from 'utils/test/factories/richie';
|
|
4
4
|
import AddressesManagement from '.';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Children, useEffect, useState,
|
|
1
|
+
import { Children, useEffect, useState, Ref } 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';
|
|
@@ -105,9 +105,10 @@ export const messages = defineMessages({
|
|
|
105
105
|
},
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
interface AddressesManagementProps
|
|
108
|
+
interface AddressesManagementProps {
|
|
109
109
|
handleClose: () => void;
|
|
110
110
|
selectAddress: (address: Joanie.Address) => void;
|
|
111
|
+
ref?: Ref<HTMLDivElement>;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const AddressesManagement = ({ handleClose, selectAddress, ref }: AddressesManagementProps) => {
|
|
@@ -191,7 +192,8 @@ const AddressesManagement = ({ handleClose, selectAddress, ref }: AddressesManag
|
|
|
191
192
|
<div className="AddressesManagement" ref={ref}>
|
|
192
193
|
<Button
|
|
193
194
|
className="AddressesManagement__closeButton"
|
|
194
|
-
color="
|
|
195
|
+
color="brand"
|
|
196
|
+
variant="tertiary"
|
|
195
197
|
size="small"
|
|
196
198
|
onClick={handleClose}
|
|
197
199
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { CourseLightFactory, RichieContextFactory } from 'utils/test/factories/richie';
|
|
3
3
|
import { CourseGlimpse, getCourseGlimpseProps } from 'components/CourseGlimpse';
|
|
4
4
|
import { CourseCertificateOffer, CourseOffer } from 'types/Course';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { RichieContextFactory, CourseLightFactory } from 'utils/test/factories/richie';
|
|
3
3
|
import { CourseGlimpseList, getCourseGlimpseListProps } from '.';
|
|
4
4
|
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
text-align: left;
|
|
16
16
|
font-size: rem-calc(12px);
|
|
17
17
|
color: r-theme-val(credit-card-selector, title-color);
|
|
18
|
-
font-weight: var(--c--
|
|
19
|
-
font-family: var(--c--
|
|
18
|
+
font-weight: var(--c--globals--font--weights--bold);
|
|
19
|
+
font-family: var(--c--globals--font--families--accent);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
&__meta {
|
|
@@ -111,7 +111,8 @@ export const CreditCardSelector = ({
|
|
|
111
111
|
{allowEdit && creditCards?.length > 0 && (
|
|
112
112
|
<Button
|
|
113
113
|
icon={<span className="material-icons">edit</span>}
|
|
114
|
-
color="
|
|
114
|
+
color="brand"
|
|
115
|
+
variant="tertiary"
|
|
115
116
|
size="medium"
|
|
116
117
|
onClick={modal.open}
|
|
117
118
|
aria-label={intl.formatMessage(messages.editCreditCardAriaLabel)}
|
|
@@ -122,7 +123,8 @@ export const CreditCardSelector = ({
|
|
|
122
123
|
<Button
|
|
123
124
|
onClick={() => setCreditCard(undefined)}
|
|
124
125
|
size="small"
|
|
125
|
-
color="
|
|
126
|
+
color="brand"
|
|
127
|
+
variant="secondary"
|
|
126
128
|
className="mt-t"
|
|
127
129
|
fullWidth={isMobile}
|
|
128
130
|
>
|
|
@@ -217,7 +219,13 @@ const CreditCardSelectorModal = ({
|
|
|
217
219
|
size={ModalSize.MEDIUM}
|
|
218
220
|
title={intl.formatMessage(messages.modalTitle)}
|
|
219
221
|
actions={
|
|
220
|
-
<Button
|
|
222
|
+
<Button
|
|
223
|
+
color="brand"
|
|
224
|
+
variant="primary"
|
|
225
|
+
size="small"
|
|
226
|
+
fullWidth={true}
|
|
227
|
+
onClick={() => onChange(selected)}
|
|
228
|
+
>
|
|
221
229
|
<FormattedMessage {...messages.modalTitle} />
|
|
222
230
|
</Button>
|
|
223
231
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
import { Button } from '@openfun/cunningham-react';
|
|
3
|
+
import { FormattedMessage, defineMessages } from 'react-intl';
|
|
4
|
+
import { Spinner } from 'components/Spinner';
|
|
5
|
+
import { useDownloadAgreement } from 'hooks/useDownloadAgreement';
|
|
6
|
+
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
download: {
|
|
9
|
+
defaultMessage: 'Download agreement',
|
|
10
|
+
description: 'Label for the button to download a signed agreement PDF',
|
|
11
|
+
id: 'components.DownloadAgreementButton.download',
|
|
12
|
+
},
|
|
13
|
+
generating: {
|
|
14
|
+
defaultMessage: 'Downloading...',
|
|
15
|
+
description: 'Accessible label displayed while agreement PDF is being downloaded.',
|
|
16
|
+
id: 'components.DownloadAgreementButton.generating',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
interface DownloadAgreementButtonProps {
|
|
21
|
+
organizationId: string;
|
|
22
|
+
agreementId: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DownloadAgreementButton = ({ organizationId, agreementId }: DownloadAgreementButtonProps) => {
|
|
26
|
+
const { download, loading } = useDownloadAgreement();
|
|
27
|
+
const labelId = useId();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Button
|
|
31
|
+
size="small"
|
|
32
|
+
color="brand"
|
|
33
|
+
variant="primary"
|
|
34
|
+
className="dashboard-item__action-button"
|
|
35
|
+
disabled={loading}
|
|
36
|
+
onClick={() => download(organizationId, agreementId)}
|
|
37
|
+
>
|
|
38
|
+
{loading ? (
|
|
39
|
+
<Spinner theme="primary" aria-labelledby={labelId}>
|
|
40
|
+
<span id={labelId}>
|
|
41
|
+
<FormattedMessage {...messages.generating} />
|
|
42
|
+
</span>
|
|
43
|
+
</Spinner>
|
|
44
|
+
) : (
|
|
45
|
+
<FormattedMessage {...messages.download} />
|
|
46
|
+
)}
|
|
47
|
+
</Button>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default DownloadAgreementButton;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { buildFilename, sanitizeForFilename } from '.';
|
|
2
|
+
|
|
3
|
+
describe('sanitizeForFilename', () => {
|
|
4
|
+
it('replaces spaces with underscores', () => {
|
|
5
|
+
expect(sanitizeForFilename('Formation React')).toBe('Formation_React');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('removes diacritics', () => {
|
|
9
|
+
expect(sanitizeForFilename('Développement web')).toBe('Developpement_web');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('removes special characters', () => {
|
|
13
|
+
expect(sanitizeForFilename('C++ / Python')).toBe('C_Python');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('preserves hyphens', () => {
|
|
17
|
+
expect(sanitizeForFilename('Formation React - Advanced')).toBe('Formation_React_-_Advanced');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('trims leading and trailing spaces', () => {
|
|
21
|
+
expect(sanitizeForFilename(' Formation ')).toBe('Formation');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('buildFilename', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.useFakeTimers();
|
|
28
|
+
jest.setSystemTime(new Date('2026-04-15T09:30:00Z'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
jest.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('builds the expected filename', () => {
|
|
36
|
+
expect(buildFilename('seats', 'Formation React')).toBe(
|
|
37
|
+
'seats_Formation_React_2026-04-15_09-30.csv',
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('sanitizes the product title in the filename', () => {
|
|
42
|
+
expect(buildFilename('seats', 'Développement web avancé')).toBe(
|
|
43
|
+
'seats_Developpement_web_avance_2026-04-15_09-30.csv',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
import { Button } from '@openfun/cunningham-react';
|
|
3
|
+
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { Spinner } from 'components/Spinner';
|
|
5
|
+
import { useDownloadBatchOrderSeats } from 'hooks/useDownloadBatchOrderSeats';
|
|
6
|
+
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
download: {
|
|
9
|
+
defaultMessage: 'Export CSV',
|
|
10
|
+
description: 'Label for the button to export batch order seats as CSV',
|
|
11
|
+
id: 'components.DownloadBatchOrderSeatsButton.download',
|
|
12
|
+
},
|
|
13
|
+
generating: {
|
|
14
|
+
defaultMessage: 'Generating export...',
|
|
15
|
+
description: 'Accessible label displayed while CSV export is being generated.',
|
|
16
|
+
id: 'components.DownloadBatchOrderSeatsButton.generating',
|
|
17
|
+
},
|
|
18
|
+
seats: {
|
|
19
|
+
defaultMessage: 'Seats',
|
|
20
|
+
description: 'Text displayed for seats value in batch order',
|
|
21
|
+
id: 'batchOrder.seats',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const sanitizeForFilename = (str: string) =>
|
|
26
|
+
str
|
|
27
|
+
.normalize('NFD')
|
|
28
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
29
|
+
.replace(/[^a-zA-Z0-9\s-]/g, '')
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/\s+/g, '_');
|
|
32
|
+
|
|
33
|
+
export const buildFilename = (prefix: string, productTitle: string) => {
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const date = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}`;
|
|
36
|
+
const time = `${String(now.getUTCHours()).padStart(2, '0')}-${String(now.getUTCMinutes()).padStart(2, '0')}`;
|
|
37
|
+
return `${prefix}_${sanitizeForFilename(productTitle)}_${date}_${time}.csv`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface DownloadBatchOrderSeatsButtonProps {
|
|
41
|
+
batchOrderId: string;
|
|
42
|
+
productTitle: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DownloadBatchOrderSeatsButton = ({
|
|
46
|
+
batchOrderId,
|
|
47
|
+
productTitle,
|
|
48
|
+
}: DownloadBatchOrderSeatsButtonProps) => {
|
|
49
|
+
const { download, loading } = useDownloadBatchOrderSeats();
|
|
50
|
+
const labelId = useId();
|
|
51
|
+
const intl = useIntl();
|
|
52
|
+
|
|
53
|
+
const handleClick = () => {
|
|
54
|
+
const prefix = intl.formatMessage(messages.seats);
|
|
55
|
+
download(batchOrderId, buildFilename(prefix, productTitle));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Button
|
|
60
|
+
size="small"
|
|
61
|
+
color="brand"
|
|
62
|
+
variant="primary"
|
|
63
|
+
className="dashboard-item__action-button"
|
|
64
|
+
disabled={loading}
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
>
|
|
67
|
+
{loading ? (
|
|
68
|
+
<Spinner theme="primary" aria-labelledby={labelId}>
|
|
69
|
+
<span id={labelId}>
|
|
70
|
+
<FormattedMessage {...messages.generating} />
|
|
71
|
+
</span>
|
|
72
|
+
</Spinner>
|
|
73
|
+
) : (
|
|
74
|
+
<FormattedMessage {...messages.download} />
|
|
75
|
+
)}
|
|
76
|
+
</Button>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default DownloadBatchOrderSeatsButton;
|
|
@@ -39,7 +39,13 @@ const DownloadContractButton = ({ contract, className }: DownloadContractButtonP
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
|
-
<Button
|
|
42
|
+
<Button
|
|
43
|
+
size="small"
|
|
44
|
+
className={className}
|
|
45
|
+
color="brand"
|
|
46
|
+
variant="secondary"
|
|
47
|
+
onClick={downloadContract}
|
|
48
|
+
>
|
|
43
49
|
<FormattedMessage {...messages.contractDownloadActionLabel} />
|
|
44
50
|
</Button>
|
|
45
51
|
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import c from 'classnames';
|
|
2
2
|
import { PropsWithChildren } from 'react';
|
|
3
3
|
|
|
4
|
-
interface FormProps
|
|
5
|
-
|
|
4
|
+
interface FormProps extends React.DetailedHTMLProps<
|
|
5
|
+
React.FormHTMLAttributes<HTMLFormElement>,
|
|
6
|
+
HTMLFormElement
|
|
7
|
+
> {}
|
|
6
8
|
|
|
7
9
|
const Form = ({ children, onSubmit, className, name, noValidate = true }: FormProps) => {
|
|
8
10
|
return (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { PropsWithChildren, useState, useRef, CSSProperties } from 'react';
|
|
3
3
|
import { HttpStatusCode } from 'utils/errors/HttpError';
|
|
4
4
|
import { Icon, IconTypeEnum } from './index';
|
|
@@ -69,6 +69,7 @@ const IconContainer = ({ name, enumKey }: IconContainerProps) => {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
const clipboardCopy = () => {
|
|
72
|
+
// eslint-disable-next-line compat/compat
|
|
72
73
|
navigator.clipboard.writeText(`${ENUM_NAME}.${enumKey}`);
|
|
73
74
|
setShowTooltip(true);
|
|
74
75
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
@@ -78,7 +78,8 @@ export const Modal = ({
|
|
|
78
78
|
className="modal__closeButton"
|
|
79
79
|
onClick={(e) => props.onRequestClose?.(e)}
|
|
80
80
|
title={intl.formatMessage(messages.closeDialog)}
|
|
81
|
-
color="
|
|
81
|
+
color="brand"
|
|
82
|
+
variant="tertiary"
|
|
82
83
|
size="small"
|
|
83
84
|
>
|
|
84
85
|
<Icon name={IconTypeEnum.ROUND_CLOSE} />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ButtonElement, Input, Alert, VariantType } from '@openfun/cunningham-react';
|
|
2
2
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
3
|
-
import { FormProvider, useForm } from 'react-hook-form';
|
|
3
|
+
import { FormProvider, Resolver, useForm } from 'react-hook-form';
|
|
4
4
|
import * as Yup from 'yup';
|
|
5
5
|
import { yupResolver } from '@hookform/resolvers/yup';
|
|
6
6
|
import { useEffect, useMemo, useRef } from 'react';
|
|
@@ -78,7 +78,7 @@ const OpenEdxFullNameForm = () => {
|
|
|
78
78
|
defaultValues,
|
|
79
79
|
mode: 'onBlur',
|
|
80
80
|
reValidateMode: 'onChange',
|
|
81
|
-
resolver: yupResolver(validationSchema)
|
|
81
|
+
resolver: yupResolver(validationSchema) as Resolver<OpenEdxFullNameFormValues>,
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
const { getValues, register, handleSubmit, reset, formState } = form;
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
height: 24px;
|
|
19
19
|
padding: 0 12px;
|
|
20
20
|
border-radius: 24px;
|
|
21
|
-
font-family: var(--c--
|
|
22
|
-
font-weight: var(--c--
|
|
21
|
+
font-family: var(--c--globals--font--families--accent);
|
|
22
|
+
font-weight: var(--c--globals--font--weights--medium);
|
|
23
23
|
font-size: rem-calc(12px);
|
|
24
24
|
|
|
25
25
|
&--canceled,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { StorybookHelper } from 'utils/StorybookHelper';
|
|
3
3
|
import { AddressFactory } from 'utils/test/factories/joanie';
|
|
4
4
|
import RegisteredAddress from '.';
|
|
@@ -91,7 +91,8 @@ const RegisteredAddress = ({ promote, select, edit, remove, address }: Props) =>
|
|
|
91
91
|
</Button>
|
|
92
92
|
<Button
|
|
93
93
|
aria-label={intl.formatMessage(messages.editButtonLabel, { title: address.title })}
|
|
94
|
-
color="
|
|
94
|
+
color="brand"
|
|
95
|
+
variant="secondary"
|
|
95
96
|
size="small"
|
|
96
97
|
onClick={() => edit(address)}
|
|
97
98
|
>
|
|
@@ -101,7 +102,8 @@ const RegisteredAddress = ({ promote, select, edit, remove, address }: Props) =>
|
|
|
101
102
|
aria-label={intl.formatMessage(messages.deleteButtonLabel, {
|
|
102
103
|
title: address.title,
|
|
103
104
|
})}
|
|
104
|
-
color="
|
|
105
|
+
color="brand"
|
|
106
|
+
variant="secondary"
|
|
105
107
|
size="small"
|
|
106
108
|
disabled={address.is_main}
|
|
107
109
|
onClick={() => remove(address)}
|
|
@@ -52,7 +52,7 @@ export const CreateAddressFormModal = (props: AddressFormModalProps) => {
|
|
|
52
52
|
size={ModalSize.MEDIUM}
|
|
53
53
|
title={intl.formatMessage(messages.title)}
|
|
54
54
|
actions={
|
|
55
|
-
<Button color="primary" size="small" onClick={handleSubmit(onSubmit)}>
|
|
55
|
+
<Button color="brand" variant="primary" size="small" onClick={handleSubmit(onSubmit)}>
|
|
56
56
|
<FormattedMessage {...messages.submit} />
|
|
57
57
|
</Button>
|
|
58
58
|
}
|
|
@@ -49,7 +49,7 @@ export const EditAddressFormModal = ({
|
|
|
49
49
|
size={ModalSize.MEDIUM}
|
|
50
50
|
title={intl.formatMessage(messages.title)}
|
|
51
51
|
actions={
|
|
52
|
-
<Button color="primary" size="small" onClick={handleSubmit(onSubmit)}>
|
|
52
|
+
<Button color="brand" variant="primary" size="small" onClick={handleSubmit(onSubmit)}>
|
|
53
53
|
<FormattedMessage {...messages.save} />
|
|
54
54
|
</Button>
|
|
55
55
|
}
|