richie-education 2.14.1 → 2.16.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.
Files changed (84) hide show
  1. package/.storybook/main.js +17 -0
  2. package/.storybook/preview-body.html +1 -0
  3. package/.storybook/preview-head.html +1 -0
  4. package/.storybook/preview.tsx +38 -0
  5. package/jest/resolver.js +34 -0
  6. package/jest/setup.ts +30 -0
  7. package/jest.config.js +6 -3
  8. package/js/components/AddressesManagement/AddressForm.spec.tsx +191 -0
  9. package/js/components/AddressesManagement/AddressForm.tsx +32 -4
  10. package/js/components/AddressesManagement/_styles.scss +1 -79
  11. package/js/components/AddressesManagement/index.spec.tsx +42 -182
  12. package/js/components/AddressesManagement/index.tsx +3 -4
  13. package/js/components/AddressesManagement/validationSchema.spec.ts +147 -0
  14. package/js/components/AddressesManagement/validationSchema.ts +51 -0
  15. package/js/components/Banner/index.stories.tsx +32 -0
  16. package/js/components/CourseProductItem/index.spec.tsx +7 -65
  17. package/js/components/CourseProductItem/index.tsx +4 -3
  18. package/js/components/CourseRunEnrollment/index.spec.tsx +1 -1
  19. package/js/components/DashBoard/index.spec.tsx +38 -0
  20. package/js/components/DashBoard/index.tsx +5 -0
  21. package/js/components/Form/CheckboxField.stories.tsx +13 -0
  22. package/js/components/Form/Field.stories.tsx +16 -0
  23. package/js/components/Form/Inputs.tsx +3 -3
  24. package/js/components/Form/RadioField.stories.tsx +18 -0
  25. package/js/components/Form/SelectField.stories.tsx +26 -0
  26. package/js/components/Form/TextAreaField.stories.tsx +13 -0
  27. package/js/components/Form/TextField.stories.tsx +13 -0
  28. package/js/components/Icon/index.stories.tsx +11 -0
  29. package/js/components/LtiConsumer/index.spec.tsx +244 -11
  30. package/js/components/LtiConsumer/index.tsx +46 -13
  31. package/js/components/Modal/index.stories.tsx +42 -0
  32. package/js/components/PaymentButton/index.tsx +1 -1
  33. package/js/components/RegisteredAddress/_styles.scss +83 -0
  34. package/js/components/RegisteredAddress/index.tsx +3 -3
  35. package/js/components/Root/index.tsx +3 -0
  36. package/js/components/SaleTunnel/_styles.scss +30 -0
  37. package/js/components/SaleTunnel/index.spec.tsx +8 -0
  38. package/js/components/SaleTunnel/index.tsx +4 -2
  39. package/js/components/SaleTunnelStepPayment/index.spec.tsx +3 -3
  40. package/js/components/SaleTunnelStepPayment/index.tsx +8 -4
  41. package/js/components/SaleTunnelStepResume/index.tsx +1 -1
  42. package/js/components/SaleTunnelStepValidation/index.spec.tsx +1 -1
  43. package/js/components/SaleTunnelStepValidation/index.tsx +1 -1
  44. package/js/components/Search/FiltersPaneCloseButton.tsx +49 -0
  45. package/js/components/Search/index.spec.tsx +57 -25
  46. package/js/components/Search/index.tsx +60 -49
  47. package/js/components/SearchFiltersPane/index.tsx +4 -1
  48. package/js/components/Spinner/index.stories.tsx +26 -0
  49. package/js/data/CourseCodeProvider/index.spec.tsx +5 -0
  50. package/js/data/JoanieApiProvider/index.spec.tsx +5 -0
  51. package/js/data/SessionProvider/BaseSessionProvider.tsx +1 -1
  52. package/js/data/SessionProvider/JoanieSessionProvider.tsx +1 -1
  53. package/js/data/SessionProvider/index.spec.tsx +2 -2
  54. package/js/settings.ts +1 -0
  55. package/js/types/Joanie.ts +2 -2
  56. package/js/types/api.ts +1 -1
  57. package/js/types/globals.d.ts +8 -0
  58. package/js/utils/api/authentication.ts +3 -3
  59. package/js/utils/api/lms/{base.spec.ts → dummy.spec.ts} +49 -12
  60. package/js/utils/api/lms/{base.ts → dummy.ts} +27 -3
  61. package/js/utils/api/lms/index.spec.ts +1 -1
  62. package/js/utils/api/lms/index.ts +4 -4
  63. package/js/utils/test/factories.ts +11 -12
  64. package/package.json +61 -48
  65. package/scss/_main.scss +13 -101
  66. package/scss/colors/_theme.scss +8 -0
  67. package/scss/components/_header.scss +20 -2
  68. package/scss/components/_index.scss +52 -0
  69. package/scss/components/templates/courses/cms/_blogpost_detail.scss +18 -1
  70. package/scss/components/templates/search/_search.scss +17 -0
  71. package/scss/generic/_index.scss +6 -0
  72. package/scss/objects/_blogpost_glimpses.scss +19 -4
  73. package/scss/objects/_course_glimpses.scss +0 -1
  74. package/scss/objects/_form.scss +26 -21
  75. package/scss/objects/_index.scss +17 -0
  76. package/scss/settings/_bootstrap.scss +18 -0
  77. package/scss/settings/_variables.scss +24 -33
  78. package/scss/tools/_buttons.scss +1 -0
  79. package/scss/tools/_colors.scss +17 -0
  80. package/scss/tools/_index.scss +14 -0
  81. package/scss/tools/_rem.scss +6 -1
  82. package/stories/Introduction.stories.mdx +13 -0
  83. package/js/components/CourseProductItem/PurchasedProductMenu.tsx +0 -135
  84. package/js/testSetup.ts +0 -13
@@ -0,0 +1,17 @@
1
+ module.exports = {
2
+ "stories": [
3
+ "../stories/**/*.stories.mdx",
4
+ "../stories/**/*.stories.@(js|jsx|ts|tsx)",
5
+ "../js/**/*.stories.@(js|jsx|ts|tsx)",
6
+ ],
7
+ "addons": [
8
+ "@storybook/addon-links",
9
+ "@storybook/addon-essentials",
10
+ "@storybook/addon-interactions"
11
+ ],
12
+ "framework": "@storybook/react",
13
+ "core": {
14
+ "builder": "@storybook/builder-webpack5"
15
+ },
16
+ staticDirs: ['../../richie/static', '../../richie/apps/core/templates/richie'],
17
+ }
@@ -0,0 +1 @@
1
+ <div id="modal-exclude" style="min-height: 0;"></div>
@@ -0,0 +1 @@
1
+ <link rel="stylesheet" type="text/css" href="/richie/css/main.css">
@@ -0,0 +1,38 @@
1
+ import { useState } from "react";
2
+ import { useAsyncEffect } from "../js/utils/useAsyncEffect";
3
+
4
+ export const parameters = {
5
+ actions: {argTypesRegex: "^on[A-Z].*"},
6
+ controls: {
7
+ matchers: {
8
+ color: /(background|color)$/i,
9
+ date: /Date$/,
10
+ },
11
+ },
12
+ }
13
+
14
+
15
+ const IconsWrapper = props => {
16
+ const [symbols, setSymbols] = useState('');
17
+
18
+ useAsyncEffect(async () => {
19
+ const response = await fetch('/icons.html');
20
+ const body = await response.text();
21
+ setSymbols(body);
22
+ }, []);
23
+
24
+ return (
25
+ <div>
26
+ <div dangerouslySetInnerHTML={{__html: symbols}}/>
27
+ {props.children}
28
+ </div>
29
+ );
30
+ };
31
+
32
+ export const decorators = [
33
+ (Story) => (
34
+ <IconsWrapper>
35
+ <Story/>
36
+ </IconsWrapper>
37
+ ),
38
+ ];
@@ -0,0 +1,34 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved.
2
+ // Licensed under the MIT License.
3
+ /*
4
+ Read this issue for further information
5
+ https://github.com/microsoft/accessibility-insights-web/pull/5421#issuecomment-1109168149
6
+ */
7
+
8
+ module.exports = (path, options) => {
9
+ // Call the defaultResolver, so we leverage its cache, error handling, etc.
10
+ return options.defaultResolver(path, {
11
+ ...options,
12
+ // Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
13
+ packageFilter: (pkg) => {
14
+ // This is a workaround for https://github.com/uuidjs/uuid/pull/616
15
+ //
16
+ // jest-environment-jsdom 28+ tries to use browser exports instead of default exports,
17
+ // but uuid only offers an ESM browser export and not a CommonJS one. Jest does not yet
18
+ // support ESM modules natively, so this causes a Jest error related to trying to parse
19
+ // "export" syntax.
20
+ //
21
+ // This workaround prevents Jest from considering uuid's module-based exports at all;
22
+ // it falls back to uuid's CommonJS+node "main" property.
23
+ //
24
+ // Once we're able to migrate our Jest config to ESM and a browser crypto
25
+ // implementation is available for the browser+ESM version of uuid to use (eg, via
26
+ // https://github.com/jsdom/jsdom/pull/3352 or a similar polyfill), this can go away.
27
+ if (pkg.name === 'uuid') {
28
+ delete pkg.exports;
29
+ delete pkg.module;
30
+ }
31
+ return pkg;
32
+ },
33
+ });
34
+ };
package/jest/setup.ts ADDED
@@ -0,0 +1,30 @@
1
+ // Extend jest matchers with jest-dom's
2
+ import '@testing-library/jest-dom/extend-expect';
3
+
4
+ import { setLogger } from 'react-query';
5
+ import { noop } from 'utils';
6
+
7
+ /*
8
+ * A little trick to prevent so package to be reset when using `jest.resetModules()`.
9
+ * https://github.com/facebook/jest/issues/8987#issuecomment-584898030
10
+ */
11
+ const RESET_MODULE_EXCEPTIONS = ['react', 'react-intl'];
12
+
13
+ const mockActualRegistry: Record<PropertyKey, any> = {};
14
+
15
+ RESET_MODULE_EXCEPTIONS.forEach((moduleName) => {
16
+ jest.doMock(moduleName, () => {
17
+ if (!mockActualRegistry[moduleName]) {
18
+ mockActualRegistry[moduleName] = jest.requireActual(moduleName);
19
+ }
20
+ return mockActualRegistry[moduleName];
21
+ });
22
+ });
23
+
24
+ /* Prevent log error during tests */
25
+ setLogger({
26
+ // eslint-disable-next-line no-console
27
+ log: console.log,
28
+ warn: console.warn,
29
+ error: noop,
30
+ });
package/jest.config.js CHANGED
@@ -6,12 +6,15 @@ module.exports = {
6
6
  moduleNameMapper: {
7
7
  '\\.(css)$': '<rootDir>/front/__mocks__/styleMock.js',
8
8
  },
9
- setupFilesAfterEnv: ['./js/testSetup.ts'],
9
+ setupFilesAfterEnv: ['<rootDir>/jest/setup.ts'],
10
10
  testMatch: [`${__dirname}/js/**/*.spec.+(ts|tsx|js)`],
11
- testURL: 'https://localhost',
12
11
  coverageDirectory: '.coverage',
13
12
  testEnvironment: 'jsdom',
14
- transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'],
13
+ testEnvironmentOptions: {
14
+ url: 'https://localhost',
15
+ },
16
+ resolver: '<rootDir>/jest/resolver.js',
17
+ transformIgnorePatterns: ['node_modules/(?!(lodash-es|@hookform/resolvers)/)'],
15
18
  globals: {
16
19
  RICHIE_VERSION: 'test',
17
20
  },
@@ -0,0 +1,191 @@
1
+ import { act } from '@testing-library/react-hooks';
2
+ import { fireEvent, getByText, render, screen } from '@testing-library/react';
3
+ import * as mockFactories from 'utils/test/factories';
4
+ import { AddressFactory } from 'utils/test/factories';
5
+ import countries from 'i18n-iso-countries';
6
+ import { IntlProvider } from 'react-intl';
7
+ import { Address } from 'types/Joanie';
8
+ import { ErrorKeys } from './validationSchema';
9
+ import AddressForm from './AddressForm';
10
+
11
+ jest.mock('hooks/useAddresses', () => ({
12
+ useAddresses: () => ({
13
+ states: {
14
+ creating: false,
15
+ updating: false,
16
+ },
17
+ }),
18
+ }));
19
+
20
+ describe('AddressForm', () => {
21
+ const handleReset = jest.fn();
22
+ const onSubmit = jest.fn();
23
+
24
+ beforeEach(() => {
25
+ jest.resetAllMocks();
26
+ jest.resetModules();
27
+ });
28
+
29
+ it('renders a button with label "Use this address" when no address is provided', () => {
30
+ render(
31
+ <IntlProvider locale="en">
32
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} />
33
+ </IntlProvider>,
34
+ );
35
+
36
+ screen.getByRole('button', { name: 'Use this address' });
37
+ expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull();
38
+ });
39
+
40
+ it('renders a button with label "Use this address" and a cancel button when no address is provided', async () => {
41
+ const address: Address = AddressFactory.generate();
42
+ render(
43
+ <IntlProvider locale="en">
44
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} address={address} />
45
+ </IntlProvider>,
46
+ );
47
+
48
+ screen.getByRole('button', { name: 'Update this address' });
49
+
50
+ const $button = screen.getByRole('button', { name: 'Cancel' });
51
+ await act(async () => {
52
+ fireEvent.click($button);
53
+ });
54
+ expect(handleReset).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ it('renders an error message when a value in the form is invalid', async () => {
58
+ render(
59
+ <IntlProvider locale="en">
60
+ <AddressForm handleReset={handleReset} onSubmit={onSubmit} />
61
+ </IntlProvider>,
62
+ );
63
+
64
+ screen.getByRole('form');
65
+ const $titleInput = screen.getByRole('textbox', { name: 'Address title' });
66
+ const $firstnameInput = screen.getByRole('textbox', { name: "Recipient's first name" });
67
+ const $lastnameInput = screen.getByRole('textbox', { name: "Recipient's last name" });
68
+ const $addressInput = screen.getByRole('textbox', { name: 'Address' });
69
+ const $cityInput = screen.getByRole('textbox', { name: 'City' });
70
+ const $postcodeInput = screen.getByRole('textbox', { name: 'Postcode' });
71
+ const $countryInput = screen.getByRole('combobox', { name: 'Country' });
72
+ const $submitButton = screen.getByRole('button', {
73
+ name: 'Use this address',
74
+ }) as HTMLButtonElement;
75
+
76
+ // - Until form is not fulfill, submit button should be disabled
77
+ expect($submitButton.disabled).toBe(true);
78
+
79
+ // - User fulfills address fields
80
+ const address = mockFactories.AddressFactory.generate();
81
+
82
+ await act(async () => {
83
+ fireEvent.input($titleInput, { target: { value: address.title } });
84
+ fireEvent.change($firstnameInput, { target: { value: address.first_name } });
85
+ fireEvent.change($lastnameInput, { target: { value: address.last_name } });
86
+ fireEvent.change($addressInput, { target: { value: address.address } });
87
+ fireEvent.change($cityInput, { target: { value: address.city } });
88
+ fireEvent.change($postcodeInput, { target: { value: address.postcode } });
89
+ fireEvent.change($countryInput, { target: { value: address.country } });
90
+ // - As form validation is triggered on blur, we need to trigger this event in
91
+ // order to update form state.
92
+ fireEvent.blur($countryInput);
93
+ });
94
+
95
+ // Once the form has been fulfilled properly, submit button should be enabled.
96
+ expect($submitButton.disabled).toBe(false);
97
+
98
+ // Before submitting, we change field values to corrupt the form data
99
+ await act(async () => {
100
+ fireEvent.input($titleInput, { target: { value: 'a' } });
101
+ fireEvent.input($firstnameInput, { target: { value: '' } });
102
+ fireEvent.change($countryInput, { target: { value: '-' } });
103
+ fireEvent.click($submitButton);
104
+ });
105
+
106
+ expect(onSubmit).not.toHaveBeenCalled();
107
+
108
+ // Error messages should have been displayed.
109
+ // Title field should have a message saying that the value is too short.
110
+ getByText($titleInput.closest('.form-field')!, 'The minimum length is 2 chars.');
111
+ // Firstname field should have a message saying that the value is required.
112
+ getByText($firstnameInput.closest('.form-field')!, 'This field is required.');
113
+ // Country field should have a message saying that the value is not valid.
114
+ getByText(
115
+ $countryInput.closest('.form-field')!,
116
+ `You must select a value within: ${Object.keys(countries.getAlpha2Codes()).join(', ')}.`,
117
+ );
118
+ });
119
+
120
+ it('renders default error message when error message does not exist', async () => {
121
+ jest.doMock('./validationSchema', () => ({
122
+ __esModule: true,
123
+ ...jest.requireActual('./validationSchema'),
124
+ errorMessages: {
125
+ [ErrorKeys.MIXED_INVALID]: {
126
+ id: 'components.AddressesManagement.validationSchema.mixedInvalid',
127
+ defaultMessage: 'This field is invalid.',
128
+ description: 'Error message displayed when a field value is invalid.',
129
+ },
130
+ },
131
+ }));
132
+
133
+ // Import locally to get module with mocked error messages.
134
+ const Form = jest.requireActual('./AddressForm').default;
135
+
136
+ render(
137
+ <IntlProvider locale="en">
138
+ <Form handleReset={handleReset} onSubmit={onSubmit} />
139
+ </IntlProvider>,
140
+ );
141
+
142
+ screen.getByRole('form');
143
+ const $titleInput = screen.getByRole('textbox', { name: 'Address title' });
144
+ const $firstnameInput = screen.getByRole('textbox', { name: "Recipient's first name" });
145
+ const $lastnameInput = screen.getByRole('textbox', { name: "Recipient's last name" });
146
+ const $addressInput = screen.getByRole('textbox', { name: 'Address' });
147
+ const $cityInput = screen.getByRole('textbox', { name: 'City' });
148
+ const $postcodeInput = screen.getByRole('textbox', { name: 'Postcode' });
149
+ const $countryInput = screen.getByRole('combobox', { name: 'Country' });
150
+ const $submitButton = screen.getByRole('button', {
151
+ name: 'Use this address',
152
+ }) as HTMLButtonElement;
153
+
154
+ // - Until form is not fulfill, submit button should be disabled
155
+ expect($submitButton.disabled).toBe(true);
156
+
157
+ // - User fulfills address fields
158
+ const address = mockFactories.AddressFactory.generate();
159
+
160
+ await act(async () => {
161
+ fireEvent.input($titleInput, { target: { value: address.title } });
162
+ fireEvent.change($firstnameInput, { target: { value: address.first_name } });
163
+ fireEvent.change($lastnameInput, { target: { value: address.last_name } });
164
+ fireEvent.change($addressInput, { target: { value: address.address } });
165
+ fireEvent.change($cityInput, { target: { value: address.city } });
166
+ fireEvent.change($postcodeInput, { target: { value: address.postcode } });
167
+ fireEvent.change($countryInput, { target: { value: address.country } });
168
+ // - As form validation is triggered on blur, we need to trigger this event in
169
+ // order to update form state.
170
+ fireEvent.blur($countryInput);
171
+ });
172
+
173
+ // Once the form has been fulfilled properly, submit button should be enabled.
174
+ expect($submitButton.disabled).toBe(false);
175
+
176
+ // Before submitting, we change field values to corrupt the form data
177
+ await act(async () => {
178
+ fireEvent.input($titleInput, { target: { value: 'a' } });
179
+ fireEvent.input($firstnameInput, { target: { value: '' } });
180
+ fireEvent.change($countryInput, { target: { value: '-' } });
181
+ fireEvent.click($submitButton);
182
+ });
183
+
184
+ expect(onSubmit).not.toHaveBeenCalled();
185
+
186
+ // Default error messages should have been displayed.
187
+ getByText($titleInput.closest('.form-field')!, 'This field is invalid.');
188
+ getByText($firstnameInput.closest('.form-field')!, 'This field is invalid.');
189
+ getByText($countryInput.closest('.form-field')!, `This field is invalid.`);
190
+ });
191
+ });
@@ -7,7 +7,8 @@ import { messages } from 'components/AddressesManagement/index';
7
7
  import { CheckboxField, SelectField, TextField } from 'components/Form';
8
8
  import { useAddresses } from 'hooks/useAddresses';
9
9
  import type { Address } from 'types/Joanie';
10
- import validationSchema from './validationSchema';
10
+ import { Maybe } from 'types/utils';
11
+ import validationSchema, { ErrorKeys, errorMessages } from './validationSchema';
11
12
 
12
13
  export type AddressFormValues = Omit<Address, 'id' | 'is_main'> & { save: boolean };
13
14
 
@@ -41,6 +42,25 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
41
42
  const [languageCode] = intl.locale.split('-');
42
43
  const countryList = countries.getNames(languageCode);
43
44
 
45
+ const getLocalizedErrorMessage = (
46
+ error: Maybe<
47
+ | string
48
+ | {
49
+ key: ErrorKeys;
50
+ values: Record<PropertyKey, string | number | Array<string | number>>;
51
+ }
52
+ >,
53
+ ) => {
54
+ if (!error) return undefined;
55
+
56
+ if (typeof error === 'string' || errorMessages[error.key] === undefined) {
57
+ // If the error has not been translated we return a default error message.
58
+ return intl.formatMessage(errorMessages[ErrorKeys.MIXED_INVALID]);
59
+ }
60
+
61
+ return intl.formatMessage(errorMessages[error.key], error.values);
62
+ };
63
+
44
64
  /**
45
65
  * Prevent form to be submitted and clear `editedAddress` state.
46
66
  */
@@ -62,6 +82,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
62
82
  id="title"
63
83
  label={intl.formatMessage(messages.titleInputLabel)}
64
84
  error={!!formState.errors.title}
85
+ message={getLocalizedErrorMessage(formState.errors.title?.message)}
65
86
  {...register('title')}
66
87
  />
67
88
  <div className="form-group">
@@ -71,6 +92,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
71
92
  id="first_name"
72
93
  label={intl.formatMessage(messages.first_nameInputLabel)}
73
94
  error={!!formState.errors.first_name}
95
+ message={getLocalizedErrorMessage(formState.errors.first_name?.message)}
74
96
  {...register('first_name')}
75
97
  />
76
98
  <TextField
@@ -79,6 +101,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
79
101
  id="last_name"
80
102
  label={intl.formatMessage(messages.last_nameInputLabel)}
81
103
  error={!!formState.errors.last_name}
104
+ message={getLocalizedErrorMessage(formState.errors.last_name?.message)}
82
105
  {...register('last_name')}
83
106
  />
84
107
  </div>
@@ -88,6 +111,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
88
111
  id="address"
89
112
  label={intl.formatMessage(messages.addressInputLabel)}
90
113
  error={!!formState.errors.address}
114
+ message={getLocalizedErrorMessage(formState.errors.address?.message)}
91
115
  {...register('address')}
92
116
  />
93
117
  <div className="form-group">
@@ -97,6 +121,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
97
121
  id="postcode"
98
122
  label={intl.formatMessage(messages.postcodeInputLabel)}
99
123
  error={!!formState.errors.postcode}
124
+ message={getLocalizedErrorMessage(formState.errors.postcode?.message)}
100
125
  {...register('postcode')}
101
126
  />
102
127
  <TextField
@@ -105,6 +130,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
105
130
  id="city"
106
131
  label={intl.formatMessage(messages.cityInputLabel)}
107
132
  error={!!formState.errors.city}
133
+ message={getLocalizedErrorMessage(formState.errors.city?.message)}
108
134
  {...register('city')}
109
135
  />
110
136
  </div>
@@ -114,6 +140,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
114
140
  id="country"
115
141
  label={intl.formatMessage(messages.countryInputLabel)}
116
142
  error={!!formState.errors.country}
143
+ message={getLocalizedErrorMessage(formState.errors.country?.message)}
117
144
  {...register('country', { value: address?.country, required: true })}
118
145
  >
119
146
  <option disabled value="-">
@@ -132,6 +159,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
132
159
  id="save"
133
160
  label={intl.formatMessage(messages.saveInputLabel)}
134
161
  error={!!formState.errors?.save}
162
+ message={getLocalizedErrorMessage(formState.errors.save?.message)}
135
163
  {...register('save')}
136
164
  />
137
165
  ) : null}
@@ -139,14 +167,14 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
139
167
  {address ? (
140
168
  <Fragment>
141
169
  <button
142
- className="button button--active"
170
+ className="button button-sale--tertiary"
143
171
  onClick={handleCancel}
144
172
  title={intl.formatMessage(messages.cancelTitleButton)}
145
173
  >
146
174
  <FormattedMessage {...messages.cancelButton} />
147
175
  </button>
148
176
  <button
149
- className="button button--primary"
177
+ className="button button-sale--primary"
150
178
  disabled={!formState.isValid || addresses.states.updating}
151
179
  type="submit"
152
180
  >
@@ -155,7 +183,7 @@ const AddressForm = ({ handleReset, onSubmit, address }: Props) => {
155
183
  </Fragment>
156
184
  ) : (
157
185
  <button
158
- className="button button--primary"
186
+ className="button button-sale--primary"
159
187
  disabled={!formState.isValid || addresses.states.creating || addresses.states.updating}
160
188
  type="submit"
161
189
  >
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  & > .button {
11
- .button__icon {
11
+ .icon.button__icon {
12
12
  display: inline-block;
13
13
  fill: none;
14
14
  height: 1.2em;
@@ -60,82 +60,4 @@
60
60
  list-style: none;
61
61
  padding: 0;
62
62
  }
63
- .registered-addresses-item {
64
- @include shadowed-box($padding: 1rem);
65
- align-items: center;
66
- display: flex;
67
-
68
- &:not(:last-child) {
69
- margin-bottom: 1rem;
70
- }
71
-
72
- @include media-breakpoint-down(md) {
73
- align-items: start;
74
- flex-direction: column;
75
-
76
- &__actions {
77
- align-self: end;
78
- }
79
- }
80
-
81
- & > * {
82
- margin: 0;
83
- }
84
-
85
- .address-main-indicator {
86
- $tick-size: 1.125rem;
87
- background-color: r-theme-val(form, input-unchecked-background);
88
- border-radius: 100vw;
89
- border: 2px solid r-theme-val(form, input-unchecked-border);
90
- box-sizing: border-box;
91
- display: block;
92
- height: $tick-size;
93
- position: relative;
94
- width: $tick-size;
95
-
96
- &:before {
97
- background: transparent;
98
- border-radius: 100vw;
99
- content: '';
100
- display: block;
101
- height: 60%;
102
- left: 50%;
103
- position: absolute;
104
- top: 50%;
105
- transform-origin: center center;
106
- transform: translate(-50%, -50%) scale(calc(100 / 60));
107
- transition: transform 400ms $r-ease-out;
108
- width: 60%;
109
- }
110
-
111
- &--is-main {
112
- background-color: r-theme-val(form, input-checked-background);
113
- border-color: r-theme-val(form, input-checked-border);
114
- color: r-theme-val(form, input-checked-color);
115
-
116
- &:before {
117
- background-color: r-theme-val(form, input-checked-color);
118
- transform: translate(-50%, -50%) scale(1);
119
- }
120
- }
121
- }
122
-
123
- &__title {
124
- font-size: 0.8rem;
125
- margin-right: 0.8rem;
126
- }
127
- &__address {
128
- flex: 1;
129
- line-height: 1em;
130
- }
131
- &__actions {
132
- & > .button {
133
- min-width: 100px;
134
-
135
- &:disabled {
136
- visibility: hidden;
137
- }
138
- }
139
- }
140
- }
141
63
  }