richie-education 3.3.1-dev9 → 3.3.2-dev10

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.
Files changed (52) hide show
  1. package/i18n/locales/ar-SA.json +29 -13
  2. package/i18n/locales/es-ES.json +29 -13
  3. package/i18n/locales/fa-IR.json +29 -13
  4. package/i18n/locales/fr-CA.json +29 -13
  5. package/i18n/locales/fr-FR.json +29 -13
  6. package/i18n/locales/ko-KR.json +29 -13
  7. package/i18n/locales/pt-PT.json +29 -13
  8. package/i18n/locales/ru-RU.json +29 -13
  9. package/i18n/locales/vi-VN.json +29 -13
  10. package/js/api/auth/keycloak.spec.ts +1 -0
  11. package/js/api/auth/keycloak.ts +5 -1
  12. package/js/api/joanie.ts +20 -0
  13. package/js/api/lms/openedx-fonzie-keycloak.spec.ts +35 -2
  14. package/js/api/lms/openedx-fonzie-keycloak.ts +26 -0
  15. package/js/api/lms/openedx-hawthorn.spec.ts +34 -2
  16. package/js/api/lms/openedx-hawthorn.ts +4 -1
  17. package/js/components/PurchaseButton/index.spec.tsx +12 -0
  18. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +3 -0
  19. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +11 -0
  20. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +141 -52
  21. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +3 -2
  22. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +6 -1
  23. package/js/components/SaleTunnel/index.credential.spec.tsx +108 -1
  24. package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +5 -1
  25. package/js/components/SaleTunnel/index.full-process-b2c.spec.tsx +9 -0
  26. package/js/components/SaleTunnel/index.spec.tsx +122 -3
  27. package/js/hooks/useDeepLink.tsx +21 -0
  28. package/js/pages/DashboardBatchOrders/index.spec.tsx +103 -0
  29. package/js/pages/DashboardKeycloakProfile/index.spec.tsx +77 -0
  30. package/js/pages/DashboardKeycloakProfile/index.tsx +93 -0
  31. package/js/pages/DashboardPreferences/index.spec.tsx +141 -0
  32. package/js/pages/DashboardPreferences/index.tsx +7 -1
  33. package/js/translations/ar-SA.json +1 -1
  34. package/js/translations/es-ES.json +1 -1
  35. package/js/translations/fa-IR.json +1 -1
  36. package/js/translations/fr-CA.json +1 -1
  37. package/js/translations/fr-FR.json +1 -1
  38. package/js/translations/ko-KR.json +1 -1
  39. package/js/translations/pt-PT.json +1 -1
  40. package/js/translations/ru-RU.json +1 -1
  41. package/js/translations/vi-VN.json +1 -1
  42. package/js/types/Joanie.ts +8 -1
  43. package/js/utils/test/factories/joanie.ts +8 -2
  44. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +15 -27
  45. package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +7 -12
  46. package/js/widgets/Dashboard/index.spec.tsx +4 -3
  47. package/js/widgets/SyllabusCourseRunsList/components/SyllabusAsideList/index.tsx +8 -27
  48. package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +41 -17
  49. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +37 -4
  50. package/js/widgets/cunningham-fr-FR-locale.json +80 -0
  51. package/js/widgets/index.tsx +6 -1
  52. package/package.json +2 -1
@@ -34,13 +34,18 @@ const messages = defineMessages({
34
34
  selfPaceRunPeriod: {
35
35
  id: 'components.SyllabusCourseRunCompacted.selfPaceCoursePeriod',
36
36
  description: 'Course date of an opened and self paced course run block',
37
- defaultMessage: 'Available until {endDate}',
37
+ defaultMessage: 'Until {endDate, select, undefined {} other {{endDate}}}',
38
38
  },
39
39
  selfPaceNoEndDate: {
40
40
  id: 'components.SyllabusCourseRunCompacted.selfPaceNoEndDate',
41
41
  description: 'Self paced course run block with no end date',
42
42
  defaultMessage: 'Available',
43
43
  },
44
+ enrollment: {
45
+ id: 'components.SyllabusCourseRun.enrollment',
46
+ description: 'Title of the enrollment dates section of an opened course run block',
47
+ defaultMessage: 'Enrollment',
48
+ },
44
49
  coursePrice: {
45
50
  id: 'components.SyllabusCourseRunCompacted.coursePrice',
46
51
  description: 'Title of the course enrollment price section of an opened course run block',
@@ -105,6 +110,7 @@ const OpenedSelfPacedCourseRun = ({
105
110
  let enrollmentDiscountedPrice = '';
106
111
  let certificatePrice = '';
107
112
  let certificateDiscountedPrice = '';
113
+ const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
108
114
 
109
115
  if (courseRun.offer) {
110
116
  const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
@@ -157,23 +163,41 @@ const OpenedSelfPacedCourseRun = ({
157
163
  <>
158
164
  {courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
159
165
  <dl>
160
- {!showLanguages && (
161
- <dt>
162
- <FormattedMessage {...messages.course} />
163
- </dt>
166
+ {hasEndDate ? (
167
+ <>
168
+ <dt>
169
+ <FormattedMessage {...messages.enrollment} />
170
+ </dt>
171
+ <dd>
172
+ <FormattedMessage
173
+ {...messages.selfPaceRunPeriod}
174
+ values={{
175
+ endDate: enrollmentEnd,
176
+ }}
177
+ />
178
+ </dd>
179
+ <dt>
180
+ <FormattedMessage {...messages.course} />
181
+ </dt>
182
+ <dd>
183
+ <FormattedMessage
184
+ {...messages.selfPaceRunPeriod}
185
+ values={{
186
+ endDate: end,
187
+ }}
188
+ />
189
+ </dd>
190
+ </>
191
+ ) : (
192
+ <>
193
+ <dt>
194
+ <FormattedMessage {...messages.enrollment} />
195
+ </dt>
196
+ <dd>
197
+ <FormattedMessage {...messages.selfPaceNoEndDate} />
198
+ </dd>
199
+ </>
164
200
  )}
165
- <dd>
166
- {hasEndDate ? (
167
- <FormattedMessage
168
- {...messages.selfPaceRunPeriod}
169
- values={{
170
- endDate: end,
171
- }}
172
- />
173
- ) : (
174
- <FormattedMessage {...messages.selfPaceNoEndDate} />
175
- )}
176
- </dd>
177
201
  {!showLanguages && (
178
202
  <>
179
203
  <dt>
@@ -172,7 +172,7 @@ describe('<SyllabusCourseRunsList/>', () => {
172
172
  });
173
173
  const runContainer = heading.parentNode! as HTMLElement;
174
174
  const courseDatesText = courseRun.end
175
- ? `Available until ${intl.formatDate(new Date(courseRun.end), DEFAULT_DATE_FORMAT)}`
175
+ ? `Until ${intl.formatDate(new Date(courseRun.end), DEFAULT_DATE_FORMAT)}`
176
176
  : `Available`;
177
177
 
178
178
  const courseDatesContainer = getByText(runContainer, courseDatesText);
@@ -221,7 +221,7 @@ describe('<SyllabusCourseRunsList/>', () => {
221
221
  const expectEmptyPortalContainer = () => {
222
222
  // This way of testing is a bit hard but we are SURE that there is no other content. This way
223
223
  // we are also testing the absence of other elements.
224
- expect(getPortalContainer().textContent).toEqual('Other course runsNo other course runs');
224
+ expect(getPortalContainer().textContent).toContain('Other course runs');
225
225
  };
226
226
 
227
227
  it('has no opened course run', async () => {
@@ -390,10 +390,13 @@ describe('<SyllabusCourseRunsList/>', () => {
390
390
  const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
391
391
  resource_link: resourceLink,
392
392
  }).one();
393
+ const archivedCourseRun = CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
394
+ resource_link: resourceLink,
395
+ }).one();
393
396
 
394
397
  render(
395
398
  <SyllabusCourseRunsList
396
- courseRuns={[courseRun]}
399
+ courseRuns={[courseRun, archivedCourseRun]}
397
400
  course={course}
398
401
  maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
399
402
  />,
@@ -412,12 +415,42 @@ describe('<SyllabusCourseRunsList/>', () => {
412
415
  expectEmptyPortalContainer();
413
416
  });
414
417
 
418
+ it('has only one opened product', async () => {
419
+ const course = PacedCourseFactory().one();
420
+ const offering = OfferingFactory().one();
421
+ const resourceLink = `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${offering.product.id}/`;
422
+ fetchMock.get(resourceLink, offering);
423
+
424
+ const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
425
+ resource_link: resourceLink,
426
+ }).one();
427
+
428
+ render(
429
+ <SyllabusCourseRunsList
430
+ courseRuns={[courseRun]}
431
+ course={course}
432
+ maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
433
+ />,
434
+ {
435
+ queryOptions: { client: createTestQueryClient({ user: null }) },
436
+ },
437
+ );
438
+
439
+ // Header.
440
+ expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
441
+ 1,
442
+ );
443
+ await expectCourseProduct(getHeaderContainer(), offering);
444
+ });
445
+
415
446
  it('renders a specific title in portal when there is one opened course run', () => {
416
447
  const course = PacedCourseFactory().one();
448
+ const ongoingOpenCourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one();
449
+ const archivedCourseRun = CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)().one();
417
450
 
418
451
  render(
419
452
  <SyllabusCourseRunsList
420
- courseRuns={[CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one()]}
453
+ courseRuns={[ongoingOpenCourseRun, archivedCourseRun]}
421
454
  course={course}
422
455
  maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
423
456
  />,
@@ -0,0 +1,80 @@
1
+ {
2
+ "components": {
3
+ "alert": {
4
+ "close_aria_label": "Supprimer l'alerte",
5
+ "expand_aria_label": "Ouvrir l'alerte",
6
+ "shrink_aria_label": "Fermer l'alerte"
7
+ },
8
+ "pagination": {
9
+ "goto_label": "Aller à",
10
+ "goto_label_aria": "Aller à la page",
11
+ "next_aria": "Aller à la page suivante",
12
+ "previous_aria": "Aller à la page précédente",
13
+ "goto_page_aria": "Aller à la page {page}",
14
+ "current_page_aria": "Vous êtes actuellement sur la page {page}"
15
+ },
16
+ "datagrid": {
17
+ "empty": "Ce tableau est vide",
18
+ "empty_alt": "Illustration d'un tableau vide",
19
+ "loader_aria": "Chargement des données",
20
+ "rows_selection_aria": "Toutes les lignes sélectionnées",
21
+ "row_selection_aria": "Ligne sélectionnée"
22
+ },
23
+ "provider": {
24
+ "test": "Ceci est un test : {name}"
25
+ },
26
+ "forms": {
27
+ "input": {
28
+ "password": {
29
+ "show_password": "Afficher le mot de passe",
30
+ "hide_password": "Masquer le mot de passe"
31
+ }
32
+ },
33
+ "select": {
34
+ "toggle_button_aria_label": "Ouvrir le menu",
35
+ "clear_button_aria_label": "Effacer la sélection",
36
+ "clear_all_button_aria_label": "Effacer toutes les sélections",
37
+ "menu_empty_placeholder": "Aucun choix disponible"
38
+ },
39
+ "file_uploader": {
40
+ "delete_file_name": "Supprimer le fichier {name}",
41
+ "delete_file": "Supprimer le fichier",
42
+ "uploading": "Upload en cours ...",
43
+ "caption": "Glisser-déposer ou ",
44
+ "browse_files": "Parcourir"
45
+ },
46
+ "date_picker": {
47
+ "toggle_button_aria_label_open": "Ouvrir le calendrier",
48
+ "toggle_button_aria_label_close": "Fermer le calendrier",
49
+ "clear_button_aria_label": "Effacer la date",
50
+ "next_month_button_aria_label": "Mois suivant",
51
+ "next_year_button_aria_label": "Année suivante",
52
+ "previous_month_button_aria_label": "Mois précédent",
53
+ "previous_year_button_aria_label": "Année précédente",
54
+ "year_select_button_aria_label": "Sélectionner une année",
55
+ "month_select_button_aria_label": "Sélectionner un mois"
56
+ }
57
+ },
58
+ "modals": {
59
+ "helpers": {
60
+ "delete_confirmation": {
61
+ "title": "Êtes-vous sûr ?",
62
+ "children": "Êtes-vous sûr de vouloir supprimer cet élément ?",
63
+ "cancel": "Annuler",
64
+ "delete": "Supprimer"
65
+ },
66
+ "confirmation": {
67
+ "title": "Êtes-vous sûr ?",
68
+ "children": "Êtes-vous sûr de vouloir faire cela ?",
69
+ "cancel": "Annuler",
70
+ "yes": "Oui"
71
+ },
72
+ "disclaimer": {
73
+ "title": "Avertissement",
74
+ "children": "Ceci est un avertissement",
75
+ "ok": "Ok"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
@@ -4,6 +4,7 @@ import startCase from 'lodash-es/startCase';
4
4
  import { lazy, Suspense } from 'react';
5
5
  import ReactDOM from 'react-dom';
6
6
  import { CunninghamProvider } from '@openfun/cunningham-react';
7
+ import cunninghamFrFRLocale from 'widgets/cunningham-fr-FR-locale.json';
7
8
  import { HistoryProvider } from 'hooks/useHistory';
8
9
  import { SessionProvider } from 'contexts/SessionContext';
9
10
  import { Spinner } from 'components/Spinner';
@@ -105,7 +106,11 @@ export const Root = ({ richieReactSpots, locale = 'en-US' }: RootProps) => {
105
106
  });
106
107
 
107
108
  return (
108
- <CunninghamProvider currentLocale={locale}>
109
+ <CunninghamProvider
110
+ currentLocale={locale}
111
+ // TODO: remove customLocales and the cunningham-fr-FR-locale.json file once Cunningham is upgraded to v4+
112
+ customLocales={{ 'fr-FR': cunninghamFrFRLocale }}
113
+ >
109
114
  <SessionProvider>
110
115
  <HistoryProvider>
111
116
  <Suspense fallback={<Spinner />}>{portals}</Suspense>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "3.3.1-dev9",
3
+ "version": "3.3.2-dev10",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {
@@ -9,6 +9,7 @@
9
9
  "extract-translations": "formatjs extract './**/*.ts*' --ignore ./node_modules --ignore './**/*.d.ts' --out-file './i18n/frontend.json' --id-interpolation-pattern '[sha512:contenthash:base64:6]' --format crowdin",
10
10
  "compile-translations": "./i18n/compile-translations.js",
11
11
  "lint": "eslint . 'js/**/*.ts?(x)'",
12
+ "prettier-check": "prettier --check 'js/**/*.+(ts|tsx|json|js|jsx)' '*.+(ts|tsx|json|js|jsx)' '**/*.+(css|scss)'",
12
13
  "prettier-write": "prettier --write 'js/**/*.+(ts|tsx|json|js|jsx)' '*.+(ts|tsx|json|js|jsx)' '**/*.+(css|scss)'",
13
14
  "build-sass": "sass scss/_main.scss ../richie/static/richie/css/main.css --load-path=node_modules",
14
15
  "build-sass-production": "sass scss/_main.scss ../richie/static/richie/css/main.css --style=compressed --load-path=node_modules",