richie-education 2.8.2 → 2.11.0
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 +7 -0
- package/babel.config.js +6 -1
- package/i18n/locales/ar-SA.json +13 -1
- package/i18n/locales/es-ES.json +13 -1
- package/i18n/locales/fr-CA.json +13 -1
- package/i18n/locales/fr-FR.json +13 -1
- package/i18n/locales/pt-PT.json +13 -1
- package/i18n/locales/ru-RU.json +186 -0
- package/js/common/searchFields/getSuggestionsSection.spec.ts +18 -0
- package/js/common/searchFields/getSuggestionsSection.ts +2 -1
- package/js/common/searchFields/index.tsx +1 -14
- package/js/components/CourseGlimpse/CourseGlimpseFooter.tsx +0 -1
- package/js/components/CourseGlimpse/index.spec.tsx +21 -3
- package/js/components/CourseGlimpse/index.tsx +25 -6
- package/js/components/CourseGlimpseList/index.spec.tsx +0 -1
- package/js/components/CourseGlimpseList/index.tsx +0 -1
- package/js/components/CourseRunEnrollment/index.spec.tsx +110 -86
- package/js/components/CourseRunEnrollment/index.tsx +52 -66
- package/js/components/Form/Inputs.tsx +241 -0
- package/js/components/Form/index.spec.tsx +291 -0
- package/js/components/Form/index.ts +1 -0
- package/js/components/LanguageSelector/index.spec.tsx +0 -1
- package/js/components/LanguageSelector/index.tsx +2 -2
- package/js/components/LtiConsumer/index.spec.tsx +1 -1
- package/js/components/LtiConsumer/index.tsx +3 -3
- package/js/components/Modal/_styles.scss +78 -0
- package/js/components/Modal/index.spec.tsx +53 -0
- package/js/components/Modal/index.tsx +49 -0
- package/js/components/PaginateCourseSearch/index.spec.tsx +0 -1
- package/js/components/PaginateCourseSearch/index.tsx +3 -3
- package/js/components/Root/index.spec.tsx +7 -2
- package/js/components/Root/index.tsx +8 -8
- package/js/components/RootSearchSuggestField/index.spec.tsx +13 -11
- package/js/components/RootSearchSuggestField/index.tsx +1 -1
- package/js/components/Search/index.spec.tsx +122 -48
- package/js/components/Search/index.tsx +29 -8
- package/js/components/SearchFilterGroup/index.spec.tsx +5 -1
- package/js/components/SearchFilterGroup/index.tsx +0 -2
- package/js/components/SearchFilterGroupModal/_styles.scss +9 -48
- package/js/components/SearchFilterGroupModal/index.spec.tsx +434 -227
- package/js/components/SearchFilterGroupModal/index.tsx +182 -97
- package/js/components/SearchFilterValueLeaf/index.spec.tsx +0 -1
- package/js/components/SearchFilterValueLeaf/index.tsx +2 -2
- package/js/components/SearchFilterValueParent/index.spec.tsx +215 -191
- package/js/components/SearchFilterValueParent/index.tsx +23 -22
- package/js/components/SearchFiltersPane/index.spec.tsx +0 -1
- package/js/components/SearchFiltersPane/index.tsx +3 -4
- package/js/components/SearchInput/index.spec.tsx +0 -1
- package/js/components/SearchInput/index.tsx +0 -1
- package/js/components/SearchSuggestField/index.spec.tsx +29 -28
- package/js/components/SearchSuggestField/index.tsx +1 -1
- package/js/components/Spinner/_styles.scss +12 -6
- package/js/components/Spinner/index.tsx +14 -4
- package/js/components/UserLogin/index.spec.tsx +58 -42
- package/js/components/UserLogin/index.tsx +10 -6
- package/js/components/UserMenu/DesktopUserMenu.tsx +5 -5
- package/js/components/UserMenu/MobileUserMenu.tsx +2 -2
- package/js/components/UserMenu/index.spec.tsx +15 -4
- package/js/components/UserMenu/index.tsx +9 -3
- package/js/data/getResourceList/index.ts +7 -4
- package/js/data/useCourseSearch/index.spec.tsx +23 -11
- package/js/data/useCourseSearch/index.ts +20 -13
- package/js/data/useCourseSearchParams/computeNewFilterValue.ts +1 -1
- package/js/data/useCourseSearchParams/index.spec.tsx +2 -2
- package/js/data/useCourseSearchParams/index.ts +1 -1
- package/js/data/useEnrollment/index.spec.tsx +114 -0
- package/js/data/useEnrollment/index.ts +38 -0
- package/js/data/useFilterValue/index.spec.tsx +2 -2
- package/js/data/useHistory/index.spec.tsx +2 -2
- package/js/data/useHistory/index.tsx +2 -2
- package/js/data/useSession/index.spec.tsx +86 -30
- package/js/data/useSession/index.tsx +49 -39
- package/js/data/useSession/no-authentication.spec.tsx +47 -0
- package/js/data/useStaticFilters/index.tsx +1 -1
- package/js/index.tsx +10 -5
- package/js/settings.ts +20 -1
- package/js/translations/ar-SA.json +1 -1
- package/js/translations/es-ES.json +1 -1
- package/js/translations/fr-CA.json +1 -1
- package/js/translations/fr-FR.json +1 -1
- package/js/translations/pt-PT.json +1 -1
- package/js/translations/ru-RU.json +1 -0
- package/js/types/Course.ts +6 -1
- package/js/types/User.ts +0 -5
- package/js/types/api.ts +5 -5
- package/js/types/commonDataProps.ts +1 -1
- package/js/types/filters.ts +1 -1
- package/js/types/index.ts +19 -3
- package/js/{utils/types.ts → types/utils.ts} +0 -0
- package/js/utils/api/authentication.ts +8 -8
- package/js/utils/api/courseEnrollment.ts +17 -14
- package/js/utils/api/lms/base.spec.ts +14 -8
- package/js/utils/api/lms/base.ts +4 -6
- package/js/utils/api/lms/index.spec.ts +12 -13
- package/js/utils/api/lms/index.ts +6 -9
- package/js/utils/api/lms/openedx-hawthorn.spec.ts +23 -28
- package/js/utils/api/lms/openedx-hawthorn.ts +4 -22
- package/js/utils/base64Parser.ts +31 -0
- package/js/utils/context.ts +14 -0
- package/js/utils/errors/handle.spec.ts +9 -9
- package/js/utils/errors/handle.ts +4 -3
- package/js/utils/index.tsx +1 -0
- package/js/utils/react-query/createQueryClient.ts +50 -0
- package/js/utils/react-query/createSessionStoragePersistor/index.spec.ts +125 -0
- package/js/utils/react-query/createSessionStoragePersistor/index.ts +47 -0
- package/js/utils/test/factories.ts +65 -6
- package/js/utils/useCache.ts +19 -9
- package/js/utils/useIntersectionObserver.tsx +48 -0
- package/package.json +44 -42
- package/scss/_main.scss +12 -9
- package/scss/colors/_gradients.scss +27 -0
- package/scss/colors/_palette.scss +31 -0
- package/scss/colors/_schemes.scss +127 -0
- package/scss/{settings/_colors.scss → colors/_theme.scss} +46 -193
- package/scss/components/_subheader.scss +8 -0
- package/scss/components/templates/courses/cms/_course_detail.scss +13 -0
- package/scss/components/templates/richie/glimpse/_glimpse.scss +1 -1
- package/scss/objects/_buttons.scss +26 -0
- package/scss/objects/_course_glimpses.scss +23 -13
- package/scss/objects/_form.scss +328 -0
- package/scss/objects/_selector.scss +1 -0
- package/scss/settings/_variables.scss +10 -2
- package/scss/tools/_buttons.scss +3 -4
- package/scss/tools/_colors.scss +127 -5
- package/scss/tools/_detail.scss +5 -5
- package/scss/tools/_utils.scss +21 -8
- package/webpack.config.js +1 -1
package/.eslintrc.json
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"no-nested-ternary": "warn",
|
|
52
52
|
"no-param-reassign": "off",
|
|
53
53
|
"no-plusplus": "off",
|
|
54
|
+
"no-promise-executor-return": "off",
|
|
54
55
|
"no-prototype-builtins": "off",
|
|
55
56
|
"no-return-assign": "off",
|
|
56
57
|
"no-return-await": "off",
|
|
@@ -60,11 +61,17 @@
|
|
|
60
61
|
"prefer-template": "off",
|
|
61
62
|
"react/button-has-type": "off",
|
|
62
63
|
"react/destructuring-assignment": "off",
|
|
64
|
+
"react/function-component-definition": [
|
|
65
|
+
"error",
|
|
66
|
+
{ "namedComponents": "arrow-function", "unnamedComponents": "arrow-function" }
|
|
67
|
+
],
|
|
63
68
|
"react/jsx-boolean-value": "off",
|
|
64
69
|
"react/jsx-fragments": "off",
|
|
65
70
|
"react/jsx-props-no-spreading": "off",
|
|
71
|
+
"react/jsx-uses-react": "off",
|
|
66
72
|
"react/no-array-index-key": "warn",
|
|
67
73
|
"react/prop-types": "off",
|
|
74
|
+
"react/react-in-jsx-scope": "off",
|
|
68
75
|
"react/require-default-props": "off"
|
|
69
76
|
},
|
|
70
77
|
"settings": {
|
package/babel.config.js
CHANGED
package/i18n/locales/ar-SA.json
CHANGED
|
@@ -103,9 +103,13 @@
|
|
|
103
103
|
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
104
|
"message": "Loading search results..."
|
|
105
105
|
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "Text search requires at least 3 characters. { query } is not long enough to search. Search results will not be affected by this query."
|
|
109
|
+
},
|
|
106
110
|
"components.SearchFilterGroupModal.closeModal": {
|
|
107
111
|
"description": "Text for the button to close the search filters modal",
|
|
108
|
-
"message": "Close"
|
|
112
|
+
"message": "Close modal"
|
|
109
113
|
},
|
|
110
114
|
"components.SearchFilterGroupModal.error": {
|
|
111
115
|
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
@@ -119,6 +123,14 @@
|
|
|
119
123
|
"description": "Placeholder message for the search input in the search filter modal.",
|
|
120
124
|
"message": "Search in { filterName }"
|
|
121
125
|
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Load more results"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "Loading search results..."
|
|
133
|
+
},
|
|
122
134
|
"components.SearchFilterGroupModal.modalTitle": {
|
|
123
135
|
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
124
136
|
"message": "Add filters for {filterName}"
|
package/i18n/locales/es-ES.json
CHANGED
|
@@ -103,9 +103,13 @@
|
|
|
103
103
|
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
104
|
"message": "Cargando resultados de búsqueda..."
|
|
105
105
|
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "La búsqueda de texto requiere al menos 3 caracteres. { query } no es lo suficientemente larga para buscar. Los resultados de la búsqueda no se verán afectados por esta consulta."
|
|
109
|
+
},
|
|
106
110
|
"components.SearchFilterGroupModal.closeModal": {
|
|
107
111
|
"description": "Text for the button to close the search filters modal",
|
|
108
|
-
"message": "Cerrar"
|
|
112
|
+
"message": "Cerrar ventana modal"
|
|
109
113
|
},
|
|
110
114
|
"components.SearchFilterGroupModal.error": {
|
|
111
115
|
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
@@ -119,6 +123,14 @@
|
|
|
119
123
|
"description": "Placeholder message for the search input in the search filter modal.",
|
|
120
124
|
"message": "Buscar en { filterName }"
|
|
121
125
|
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Cargar más resultados"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "Cargando resultados de búsqueda..."
|
|
133
|
+
},
|
|
122
134
|
"components.SearchFilterGroupModal.modalTitle": {
|
|
123
135
|
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
124
136
|
"message": "Añadir filtros para {filterName}"
|
package/i18n/locales/fr-CA.json
CHANGED
|
@@ -103,9 +103,13 @@
|
|
|
103
103
|
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
104
|
"message": "Chargement des résultats de recherche..."
|
|
105
105
|
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "La recherche de texte nécessite au moins 3 caractères. { query } n'est pas assez long. Les résultats de recherche ne seront pas affectés par cette requête."
|
|
109
|
+
},
|
|
106
110
|
"components.SearchFilterGroupModal.closeModal": {
|
|
107
111
|
"description": "Text for the button to close the search filters modal",
|
|
108
|
-
"message": "Fermer"
|
|
112
|
+
"message": "Fermer le modal"
|
|
109
113
|
},
|
|
110
114
|
"components.SearchFilterGroupModal.error": {
|
|
111
115
|
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
@@ -119,6 +123,14 @@
|
|
|
119
123
|
"description": "Placeholder message for the search input in the search filter modal.",
|
|
120
124
|
"message": "Rechercher parmi les { filterName }"
|
|
121
125
|
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Charger plus de résultats"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "Chargement des résultats de recherche..."
|
|
133
|
+
},
|
|
122
134
|
"components.SearchFilterGroupModal.modalTitle": {
|
|
123
135
|
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
124
136
|
"message": "Ajouter des filtres pour {filterName}"
|
package/i18n/locales/fr-FR.json
CHANGED
|
@@ -103,9 +103,13 @@
|
|
|
103
103
|
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
104
|
"message": "Chargement des résultats de recherche..."
|
|
105
105
|
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "La recherche de texte nécessite au moins 3 caractères. { query } n'est pas assez long pour la recherche. Les résultats de recherche ne seront pas affectés par cette requête."
|
|
109
|
+
},
|
|
106
110
|
"components.SearchFilterGroupModal.closeModal": {
|
|
107
111
|
"description": "Text for the button to close the search filters modal",
|
|
108
|
-
"message": "Fermer"
|
|
112
|
+
"message": "Fermer la fenêtre modale"
|
|
109
113
|
},
|
|
110
114
|
"components.SearchFilterGroupModal.error": {
|
|
111
115
|
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
@@ -119,6 +123,14 @@
|
|
|
119
123
|
"description": "Placeholder message for the search input in the search filter modal.",
|
|
120
124
|
"message": "Rechercher parmi les { filterName }"
|
|
121
125
|
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Charger plus de résultats"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "Chargement des résultats de recherche..."
|
|
133
|
+
},
|
|
122
134
|
"components.SearchFilterGroupModal.modalTitle": {
|
|
123
135
|
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
124
136
|
"message": "Ajouter des filtres pour {filterName}"
|
package/i18n/locales/pt-PT.json
CHANGED
|
@@ -103,9 +103,13 @@
|
|
|
103
103
|
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
104
|
"message": "A carregar os resultados da pesquisa..."
|
|
105
105
|
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "A pesquisa de texto requer pelo menos 3 caracteres. { query } não é longo o suficiente para pesquisar. Os resultados da pesquisa não serão alterados por esta consulta."
|
|
109
|
+
},
|
|
106
110
|
"components.SearchFilterGroupModal.closeModal": {
|
|
107
111
|
"description": "Text for the button to close the search filters modal",
|
|
108
|
-
"message": "Fechar"
|
|
112
|
+
"message": "Fechar modal"
|
|
109
113
|
},
|
|
110
114
|
"components.SearchFilterGroupModal.error": {
|
|
111
115
|
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
@@ -119,6 +123,14 @@
|
|
|
119
123
|
"description": "Placeholder message for the search input in the search filter modal.",
|
|
120
124
|
"message": "Pesquisar em { filterName }"
|
|
121
125
|
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Carregar mais resultados"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "A carregar os resultados da pesquisa..."
|
|
133
|
+
},
|
|
122
134
|
"components.SearchFilterGroupModal.modalTitle": {
|
|
123
135
|
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
124
136
|
"message": "Adicionar filtros para {filterName}"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components.CourseGlimpse.cover": {
|
|
3
|
+
"description": "Placeholder text when the course we are glimpsing at is missing a cover image",
|
|
4
|
+
"message": "Обложка"
|
|
5
|
+
},
|
|
6
|
+
"components.CourseGlimpseList.courseCount": {
|
|
7
|
+
"description": "Result count & pagination information for course search. Appears right above search results",
|
|
8
|
+
"message": "Показано от {start, number} до {end, number} на {courseCount, number} {courseCount, plural, one {курсе} few {курсах} many {курсах} other {курсах}} в соответствии с вашим поиском"
|
|
9
|
+
},
|
|
10
|
+
"components.CourseRunEnrollment.enroll": {
|
|
11
|
+
"description": "CTA for users who can enroll in the course run or could enroll if they logged in.",
|
|
12
|
+
"message": "Записаться сейчас"
|
|
13
|
+
},
|
|
14
|
+
"components.CourseRunEnrollment.enrolled": {
|
|
15
|
+
"description": "Help text for users who see the \"Go to course\" CTA on course run enrollment",
|
|
16
|
+
"message": "Вы зачислены на этот курс"
|
|
17
|
+
},
|
|
18
|
+
"components.CourseRunEnrollment.enrollmentClosed": {
|
|
19
|
+
"description": "Help text replacing the CTA on a course run when enrollment is closed.",
|
|
20
|
+
"message": "Регистрация на курс в данный момент закрыта"
|
|
21
|
+
},
|
|
22
|
+
"components.CourseRunEnrollment.enrollmentFailed": {
|
|
23
|
+
"description": "Help text below the \"Enroll now\" CTA when an enrollment attempt has already failed.",
|
|
24
|
+
"message": "Не удалось выполнить запрос на зачисление."
|
|
25
|
+
},
|
|
26
|
+
"components.CourseRunEnrollment.goToCourse": {
|
|
27
|
+
"description": "CTA for users who are already enrolled in a course run.",
|
|
28
|
+
"message": "Перейти к курсу"
|
|
29
|
+
},
|
|
30
|
+
"components.CourseRunEnrollment.loadingInitial": {
|
|
31
|
+
"description": "Accessible text for the initial loading spinner on the course run enrollment button.",
|
|
32
|
+
"message": "Загрузка информации о зачислении..."
|
|
33
|
+
},
|
|
34
|
+
"components.CourseRunEnrollment.loginToEnroll": {
|
|
35
|
+
"description": "Helper text in the enroll button for non logged in users",
|
|
36
|
+
"message": "Войти для записи"
|
|
37
|
+
},
|
|
38
|
+
"components.DesktopUserMenu.menuPurpose": {
|
|
39
|
+
"description": "Accessible label for user menu button",
|
|
40
|
+
"message": "Доступ к настройкам вашего профиля"
|
|
41
|
+
},
|
|
42
|
+
"components.LanguageSelector.currentlySelected": {
|
|
43
|
+
"description": "Accessible hint to mark the currently selected language in the language selector",
|
|
44
|
+
"message": "(сейчас выбрано)"
|
|
45
|
+
},
|
|
46
|
+
"components.LanguageSelector.languages": {
|
|
47
|
+
"description": "Default text for the language selector button when the current language cannot be identified",
|
|
48
|
+
"message": "Языки"
|
|
49
|
+
},
|
|
50
|
+
"components.LanguageSelector.selectLanguage": {
|
|
51
|
+
"description": "Accessible label for the language selector button",
|
|
52
|
+
"message": "Выберите язык:"
|
|
53
|
+
},
|
|
54
|
+
"components.LanguageSelector.switchToLanguage": {
|
|
55
|
+
"description": "Accessible link title for the language switching links in language selector",
|
|
56
|
+
"message": "Переключиться на {language}"
|
|
57
|
+
},
|
|
58
|
+
"components.PaginateCourseSearch.currentlyReadingLastPageN": {
|
|
59
|
+
"description": "Accessibility helper in pagination, shown next to the current page number when it is the last page.",
|
|
60
|
+
"message": "В настоящее время чтение последней страницы {page}"
|
|
61
|
+
},
|
|
62
|
+
"components.PaginateCourseSearch.currentlyReadingPageN": {
|
|
63
|
+
"description": "Accessibility helper in pagination, shown next to the current page number when it is not the last page.",
|
|
64
|
+
"message": "Сейчас чтение страницы {page}"
|
|
65
|
+
},
|
|
66
|
+
"components.PaginateCourseSearch.lastPageN": {
|
|
67
|
+
"description": "Accessibility helper for pagination, added on the last page link.",
|
|
68
|
+
"message": "Последняя страница {page}"
|
|
69
|
+
},
|
|
70
|
+
"components.PaginateCourseSearch.nextPageN": {
|
|
71
|
+
"description": "Accessibility helper for pagination, added on the next page link.",
|
|
72
|
+
"message": "Следующая страница {page}"
|
|
73
|
+
},
|
|
74
|
+
"components.PaginateCourseSearch.pageN": {
|
|
75
|
+
"description": "Accessibility helper for pagination, added on all page links for screen readers,\n only shown next to \"page 1\" visually.",
|
|
76
|
+
"message": "Страница {page}"
|
|
77
|
+
},
|
|
78
|
+
"components.PaginateCourseSearch.pagination": {
|
|
79
|
+
"description": "Label for the pagination navigation in course search results.",
|
|
80
|
+
"message": "Постраничная навигация"
|
|
81
|
+
},
|
|
82
|
+
"components.PaginateCourseSearch.previousPageN": {
|
|
83
|
+
"description": "Accessibility helper for pagination, added on the previous page link.",
|
|
84
|
+
"message": "Предыдущая страница {page}"
|
|
85
|
+
},
|
|
86
|
+
"components.RootSearchSuggestField.searchFieldPlaceholder": {
|
|
87
|
+
"description": "Placeholder text displayed in the search field when it is empty.",
|
|
88
|
+
"message": "Поиск курсов"
|
|
89
|
+
},
|
|
90
|
+
"components.Search.errorMessage": {
|
|
91
|
+
"description": "Error message for Search view when the request to load courses fails",
|
|
92
|
+
"message": "Что-то не так! Курсы не могут быть загружены."
|
|
93
|
+
},
|
|
94
|
+
"components.Search.hideFiltersPane": {
|
|
95
|
+
"description": "Accessibility text for the button/icon that toggles *off* the filters pane on mobile",
|
|
96
|
+
"message": "Скрыть панель фильтров"
|
|
97
|
+
},
|
|
98
|
+
"components.Search.showFiltersPane": {
|
|
99
|
+
"description": "Accessibility text for the button/icon that toggles *on* the filters pane on mobile",
|
|
100
|
+
"message": "Показать панель фильтров"
|
|
101
|
+
},
|
|
102
|
+
"components.Search.spinnerText": {
|
|
103
|
+
"description": "Accessibility text for the spinner while search results are being loaded",
|
|
104
|
+
"message": "Загрузка результатов поиска..."
|
|
105
|
+
},
|
|
106
|
+
"components.Search.textQueryLengthWarning": {
|
|
107
|
+
"description": "Warning message in search results when the text query is not long enough to be used.",
|
|
108
|
+
"message": "Текстовый поиск требует не менее 3 символов. { query } не достаточно длинный, чтобы искать. Этот запрос не повлияет на результаты поиска."
|
|
109
|
+
},
|
|
110
|
+
"components.SearchFilterGroupModal.closeModal": {
|
|
111
|
+
"description": "Text for the button to close the search filters modal",
|
|
112
|
+
"message": "Закрыть модальное окно"
|
|
113
|
+
},
|
|
114
|
+
"components.SearchFilterGroupModal.error": {
|
|
115
|
+
"description": "Error message when the search for more filter value fails in the search filters modal.",
|
|
116
|
+
"message": "Произошла ошибка при поиске {filterName}."
|
|
117
|
+
},
|
|
118
|
+
"components.SearchFilterGroupModal.inputLabel": {
|
|
119
|
+
"description": "Accessible label for the search input in the search filter modal.",
|
|
120
|
+
"message": "Поиск фильтров для добавления"
|
|
121
|
+
},
|
|
122
|
+
"components.SearchFilterGroupModal.inputPlaceholder": {
|
|
123
|
+
"description": "Placeholder message for the search input in the search filter modal.",
|
|
124
|
+
"message": "Искать в { filterName }"
|
|
125
|
+
},
|
|
126
|
+
"components.SearchFilterGroupModal.loadMoreResults": {
|
|
127
|
+
"description": "Button to manually load more results for the current active filter",
|
|
128
|
+
"message": "Загрузить больше результатов"
|
|
129
|
+
},
|
|
130
|
+
"components.SearchFilterGroupModal.loadingResults": {
|
|
131
|
+
"description": "Loading message while loading more results in the search filter modal.",
|
|
132
|
+
"message": "Загрузка результатов поиска..."
|
|
133
|
+
},
|
|
134
|
+
"components.SearchFilterGroupModal.modalTitle": {
|
|
135
|
+
"description": "Title for the modal to add more filter values in the search filters modal.",
|
|
136
|
+
"message": "Добавить фильтры для {filterName}"
|
|
137
|
+
},
|
|
138
|
+
"components.SearchFilterGroupModal.moreOptionsButton": {
|
|
139
|
+
"description": "Test for the button to see more filter values than the top N that appear by default.",
|
|
140
|
+
"message": "Ещё опции"
|
|
141
|
+
},
|
|
142
|
+
"components.SearchFilterGroupModal.queryTooShort": {
|
|
143
|
+
"description": "Users need to enter at least 3 characters to search for more filter values; this message informs them when they start typing.",
|
|
144
|
+
"message": "Введите минимум 3 символа, чтобы начать поиск."
|
|
145
|
+
},
|
|
146
|
+
"components.SearchFilterValueParent.ariaHideChildren": {
|
|
147
|
+
"description": "Accessibility message for the button to hide children of the current filter",
|
|
148
|
+
"message": "Скрыть дополнительные фильтры для {filterValueName}"
|
|
149
|
+
},
|
|
150
|
+
"components.SearchFilterValueParent.ariaShowChildren": {
|
|
151
|
+
"description": "Accessibility message for the button to show children of the current filter",
|
|
152
|
+
"message": "Показать больше фильтров для {filterValueName}"
|
|
153
|
+
},
|
|
154
|
+
"components.SearchFiltersPane.clearFilters": {
|
|
155
|
+
"description": "Helper button in search filters pane in search page to remove all active filters",
|
|
156
|
+
"message": "Очистите {activeFilterCount, number} {activeFilterCount, plural, one {активный фильтр} few {активных фильтра} many {активных фильтров} other {активных фильтров}}"
|
|
157
|
+
},
|
|
158
|
+
"components.SearchFiltersPane.title": {
|
|
159
|
+
"description": "Title for the search filters pane in course search.",
|
|
160
|
+
"message": "Фильтровать курсы"
|
|
161
|
+
},
|
|
162
|
+
"components.SearchInput.button": {
|
|
163
|
+
"description": "Accessibility text for the search button inside the Search input.",
|
|
164
|
+
"message": "Поиск"
|
|
165
|
+
},
|
|
166
|
+
"components.SearchSuggestField.searchFieldPlaceholder": {
|
|
167
|
+
"description": "Placeholder text displayed in the search field when it is empty.",
|
|
168
|
+
"message": "Поиск курсов, организаций, категорий"
|
|
169
|
+
},
|
|
170
|
+
"components.UserLogin.logIn": {
|
|
171
|
+
"description": "Text for the login button.",
|
|
172
|
+
"message": "Вход"
|
|
173
|
+
},
|
|
174
|
+
"components.UserLogin.logOut": {
|
|
175
|
+
"description": "Text for the logout button.",
|
|
176
|
+
"message": "Выход"
|
|
177
|
+
},
|
|
178
|
+
"components.UserLogin.signup": {
|
|
179
|
+
"description": "Text for the signup button.",
|
|
180
|
+
"message": "Зарегистрироваться"
|
|
181
|
+
},
|
|
182
|
+
"components.UserLogin.spinnerText": {
|
|
183
|
+
"description": "Accessibility text for the spinner in the login area.",
|
|
184
|
+
"message": "Загрузка статуса входа..."
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -5,9 +5,11 @@ import { getSuggestionsSection } from './getSuggestionsSection';
|
|
|
5
5
|
|
|
6
6
|
const mockHandle: jest.Mock<typeof handle> = handle as any;
|
|
7
7
|
jest.mock('utils/errors/handle');
|
|
8
|
+
jest.mock('utils/context', () => jest.fn());
|
|
8
9
|
|
|
9
10
|
describe('common/searchFields/getSuggestionsSection', () => {
|
|
10
11
|
afterEach(() => {
|
|
12
|
+
jest.resetAllMocks();
|
|
11
13
|
fetchMock.restore();
|
|
12
14
|
});
|
|
13
15
|
|
|
@@ -53,6 +55,22 @@ describe('common/searchFields/getSuggestionsSection', () => {
|
|
|
53
55
|
);
|
|
54
56
|
});
|
|
55
57
|
|
|
58
|
+
it('reports the error and the explanation when the server returns a 400 error', async () => {
|
|
59
|
+
fetchMock.get('/api/v1.0/courses/autocomplete/?query=error', {
|
|
60
|
+
body: {
|
|
61
|
+
errors: ['Missing autocomplete "query" for request to richie_courses.'],
|
|
62
|
+
},
|
|
63
|
+
status: 400,
|
|
64
|
+
});
|
|
65
|
+
await getSuggestionsSection('courses', 'Courses', 'error');
|
|
66
|
+
expect(mockHandle).toHaveBeenCalledWith(
|
|
67
|
+
new Error('Failed to get list from courses autocomplete : 400'),
|
|
68
|
+
{
|
|
69
|
+
errors: ['Missing autocomplete "query" for request to richie_courses.'],
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
56
74
|
it('reports the error when it receives broken json', async () => {
|
|
57
75
|
fetchMock.get('/api/v1.0/courses/autocomplete/?query=some%20search', 'not json');
|
|
58
76
|
await getSuggestionsSection('courses', 'Courses', 'some search');
|
|
@@ -28,7 +28,8 @@ export const getSuggestionsSection = async (kind: string, title: string, query:
|
|
|
28
28
|
// Fetch treats remote errors (400, 404, 503...) as successes
|
|
29
29
|
// The ok flag is the way to discriminate
|
|
30
30
|
if (!response.ok) {
|
|
31
|
-
|
|
31
|
+
const error = new Error(`Failed to get list from ${kind} autocomplete : ${response.status}`);
|
|
32
|
+
return response.status === 400 ? handle(error, await response.json()) : handle(error);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
let responseData: Suggestion<string>[];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import debounce from 'lodash-es/debounce';
|
|
3
2
|
|
|
4
3
|
import { StaticFilterDefinitions } from 'types/filters';
|
|
@@ -77,17 +76,5 @@ export const getRelevantFilter = (
|
|
|
77
76
|
suggestion: SearchSuggestion,
|
|
78
77
|
) => {
|
|
79
78
|
// Use the `kind` field on the suggestion to pick the relevant filter to update.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// We need a special-case to handle categories until we refactor the API to separate endpoints between
|
|
83
|
-
// kinds of categories
|
|
84
|
-
if (!filter) {
|
|
85
|
-
// Pick the filter to update based on the payload's path: it contains the relevant filter's page path
|
|
86
|
-
// (for eg. a meta-category or the "organizations" root page)
|
|
87
|
-
filter = Object.values(filters).find(
|
|
88
|
-
(fltr) => !!fltr.base_path && String(suggestion.id).substr(2).startsWith(fltr.base_path),
|
|
89
|
-
)!;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return filter;
|
|
79
|
+
return Object.values(filters).find((fltr) => fltr.name === suggestion.kind)!;
|
|
93
80
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import React from 'react';
|
|
3
2
|
import { IntlProvider } from 'react-intl';
|
|
4
3
|
import { CommonDataProps } from 'types/commonDataProps';
|
|
5
4
|
import { ContextFactory } from 'utils/test/factories';
|
|
@@ -21,6 +20,11 @@ describe('components/CourseGlimpse', () => {
|
|
|
21
20
|
icon: null,
|
|
22
21
|
id: '742',
|
|
23
22
|
organization_highlighted: 'Some Organization',
|
|
23
|
+
organization_highlighted_cover_image: {
|
|
24
|
+
sizes: '330px',
|
|
25
|
+
src: '/thumbs/org_small.png',
|
|
26
|
+
srcset: 'some srcset',
|
|
27
|
+
},
|
|
24
28
|
organizations: ['36', '63'],
|
|
25
29
|
state: {
|
|
26
30
|
call_to_action: 'Enroll now',
|
|
@@ -34,7 +38,7 @@ describe('components/CourseGlimpse', () => {
|
|
|
34
38
|
const contextProps: CommonDataProps['context'] = ContextFactory().generate();
|
|
35
39
|
|
|
36
40
|
it('renders a course glimpse with its data', () => {
|
|
37
|
-
render(
|
|
41
|
+
const { container } = render(
|
|
38
42
|
<IntlProvider locale="en">
|
|
39
43
|
<CourseGlimpse
|
|
40
44
|
context={contextProps}
|
|
@@ -55,10 +59,24 @@ describe('components/CourseGlimpse', () => {
|
|
|
55
59
|
screen.getByText('Some Organization');
|
|
56
60
|
// Matches on 'Starts on Mar 14, 2019', date is wrapped with intl <span>
|
|
57
61
|
screen.getByText('Starts on Mar 14, 2019');
|
|
62
|
+
|
|
63
|
+
// Check course logo
|
|
64
|
+
const courseGlipseMedia = container.getElementsByClassName('course-glimpse__media');
|
|
65
|
+
expect(courseGlipseMedia.length).toBe(1);
|
|
66
|
+
const img = courseGlipseMedia[0].firstChild;
|
|
67
|
+
expect(img).toBeInstanceOf(HTMLImageElement);
|
|
58
68
|
// The logo is rendered along with alt text "" as it is decorative and included in a link block
|
|
59
|
-
const img = screen.getByRole('img');
|
|
60
69
|
expect(img).toHaveAttribute('alt', '');
|
|
61
70
|
expect(img).toHaveAttribute('src', '/thumbs/small.png');
|
|
71
|
+
|
|
72
|
+
// Check organization logo
|
|
73
|
+
const orgLogoElement = container.getElementsByClassName('course-glimpse__organization-logo');
|
|
74
|
+
expect(orgLogoElement.length).toBe(1);
|
|
75
|
+
const orgImg = orgLogoElement[0].firstChild;
|
|
76
|
+
expect(orgImg).toBeInstanceOf(HTMLImageElement);
|
|
77
|
+
// The logo is rendered along with alt text "" as it is decorative and included in a link block
|
|
78
|
+
expect(orgImg).toHaveAttribute('alt', '');
|
|
79
|
+
expect(orgImg).toHaveAttribute('src', '/thumbs/org_small.png');
|
|
62
80
|
});
|
|
63
81
|
|
|
64
82
|
it('works when there is no call to action or datetime on the state (eg. an archived course)', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memo } from 'react';
|
|
2
2
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
|
3
3
|
|
|
4
4
|
import { CommonDataProps } from 'types/commonDataProps';
|
|
@@ -20,6 +20,7 @@ const messages = defineMessages({
|
|
|
20
20
|
const CourseGlimpseBase = ({ context, course }: CourseGlimpseProps & CommonDataProps) => (
|
|
21
21
|
<a className="course-glimpse course-glimpse--link" href={course.absolute_url}>
|
|
22
22
|
<div className="course-glimpse__media">
|
|
23
|
+
{/* alt forced to empty string because it's a decorative image */}
|
|
23
24
|
{course.cover_image ? (
|
|
24
25
|
<img
|
|
25
26
|
alt=""
|
|
@@ -36,14 +37,32 @@ const CourseGlimpseBase = ({ context, course }: CourseGlimpseProps & CommonDataP
|
|
|
36
37
|
<div className="course-glimpse__content">
|
|
37
38
|
{course.icon ? (
|
|
38
39
|
<div className="course-glimpse__icon">
|
|
39
|
-
<
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
<span className="category-badge">
|
|
41
|
+
{/* alt forced to empty string because it's a decorative image */}
|
|
42
|
+
<img
|
|
43
|
+
alt=""
|
|
44
|
+
className="category-badge__icon"
|
|
45
|
+
sizes={course.icon.sizes}
|
|
46
|
+
src={course.icon.src}
|
|
47
|
+
srcSet={course.icon.srcset}
|
|
48
|
+
/>
|
|
49
|
+
<span className="category-badge__title">{course.icon.title}</span>
|
|
50
|
+
</span>
|
|
43
51
|
</div>
|
|
44
52
|
) : null}
|
|
45
53
|
<div className="course-glimpse__wrapper">
|
|
46
54
|
<p className="course-glimpse__title">{course.title}</p>
|
|
55
|
+
{course.organization_highlighted_cover_image ? (
|
|
56
|
+
<div className="course-glimpse__organization-logo">
|
|
57
|
+
{/* alt forced to empty string because it's a decorative image */}
|
|
58
|
+
<img
|
|
59
|
+
alt=""
|
|
60
|
+
sizes={course.organization_highlighted_cover_image.sizes}
|
|
61
|
+
src={course.organization_highlighted_cover_image.src}
|
|
62
|
+
srcSet={course.organization_highlighted_cover_image.srcset}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
) : null}
|
|
47
66
|
<div className="course-glimpse__organization">
|
|
48
67
|
<svg aria-hidden={true} role="img" className="icon">
|
|
49
68
|
<use xlinkHref="#icon-org" />
|
|
@@ -68,4 +87,4 @@ const areEqual: (
|
|
|
68
87
|
) => boolean = (prevProps, newProps) =>
|
|
69
88
|
prevProps.context === newProps.context && prevProps.course.id === newProps.course.id;
|
|
70
89
|
|
|
71
|
-
export const CourseGlimpse =
|
|
90
|
+
export const CourseGlimpse = memo(CourseGlimpseBase, areEqual);
|