richie-education 2.25.0-b2.dev176 → 2.25.0-b2.dev183

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 (31) hide show
  1. package/js/api/joanie.spec.ts +2 -1
  2. package/js/api/joanie.ts +1 -51
  3. package/js/api/lms/dummy.ts +22 -0
  4. package/js/api/lms/openedx-fonzie.spec.ts +20 -0
  5. package/js/api/lms/openedx-fonzie.ts +35 -0
  6. package/js/api/utils.ts +51 -0
  7. package/js/components/AddressesManagement/AddressForm/index.tsx +15 -14
  8. package/js/components/Form/Form/index.tsx +23 -0
  9. package/js/components/Form/index.ts +3 -0
  10. package/js/hooks/useDashboardAddressForm.tsx +13 -12
  11. package/js/hooks/useOpenEdxProfile/index.ts +45 -0
  12. package/js/hooks/useOpenEdxProfile/utils/index.spec.ts +69 -0
  13. package/js/hooks/useOpenEdxProfile/utils/index.ts +131 -0
  14. package/js/pages/DashboardAddressesManagement/_styles.scss +1 -1
  15. package/js/pages/DashboardAddressesManagement/index.spec.tsx +34 -12
  16. package/js/pages/DashboardAddressesManagement/index.tsx +14 -9
  17. package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.spec.tsx +156 -115
  18. package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.tsx +9 -8
  19. package/js/pages/DashboardCreditCardsManagement/index.spec.tsx +92 -121
  20. package/js/pages/DashboardOpenEdxProfile/index.spec.tsx +131 -0
  21. package/js/pages/DashboardOpenEdxProfile/index.tsx +237 -0
  22. package/js/pages/DashboardPreferences/index.tsx +2 -0
  23. package/js/types/api.ts +6 -1
  24. package/js/types/openEdx.ts +41 -0
  25. package/js/utils/react-query/useSessionMutation/index.spec.tsx +1 -1
  26. package/js/utils/react-query/useSessionQuery/index.spec.tsx +1 -1
  27. package/js/utils/test/factories/openEdx.tsx +23 -0
  28. package/js/widgets/Dashboard/components/DashboardBox/_styles.scss +6 -0
  29. package/js/widgets/Dashboard/components/DashboardBox/index.tsx +4 -0
  30. package/package.json +1 -1
  31. package/scss/objects/_form.scss +9 -2
@@ -0,0 +1,131 @@
1
+ import { IntlShape, defineMessages } from 'react-intl';
2
+ import countries from 'i18n-iso-countries';
3
+ import { OpenEdxGender, OpenEdxLevelOfEducation, OpenEdxApiProfile } from 'types/openEdx';
4
+ import { Maybe } from 'types/utils';
5
+
6
+ export const levelOfEducationMessages = defineMessages<OpenEdxLevelOfEducation>({
7
+ [OpenEdxLevelOfEducation.MASTER_OR_PROFESSIONNAL_DEGREE]: {
8
+ id: 'openEdxProfile.levelOfEducation.masterOrProfessionnalDegree',
9
+ description:
10
+ 'Translation for level of education "master or professional degree" in openEdx profile',
11
+ defaultMessage: 'Master',
12
+ },
13
+ [OpenEdxLevelOfEducation.PHD_OR_DOCTORATE]: {
14
+ id: 'openEdxProfile.levelOfEducation.phdOrDoctorate',
15
+ description: 'Translation for level of education "phd or doctorate" in openEdx profile',
16
+ defaultMessage: 'PHD',
17
+ },
18
+ [OpenEdxLevelOfEducation.BACHELOR_DEGREE]: {
19
+ id: 'openEdxProfile.levelOfEducation.bachelorDegree',
20
+ description: 'Translation for level of education "bachelor degree" in openEdx profile',
21
+ defaultMessage: 'Bachelor degree',
22
+ },
23
+ [OpenEdxLevelOfEducation.ASSOCIATE_DEGREE]: {
24
+ id: 'openEdxProfile.levelOfEducation.associateDegree',
25
+ description: 'Translation for level of education "associate degree" in openEdx profile',
26
+ defaultMessage: 'Associate degree',
27
+ },
28
+ [OpenEdxLevelOfEducation.SECONDARY_OR_HIGH_SCHOOL]: {
29
+ id: 'openEdxProfile.levelOfEducation.secondaryOrHighSchool',
30
+ description: 'Translation for level of education "secondary or high school" in openEdx profile',
31
+ defaultMessage: 'Secondary or high school',
32
+ },
33
+ [OpenEdxLevelOfEducation.JUNIOR_SECONDARY_OR_MIDDLE_SCHOOL]: {
34
+ id: 'openEdxProfile.levelOfEducation.juniorSecondaryOrMiddleSchool',
35
+ description:
36
+ 'Translation for level of education "junior secondary or middle school" in openEdx profile',
37
+ defaultMessage: 'Junior secondary or middle school',
38
+ },
39
+ [OpenEdxLevelOfEducation.ELEMENTARY_PRIMARY_SCHOOL]: {
40
+ id: 'openEdxProfile.levelOfEducation.elementaryPrimarySchool',
41
+ description:
42
+ 'Translation for level of education "elementary primary school" in openEdx profile',
43
+ defaultMessage: 'Elementary primary school',
44
+ },
45
+ [OpenEdxLevelOfEducation.NONE]: {
46
+ id: 'openEdxProfile.levelOfEducation.none',
47
+ description: 'Translation for level of education "none" in openEdx profile',
48
+ defaultMessage: 'None',
49
+ },
50
+ [OpenEdxLevelOfEducation.OTHER]: {
51
+ id: 'openEdxProfile.levelOfEducation.other',
52
+ description: 'Translation for level of education "other" in openEdx profile',
53
+ defaultMessage: 'Other',
54
+ },
55
+ });
56
+
57
+ export const genderMessages = defineMessages<OpenEdxGender>({
58
+ [OpenEdxGender.MALE]: {
59
+ id: 'openEdxProfile.gender.male',
60
+ description: 'Translation for gender "male" in openEdx profile',
61
+ defaultMessage: 'Male',
62
+ },
63
+ [OpenEdxGender.FEMALE]: {
64
+ id: 'openEdxProfile.gender.female',
65
+ description: 'Translation for gender "female" in openEdx profile',
66
+ defaultMessage: 'Female',
67
+ },
68
+ [OpenEdxGender.OTHER]: {
69
+ id: 'openEdxProfile.gender.other',
70
+ description: 'Translation for gender "other" in openEdx profile',
71
+ defaultMessage: 'Other',
72
+ },
73
+ });
74
+
75
+ export interface OpenEdxProfile {
76
+ username: Maybe<string>;
77
+ name: Maybe<string>;
78
+ country: Maybe<string>;
79
+ yearOfBirth: Maybe<string>;
80
+ levelOfEducation: Maybe<string>;
81
+ email: Maybe<string>;
82
+ dateJoined: Maybe<string>;
83
+ gender: Maybe<string>;
84
+ language: Maybe<string>;
85
+ favoriteLanguage: Maybe<string>;
86
+ }
87
+
88
+ export const parseOpenEdxApiProfile = (
89
+ intl: IntlShape,
90
+ data?: OpenEdxApiProfile,
91
+ ): OpenEdxProfile => {
92
+ const [languageCode] = intl.locale.split('-');
93
+ const defaultValues: OpenEdxProfile = {
94
+ username: ' - ',
95
+ name: ' - ',
96
+ email: ' - ',
97
+ language: ' - ',
98
+ country: ' - ',
99
+ levelOfEducation: ' - ',
100
+ gender: ' - ',
101
+ yearOfBirth: ' - ',
102
+ favoriteLanguage: ' - ',
103
+ dateJoined: undefined,
104
+ };
105
+
106
+ const languageNames = new Intl.DisplayNames([intl.locale], { type: 'language' });
107
+ const parsedData = data
108
+ ? {
109
+ username: data.username || defaultValues.username,
110
+ name: data.name || defaultValues.name,
111
+ email: data.email || defaultValues.email,
112
+ yearOfBirth: data.year_of_birth || defaultValues.yearOfBirth,
113
+ dateJoined: data.date_joined || defaultValues.dateJoined,
114
+ levelOfEducation: data.level_of_education
115
+ ? intl.formatMessage(levelOfEducationMessages[data.level_of_education])
116
+ : defaultValues.levelOfEducation,
117
+ gender: data.gender
118
+ ? intl.formatMessage(genderMessages[data.gender])
119
+ : defaultValues.gender,
120
+ country: data.country
121
+ ? countries.getName(data.country, languageCode)
122
+ : defaultValues.country,
123
+ language: data['pref-lang'] ? languageNames.of(data['pref-lang']) : defaultValues.language,
124
+ favoriteLanguage: data.language_proficiencies.length
125
+ ? languageNames.of(data.language_proficiencies[0].code)
126
+ : defaultValues.favoriteLanguage,
127
+ }
128
+ : defaultValues;
129
+
130
+ return parsedData;
131
+ };
@@ -1,7 +1,7 @@
1
1
  .dashboard-addresses {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- gap: 2rem;
4
+ gap: rem-calc(32px);
5
5
 
6
6
  &__empty {
7
7
  text-align: center;
@@ -11,7 +11,10 @@ import {
11
11
  import { IntlProvider } from 'react-intl';
12
12
  import { QueryClientProvider } from '@tanstack/react-query';
13
13
  import fetchMock from 'fetch-mock';
14
- import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
14
+ import {
15
+ UserFactory,
16
+ RichieContextFactory as mockRichieContextFactory,
17
+ } from 'utils/test/factories/richie';
15
18
  import { AddressFactory } from 'utils/test/factories/joanie';
16
19
  import { SessionProvider } from 'contexts/SessionContext';
17
20
  import { DashboardTest } from 'widgets/Dashboard/components/DashboardTest';
@@ -21,13 +24,15 @@ import { resolveAll } from 'utils/resolveAll';
21
24
  import { createTestQueryClient } from 'utils/test/createTestQueryClient';
22
25
  import { expectBannerError } from 'utils/test/expectBanner';
23
26
  import { HttpStatusCode } from 'utils/errors/HttpError';
27
+ import { User } from 'types/User';
28
+ import { OpenEdxApiProfileFactory } from 'utils/test/factories/openEdx';
24
29
 
25
30
  import { LearnerDashboardPaths } from 'widgets/Dashboard/utils/learnerRoutesPaths';
26
31
 
27
32
  jest.mock('utils/context', () => ({
28
33
  __esModule: true,
29
34
  default: mockRichieContextFactory({
30
- authentication: { backend: 'fonzie', endpoint: 'https://demo.endpoint' },
35
+ authentication: { backend: 'fonzie', endpoint: 'https://endpoint.test' },
31
36
  joanie_backend: { endpoint: 'https://joanie.endpoint' },
32
37
  }).one(),
33
38
  }));
@@ -37,7 +42,24 @@ jest.mock('utils/indirection/window', () => ({
37
42
  }));
38
43
 
39
44
  describe('<DashAddressesManagement/>', () => {
45
+ let richieUser: User;
40
46
  beforeEach(() => {
47
+ richieUser = UserFactory().one();
48
+ const openEdxProfile = OpenEdxApiProfileFactory({
49
+ username: richieUser.username,
50
+ email: richieUser.email,
51
+ name: richieUser.full_name,
52
+ }).one();
53
+ const { 'pref-lang': prefLang, ...openEdxAccount } = openEdxProfile;
54
+
55
+ fetchMock.get(
56
+ `https://endpoint.test/api/user/v1/accounts/${richieUser.username}`,
57
+ openEdxAccount,
58
+ );
59
+ fetchMock.get(`https://endpoint.test/api/user/v1/preferences/${richieUser.username}`, {
60
+ 'pref-lang': prefLang,
61
+ });
62
+
41
63
  fetchMock.get('https://joanie.endpoint/api/v1.0/orders/', []);
42
64
  fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', []);
43
65
  });
@@ -51,7 +73,7 @@ describe('<DashAddressesManagement/>', () => {
51
73
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', []);
52
74
  await act(async () => {
53
75
  render(
54
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
76
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
55
77
  <IntlProvider locale="en">
56
78
  <SessionProvider>
57
79
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -73,7 +95,7 @@ describe('<DashAddressesManagement/>', () => {
73
95
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
74
96
  await act(async () => {
75
97
  render(
76
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
98
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
77
99
  <IntlProvider locale="en">
78
100
  <SessionProvider>
79
101
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -96,7 +118,7 @@ describe('<DashAddressesManagement/>', () => {
96
118
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
97
119
  await act(async () => {
98
120
  render(
99
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
121
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
100
122
  <IntlProvider locale="en">
101
123
  <SessionProvider>
102
124
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -142,7 +164,7 @@ describe('<DashAddressesManagement/>', () => {
142
164
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
143
165
  await act(async () => {
144
166
  render(
145
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
167
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
146
168
  <IntlProvider locale="en">
147
169
  <SessionProvider>
148
170
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -194,7 +216,7 @@ describe('<DashAddressesManagement/>', () => {
194
216
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
195
217
  await act(async () => {
196
218
  render(
197
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
219
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
198
220
  <IntlProvider locale="en">
199
221
  <SessionProvider>
200
222
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -218,7 +240,7 @@ describe('<DashAddressesManagement/>', () => {
218
240
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
219
241
  await act(async () => {
220
242
  render(
221
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
243
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
222
244
  <IntlProvider locale="en">
223
245
  <SessionProvider>
224
246
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -245,7 +267,7 @@ describe('<DashAddressesManagement/>', () => {
245
267
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', addresses);
246
268
  await act(async () => {
247
269
  render(
248
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
270
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
249
271
  <IntlProvider locale="en">
250
272
  <SessionProvider>
251
273
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -270,7 +292,7 @@ describe('<DashAddressesManagement/>', () => {
270
292
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', []);
271
293
  await act(async () => {
272
294
  render(
273
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
295
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
274
296
  <IntlProvider locale="en">
275
297
  <SessionProvider>
276
298
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -293,7 +315,7 @@ describe('<DashAddressesManagement/>', () => {
293
315
  fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', [address]);
294
316
  await act(async () => {
295
317
  render(
296
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
318
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
297
319
  <IntlProvider locale="en">
298
320
  <SessionProvider>
299
321
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -323,7 +345,7 @@ describe('<DashAddressesManagement/>', () => {
323
345
 
324
346
  await act(async () => {
325
347
  render(
326
- <QueryClientProvider client={createTestQueryClient({ user: true })}>
348
+ <QueryClientProvider client={createTestQueryClient({ user: richieUser })}>
327
349
  <IntlProvider locale="en">
328
350
  <SessionProvider>
329
351
  <DashboardTest initialRoute={LearnerDashboardPaths.PREFERENCES} />
@@ -7,6 +7,7 @@ import { Icon, IconTypeEnum } from 'components/Icon';
7
7
  import { Spinner } from 'components/Spinner';
8
8
  import { useAddressesManagement } from 'hooks/useAddressesManagement';
9
9
  import { Address } from 'types/Joanie';
10
+ import { DashboardBox } from 'widgets/Dashboard/components/DashboardBox';
10
11
  import { DashboardAddressBox } from './DashboardAddressBox';
11
12
 
12
13
  const messages = defineMessages({
@@ -72,15 +73,19 @@ export const DashboardAddressesManagement = ({
72
73
  <FormattedMessage {...messages.emptyList} />
73
74
  </p>
74
75
  )}
75
- {addressesList.map((address) => (
76
- <DashboardAddressBox
77
- key={address.id}
78
- address={address}
79
- edit={(_address) => onClickEdit?.(_address)}
80
- remove={remove}
81
- promote={promote}
82
- />
83
- ))}
76
+ {addressesList.length > 0 && (
77
+ <DashboardBox.List>
78
+ {addressesList.map((address) => (
79
+ <DashboardAddressBox
80
+ key={address.id}
81
+ address={address}
82
+ edit={(_address) => onClickEdit?.(_address)}
83
+ remove={remove}
84
+ promote={promote}
85
+ />
86
+ ))}
87
+ </DashboardBox.List>
88
+ )}
84
89
  <Button color="secondary" fullWidth onClick={() => onClickCreate?.()}>
85
90
  <Icon name={IconTypeEnum.PLUS} className="button__icon" />
86
91
  <FormattedMessage {...messages.add} />