@openedx/frontend-app-authn 1.0.0-alpha.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 (177) hide show
  1. package/LICENSE +661 -0
  2. package/README.rst +204 -0
  3. package/package.json +79 -0
  4. package/src/Main.tsx +23 -0
  5. package/src/__mocks__/file.js +1 -0
  6. package/src/__mocks__/svg.js +1 -0
  7. package/src/app.scss +6 -0
  8. package/src/app.ts +43 -0
  9. package/src/base-container/components/default-layout/DefaultLayout.test.jsx +48 -0
  10. package/src/base-container/components/default-layout/LargeLayout.jsx +42 -0
  11. package/src/base-container/components/default-layout/MediumLayout.jsx +49 -0
  12. package/src/base-container/components/default-layout/SmallLayout.jsx +37 -0
  13. package/src/base-container/components/default-layout/index.jsx +3 -0
  14. package/src/base-container/components/default-layout/messages.js +16 -0
  15. package/src/base-container/components/image-layout/ExtraSmallLayout.jsx +31 -0
  16. package/src/base-container/components/image-layout/LargeLayout.jsx +32 -0
  17. package/src/base-container/components/image-layout/MediumLayout.jsx +32 -0
  18. package/src/base-container/components/image-layout/SmallLayout.jsx +31 -0
  19. package/src/base-container/components/image-layout/index.jsx +4 -0
  20. package/src/base-container/components/image-layout/index.scss +37 -0
  21. package/src/base-container/components/image-layout/messages.js +16 -0
  22. package/src/base-container/components/welcome-page-layout/LargeLayout.jsx +46 -0
  23. package/src/base-container/components/welcome-page-layout/MediumLayout.jsx +49 -0
  24. package/src/base-container/components/welcome-page-layout/SmallLayout.jsx +38 -0
  25. package/src/base-container/components/welcome-page-layout/index.jsx +3 -0
  26. package/src/base-container/components/welcome-page-layout/messages.js +21 -0
  27. package/src/base-container/index.jsx +70 -0
  28. package/src/base-container/tests/BaseContainer.test.jsx +44 -0
  29. package/src/common-components/EmbeddedRegistrationRoute.jsx +26 -0
  30. package/src/common-components/EnterpriseSSO.jsx +110 -0
  31. package/src/common-components/FormGroup.jsx +106 -0
  32. package/src/common-components/InstitutionLogistration.jsx +102 -0
  33. package/src/common-components/NotFoundPage.jsx +15 -0
  34. package/src/common-components/PasswordField.jsx +175 -0
  35. package/src/common-components/RedirectLogistration.jsx +94 -0
  36. package/src/common-components/RegisterFaIcons.jsx +8 -0
  37. package/src/common-components/SocialAuthProviders.jsx +75 -0
  38. package/src/common-components/ThirdPartyAuth.jsx +123 -0
  39. package/src/common-components/ThirdPartyAuthAlert.jsx +49 -0
  40. package/src/common-components/UnAuthOnlyRoute.jsx +41 -0
  41. package/src/common-components/data/actions.js +27 -0
  42. package/src/common-components/data/reducers.js +63 -0
  43. package/src/common-components/data/sagas.js +32 -0
  44. package/src/common-components/data/selectors.js +28 -0
  45. package/src/common-components/data/service.js +23 -0
  46. package/src/common-components/data/tests/reducer.test.js +82 -0
  47. package/src/common-components/data/tests/sagas.test.js +71 -0
  48. package/src/common-components/index.jsx +14 -0
  49. package/src/common-components/messages.jsx +127 -0
  50. package/src/common-components/tests/EmbeddedRegistrationRoute.test.jsx +76 -0
  51. package/src/common-components/tests/FormField.test.jsx +289 -0
  52. package/src/common-components/tests/SocialAuthProviders.test.jsx +75 -0
  53. package/src/common-components/tests/ThirdPartyAuthAlert.test.jsx +39 -0
  54. package/src/common-components/tests/UnAuthOnlyRoute.test.jsx +73 -0
  55. package/src/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap +156 -0
  56. package/src/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap +53 -0
  57. package/src/constants.ts +1 -0
  58. package/src/data/configureStore.js +33 -0
  59. package/src/data/constants.js +38 -0
  60. package/src/data/countries.ts +59 -0
  61. package/src/data/oneTrust.js +3 -0
  62. package/src/data/reducers.js +36 -0
  63. package/src/data/sagas.js +19 -0
  64. package/src/data/tests/cookies.test.js +42 -0
  65. package/src/data/tests/dataUtils.test.js +19 -0
  66. package/src/data/tests/reduxUtils.test.js +14 -0
  67. package/src/data/utils/cookies.js +12 -0
  68. package/src/data/utils/dataUtils.js +81 -0
  69. package/src/data/utils/index.js +11 -0
  70. package/src/data/utils/reduxUtils.js +34 -0
  71. package/src/data/utils/useMobileResponsive.js +24 -0
  72. package/src/field-renderer/FieldRenderer.jsx +156 -0
  73. package/src/field-renderer/index.jsx +2 -0
  74. package/src/field-renderer/tests/FieldRenderer.test.jsx +201 -0
  75. package/src/forgot-password/ForgotPasswordAlert.jsx +97 -0
  76. package/src/forgot-password/ForgotPasswordPage.jsx +193 -0
  77. package/src/forgot-password/data/actions.js +32 -0
  78. package/src/forgot-password/data/reducers.js +58 -0
  79. package/src/forgot-password/data/sagas.js +35 -0
  80. package/src/forgot-password/data/selectors.js +10 -0
  81. package/src/forgot-password/data/service.js +21 -0
  82. package/src/forgot-password/data/tests/reducers.test.js +34 -0
  83. package/src/forgot-password/data/tests/sagas.test.js +67 -0
  84. package/src/forgot-password/index.js +5 -0
  85. package/src/forgot-password/messages.js +128 -0
  86. package/src/forgot-password/tests/ForgotPasswordPage.test.jsx +269 -0
  87. package/src/i18n/index.js +25 -0
  88. package/src/index.ts +3 -0
  89. package/src/login/AccountActivationMessage.jsx +78 -0
  90. package/src/login/ChangePasswordPrompt.jsx +91 -0
  91. package/src/login/LoginFailure.jsx +231 -0
  92. package/src/login/LoginPage.jsx +368 -0
  93. package/src/login/data/actions.js +39 -0
  94. package/src/login/data/constants.js +20 -0
  95. package/src/login/data/reducers.js +76 -0
  96. package/src/login/data/sagas.js +45 -0
  97. package/src/login/data/service.js +24 -0
  98. package/src/login/data/tests/reducers.test.js +155 -0
  99. package/src/login/data/tests/sagas.test.js +110 -0
  100. package/src/login/index.js +5 -0
  101. package/src/login/messages.jsx +211 -0
  102. package/src/login/tests/AccountActivationMessage.test.jsx +122 -0
  103. package/src/login/tests/ChangePasswordPrompt.test.jsx +75 -0
  104. package/src/login/tests/LoginFailure.test.jsx +323 -0
  105. package/src/login/tests/LoginPage.test.jsx +839 -0
  106. package/src/logistration/Logistration.jsx +189 -0
  107. package/src/logistration/Logistration.test.jsx +275 -0
  108. package/src/progressive-profiling/ProgressiveProfiling.jsx +313 -0
  109. package/src/progressive-profiling/ProgressiveProfilingPageModal.jsx +56 -0
  110. package/src/progressive-profiling/data/actions.js +22 -0
  111. package/src/progressive-profiling/data/reducers.js +38 -0
  112. package/src/progressive-profiling/data/sagas.js +24 -0
  113. package/src/progressive-profiling/data/selectors.js +14 -0
  114. package/src/progressive-profiling/data/service.js +17 -0
  115. package/src/progressive-profiling/index.js +5 -0
  116. package/src/progressive-profiling/messages.jsx +57 -0
  117. package/src/progressive-profiling/tests/ProgressiveProfiling.test.jsx +322 -0
  118. package/src/register/RegistrationFields/CountryField/CountryField.jsx +164 -0
  119. package/src/register/RegistrationFields/CountryField/CountryField.test.jsx +204 -0
  120. package/src/register/RegistrationFields/CountryField/validator.js +32 -0
  121. package/src/register/RegistrationFields/EmailField/EmailField.jsx +140 -0
  122. package/src/register/RegistrationFields/EmailField/EmailField.test.jsx +236 -0
  123. package/src/register/RegistrationFields/EmailField/constants.js +131 -0
  124. package/src/register/RegistrationFields/EmailField/validator.js +126 -0
  125. package/src/register/RegistrationFields/EmailField/validator.test.js +39 -0
  126. package/src/register/RegistrationFields/HonorCodeField/HonorCode.jsx +104 -0
  127. package/src/register/RegistrationFields/HonorCodeField/HonorCode.test.jsx +70 -0
  128. package/src/register/RegistrationFields/NameField/NameField.jsx +68 -0
  129. package/src/register/RegistrationFields/NameField/NameField.test.jsx +146 -0
  130. package/src/register/RegistrationFields/NameField/validator.js +22 -0
  131. package/src/register/RegistrationFields/TermsOfServiceField/TermsOfService.jsx +58 -0
  132. package/src/register/RegistrationFields/TermsOfServiceField/TermsOfService.test.jsx +55 -0
  133. package/src/register/RegistrationFields/UsernameField/UsernameField.jsx +162 -0
  134. package/src/register/RegistrationFields/UsernameField/UsernameField.test.jsx +319 -0
  135. package/src/register/RegistrationFields/UsernameField/validator.js +16 -0
  136. package/src/register/RegistrationFields/index.js +6 -0
  137. package/src/register/RegistrationPage.jsx +428 -0
  138. package/src/register/RegistrationPage.test.jsx +854 -0
  139. package/src/register/components/ConfigurableRegistrationForm.jsx +244 -0
  140. package/src/register/components/RegistrationFailure.jsx +80 -0
  141. package/src/register/components/tests/ConfigurableRegistrationForm.test.jsx +401 -0
  142. package/src/register/components/tests/RegistrationFailure.test.jsx +212 -0
  143. package/src/register/components/tests/ThirdPartyAuth.test.jsx +439 -0
  144. package/src/register/data/actions.js +85 -0
  145. package/src/register/data/constants.js +13 -0
  146. package/src/register/data/reducers.js +140 -0
  147. package/src/register/data/sagas.js +58 -0
  148. package/src/register/data/selectors.js +34 -0
  149. package/src/register/data/service.js +45 -0
  150. package/src/register/data/tests/reducers.test.js +277 -0
  151. package/src/register/data/tests/sagas.test.js +239 -0
  152. package/src/register/data/tests/utils.test.js +98 -0
  153. package/src/register/data/utils.js +153 -0
  154. package/src/register/index.js +4 -0
  155. package/src/register/messages.jsx +206 -0
  156. package/src/reset-password/ResetPasswordFailure.jsx +55 -0
  157. package/src/reset-password/ResetPasswordPage.jsx +224 -0
  158. package/src/reset-password/ResetPasswordSuccess.jsx +19 -0
  159. package/src/reset-password/data/actions.js +50 -0
  160. package/src/reset-password/data/constants.js +16 -0
  161. package/src/reset-password/data/reducers.js +44 -0
  162. package/src/reset-password/data/sagas.js +67 -0
  163. package/src/reset-password/data/selectors.js +10 -0
  164. package/src/reset-password/data/service.js +61 -0
  165. package/src/reset-password/data/tests/sagas.test.js +185 -0
  166. package/src/reset-password/index.js +5 -0
  167. package/src/reset-password/messages.js +88 -0
  168. package/src/reset-password/tests/ResetPasswordPage.test.jsx +241 -0
  169. package/src/routes.jsx +79 -0
  170. package/src/sass/_base_component.scss +202 -0
  171. package/src/sass/_forgot_password.scss +3 -0
  172. package/src/sass/_login_page.scss +3 -0
  173. package/src/sass/_progressive_profiling_page.scss +30 -0
  174. package/src/sass/_registration.scss +123 -0
  175. package/src/sass/_reset_password.scss +3 -0
  176. package/src/sass/_style.scss +465 -0
  177. package/src/setupTest.js +68 -0
@@ -0,0 +1,88 @@
1
+ import { defineMessages } from '@openedx/frontend-base';
2
+
3
+ const messages = defineMessages({
4
+ 'sign.in': {
5
+ id: 'sign.in',
6
+ defaultMessage: 'Sign in',
7
+ description: 'Sign in toggle text',
8
+ },
9
+ 'reset.password.page.title': {
10
+ id: 'reset.password.page.title',
11
+ defaultMessage: 'Reset Password | {siteName}',
12
+ description: 'page title',
13
+ },
14
+ 'reset.password': {
15
+ id: 'reset.password',
16
+ defaultMessage: 'Reset password',
17
+ description: 'The page heading and button text for reset password page.',
18
+ },
19
+ 'reset.password.page.instructions': {
20
+ id: 'reset.password.page.instructions',
21
+ defaultMessage: 'Enter and confirm your new password.',
22
+ description: 'Instructions message for reset password page.',
23
+ },
24
+ 'new.password.label': {
25
+ id: 'new.password.label',
26
+ defaultMessage: 'New password',
27
+ description: 'New password field label for the reset password page.',
28
+ },
29
+ 'confirm.password.label': {
30
+ id: 'confirm.password.label',
31
+ defaultMessage: 'Confirm password',
32
+ description: 'Confirm password field label for the reset password page.',
33
+ },
34
+ // validation errors
35
+ 'password.validation.message': {
36
+ id: 'password.validation.message',
37
+ defaultMessage: 'Password criteria has not been met',
38
+ description: 'Error message for empty or invalid password',
39
+ },
40
+ 'passwords.do.not.match': {
41
+ id: 'passwords.do.not.match',
42
+ defaultMessage: 'Passwords do not match',
43
+ description: 'Password format error.',
44
+ },
45
+ 'confirm.your.password': {
46
+ id: 'confirm.your.password',
47
+ defaultMessage: 'Confirm your password',
48
+ description: 'Field validation message when confirm password is empty',
49
+ },
50
+ // alert banner strings
51
+ 'reset.password.failure.heading': {
52
+ id: 'reset.password.failure.heading',
53
+ defaultMessage: 'We couldn\'t reset your password.',
54
+ description: 'Heading for reset password request failure',
55
+ },
56
+ 'reset.password.form.submission.error': {
57
+ id: 'reset.password.form.submission.error',
58
+ defaultMessage: 'Please check your responses and try again.',
59
+ description: 'Error message for reset password page',
60
+ },
61
+ 'reset.server.rate.limit.error': {
62
+ id: 'reset.server.rate.limit.error',
63
+ defaultMessage: 'Too many requests.',
64
+ description: 'Too many request at server end point',
65
+ },
66
+ 'reset.password.success.heading': {
67
+ id: 'reset.password.success.heading',
68
+ defaultMessage: 'Password reset complete.',
69
+ description: 'Heading for alert box when reset password is successful',
70
+ },
71
+ 'reset.password.success': {
72
+ id: 'reset.password.success',
73
+ defaultMessage: 'Your password has been reset. Sign in to your account.',
74
+ description: 'Reset password success message',
75
+ },
76
+ 'internal.server.error': {
77
+ id: 'internal.server.error',
78
+ defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.',
79
+ description: 'Error message that appears when server responds with 500 error code',
80
+ },
81
+ 'rate.limit.error': {
82
+ id: 'rate.limit.error',
83
+ defaultMessage: 'An error has occurred because of too many requests. Please try again after some time.',
84
+ description: 'Error message that appears when server responds with 429 error code',
85
+ },
86
+ });
87
+
88
+ export default messages;
@@ -0,0 +1,241 @@
1
+ import { Provider } from 'react-redux';
2
+
3
+ import { CurrentAppProvider, configureI18n, getSiteConfig, injectIntl, IntlProvider } from '@openedx/frontend-base';
4
+ import {
5
+ fireEvent, render, screen,
6
+ } from '@testing-library/react';
7
+ import { act } from 'react-dom/test-utils';
8
+ import { MemoryRouter, useParams } from 'react-router-dom';
9
+ import configureStore from 'redux-mock-store';
10
+
11
+ import { appId } from '../../constants';
12
+ import { initializeMockServices } from '../../setupTest';
13
+ import { LOGIN_PAGE, RESET_PAGE } from '../../data/constants';
14
+ import { resetPassword, validateToken } from '../data/actions';
15
+ import {
16
+ PASSWORD_RESET, PASSWORD_RESET_ERROR, SUCCESS, TOKEN_STATE,
17
+ } from '../data/constants';
18
+ import ResetPasswordPage from '../ResetPasswordPage';
19
+
20
+ const mockNavigate = jest.fn();
21
+ const mockParams = jest.fn();
22
+ jest.mock('react-router-dom', () => ({
23
+ ...jest.requireActual('react-router-dom'),
24
+ useNavigate: () => mockNavigate,
25
+ useParams: jest.fn(),
26
+ }));
27
+
28
+ initializeMockServices();
29
+ const IntlResetPasswordPage = injectIntl(ResetPasswordPage);
30
+ const mockStore = configureStore();
31
+ const token = '1c-bmjdkc-5e60e084cf8113048ca7';
32
+
33
+ describe('ResetPasswordPage', () => {
34
+ let props = {};
35
+ let store = {};
36
+
37
+ const reduxWrapper = children => (
38
+ <IntlProvider locale="en">
39
+ <MemoryRouter>
40
+ <CurrentAppProvider appId={appId}>
41
+ <Provider store={store}>{children}</Provider>
42
+ </CurrentAppProvider>
43
+ </MemoryRouter>
44
+ </IntlProvider>
45
+ );
46
+
47
+ const initialState = {
48
+ register: {
49
+ validationApiRateLimited: false,
50
+ },
51
+ resetPassword: {},
52
+ };
53
+
54
+ beforeEach(() => {
55
+ store = mockStore(initialState);
56
+ configureI18n({
57
+ messages: { 'es-419': {}, de: {}, 'en-us': {} },
58
+ });
59
+ props = {
60
+ resetPassword: jest.fn(),
61
+ status: null,
62
+ token: null,
63
+ errors: null,
64
+ match: {
65
+ params: {},
66
+ },
67
+ };
68
+ useParams.mockReturnValue({ token: null });
69
+ });
70
+
71
+ afterEach(() => {
72
+ jest.clearAllMocks();
73
+ });
74
+
75
+ // ******** form submission tests ********
76
+
77
+ it('with valid inputs resetPassword action is dispatched', async () => {
78
+ const password = 'test-password-1';
79
+
80
+ store = mockStore({
81
+ ...initialState,
82
+ resetPassword: {
83
+ status: TOKEN_STATE.VALID,
84
+ },
85
+ });
86
+
87
+ store.dispatch = jest.fn(store.dispatch);
88
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
89
+ const newPasswordInput = screen.getByLabelText('New password');
90
+ const confirmPasswordInput = screen.getByLabelText('Confirm password');
91
+
92
+ fireEvent.change(newPasswordInput, { target: { value: password } });
93
+ fireEvent.change(confirmPasswordInput, { target: { value: password } });
94
+
95
+ const resetPasswordButton = screen.getByRole('button', { name: /Reset password/i, id: 'submit-new-password' });
96
+ await act(async () => {
97
+ fireEvent.click(resetPasswordButton);
98
+ });
99
+ expect(store.dispatch).toHaveBeenCalledWith(
100
+ resetPassword({ new_password1: password, new_password2: password }, props.token, {}),
101
+ );
102
+ });
103
+
104
+ // ******** test reset password field validations ********
105
+
106
+ it('should show error messages for required fields on empty form submission', () => {
107
+ store = mockStore({
108
+ ...initialState,
109
+ resetPassword: {
110
+ status: TOKEN_STATE.VALID,
111
+ },
112
+ });
113
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
114
+ const resetPasswordButton = screen.getByRole('button', { name: /Reset password/i, id: 'submit-new-password' });
115
+ fireEvent.click(resetPasswordButton);
116
+
117
+ expect(screen.queryByText(/We couldn't reset your password./i)).toBeTruthy();
118
+ expect(screen.queryByText('Password criteria has not been met')).toBeTruthy();
119
+ expect(screen.queryByText('Confirm your password')).toBeTruthy();
120
+
121
+ const newPasswordInput = screen.getByLabelText('New password');
122
+ fireEvent.focus(newPasswordInput);
123
+ expect(screen.queryByText('Password criteria has not been met')).toBeNull();
124
+
125
+ const confirmPasswordInput = screen.getByLabelText('Confirm password');
126
+ fireEvent.focus(confirmPasswordInput);
127
+ expect(screen.queryByText('Confirm your password')).toBeNull();
128
+ });
129
+
130
+ it('should show error message when new and confirm password do not match', () => {
131
+ store = mockStore({
132
+ ...initialState,
133
+ resetPassword: {
134
+ status: TOKEN_STATE.VALID,
135
+ },
136
+ });
137
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
138
+ const confirmPasswordInput = screen.getByLabelText('Confirm password');
139
+ fireEvent.change(confirmPasswordInput, { target: { value: 'password-mismatch' } });
140
+
141
+ const passwordsDoNotMatchError = screen.queryByText('Passwords do not match');
142
+ expect(passwordsDoNotMatchError).toBeTruthy();
143
+ });
144
+
145
+ // ******** alert message tests ********
146
+
147
+ it('should show reset password rate limit error', () => {
148
+ const validationMessage = 'Too many requests.An error has occurred because of too many requests. Please try again after some time.';
149
+ store = mockStore({
150
+ ...initialState,
151
+ resetPassword: {
152
+ status: PASSWORD_RESET.FORBIDDEN_REQUEST,
153
+ },
154
+ });
155
+
156
+ const { container } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
157
+
158
+ const alertElements = container.querySelectorAll('.alert-danger');
159
+ const rateLimitError = alertElements[0].textContent;
160
+ expect(rateLimitError).toBe(validationMessage);
161
+ });
162
+
163
+ it('should show reset password internal server error', () => {
164
+ const validationMessage = 'We couldn\'t reset your password.An error has occurred. Try refreshing the page, or check your internet connection.';
165
+ store = mockStore({
166
+ ...initialState,
167
+ resetPassword: {
168
+ status: PASSWORD_RESET.INTERNAL_SERVER_ERROR,
169
+ },
170
+ });
171
+
172
+ const { container } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
173
+ const alertElements = container.querySelectorAll('.alert-danger');
174
+ const internalServerError = alertElements[0].textContent;
175
+ expect(internalServerError).toBe(validationMessage);
176
+ });
177
+
178
+ // ******** miscellaneous tests ********
179
+
180
+ it('should call validation on password field when blur event fires', () => {
181
+ const resetPasswordPage = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
182
+ const expectedText = 'Password criteria has not been metPassword must contain at least 8 characters, at least one letter, and at least one number';
183
+ const newPasswordInput = resetPasswordPage.container.querySelector('input#newPassword');
184
+ newPasswordInput.value = 'test-password';
185
+ fireEvent.change(newPasswordInput);
186
+
187
+ fireEvent.blur(newPasswordInput);
188
+
189
+ const feedbackDiv = resetPasswordPage.container.querySelector('div[feedback-for="newPassword"]');
190
+ expect(feedbackDiv.textContent).toEqual(expectedText);
191
+ });
192
+
193
+ it('show spinner when api call is pending', () => {
194
+ store.dispatch = jest.fn(store.dispatch);
195
+ props = {
196
+ status:
197
+ TOKEN_STATE.PENDING,
198
+ };
199
+ useParams.mockReturnValue({ token });
200
+
201
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
202
+
203
+ expect(store.dispatch).toHaveBeenCalledWith(validateToken(token));
204
+
205
+ useParams.mockClear();
206
+ });
207
+ it('should redirect the user to Reset password email screen ', async () => {
208
+ props = {
209
+ status:
210
+ PASSWORD_RESET_ERROR,
211
+ };
212
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
213
+ expect(mockNavigate).toHaveBeenCalledWith(RESET_PAGE);
214
+ });
215
+ it('should redirect the user to root url of the application ', async () => {
216
+ props = {
217
+ status: SUCCESS,
218
+ };
219
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
220
+ expect(mockNavigate).toHaveBeenCalledWith(LOGIN_PAGE);
221
+ });
222
+
223
+ it('shows spinner during token validation', () => {
224
+ render(reduxWrapper(<IntlResetPasswordPage {...props} />));
225
+ const spinnerElement = document.getElementsByClassName('div.spinner-header');
226
+
227
+ expect(spinnerElement).toBeTruthy();
228
+ });
229
+
230
+ // ******** redirection tests ********
231
+
232
+ it('by clicking on sign in tab should redirect onto login page', async () => {
233
+ const { getByText } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
234
+
235
+ const signInTab = getByText('Sign in');
236
+
237
+ fireEvent.click(signInTab);
238
+
239
+ expect(mockNavigate).toHaveBeenCalledWith(LOGIN_PAGE);
240
+ });
241
+ });
package/src/routes.jsx ADDED
@@ -0,0 +1,79 @@
1
+ import { Navigate } from 'react-router';
2
+
3
+ import { EmbeddedRegistrationRoute, NotFoundPage, UnAuthOnlyRoute } from './common-components';
4
+ import { LOGIN_PAGE } from './data/constants';
5
+ import { ForgotPasswordPage } from './forgot-password';
6
+ import Logistration from './logistration/Logistration';
7
+ import { ProgressiveProfiling } from './progressive-profiling';
8
+ import { RegistrationPage } from './register';
9
+ import { ResetPasswordPage } from './reset-password';
10
+
11
+ import Main from './Main';
12
+
13
+ const routes = [
14
+ {
15
+ id: 'org.openedx.frontend.route.authn.main',
16
+ Component: Main,
17
+ children: [
18
+ {
19
+ path: 'register-embedded',
20
+ element: (
21
+ <EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>
22
+ ),
23
+ },
24
+ {
25
+ path: 'login',
26
+ handle: {
27
+ role: 'org.openedx.frontend.role.login',
28
+ },
29
+ element: (
30
+ <UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
31
+ ),
32
+ },
33
+ {
34
+ path: 'register',
35
+ handle: {
36
+ role: 'org.openedx.frontend.role.register',
37
+ },
38
+ element: (
39
+ <UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>
40
+ ),
41
+ },
42
+ {
43
+ path: 'reset',
44
+ handle: {
45
+ role: 'org.openedx.frontend.role.resetPassword',
46
+ },
47
+ element: (
48
+ <UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>
49
+ ),
50
+ },
51
+ {
52
+ path: 'password_reset_confirm/:token',
53
+ handle: {
54
+ role: 'org.openedx.frontend.role.confirmPassword',
55
+ },
56
+ element: (
57
+ <ResetPasswordPage />
58
+ ),
59
+ },
60
+ {
61
+ path: 'welcome',
62
+ handle: {
63
+ role: 'org.openedx.frontend.role.welcome',
64
+ },
65
+ element: (
66
+ <ProgressiveProfiling />
67
+ ),
68
+ },
69
+ {
70
+ path: 'notfound',
71
+ element: (
72
+ <NotFoundPage />
73
+ ),
74
+ },
75
+ ]
76
+ }
77
+ ];
78
+
79
+ export default routes;
@@ -0,0 +1,202 @@
1
+ @use "@openedx/paragon/scss/core/core" as paragon;
2
+
3
+ .layout {
4
+ display: flex;
5
+
6
+ @include paragon.media-breakpoint-down('lg') {
7
+ flex-direction: column;
8
+ justify-content: center;
9
+ align-items: center;
10
+ }
11
+
12
+ @include paragon.media-breakpoint-up('xl') {
13
+ justify-content: space-between;
14
+ }
15
+ }
16
+
17
+ .content {
18
+ @include paragon.media-breakpoint-up('xl') {
19
+ display: flex;
20
+ justify-content: center;
21
+ width: 50vw;
22
+ margin-top: 4rem;
23
+ }
24
+ }
25
+
26
+ .logo-small {
27
+ width: 44.67px;
28
+ margin-top: 1.25rem;
29
+ margin-left: 1.5rem;
30
+ }
31
+
32
+ .logo {
33
+ width: 71px;
34
+ margin-top: 2rem;
35
+ margin-left: 1.5rem;
36
+ }
37
+
38
+ .mw-320 {
39
+ max-width: 320px;
40
+ }
41
+
42
+ .main-heading {
43
+ position: relative;
44
+ z-index: 2;
45
+ }
46
+
47
+ .complete-your-profile {
48
+ font-weight: 700;
49
+ line-height: 1;
50
+
51
+ @include paragon.media-breakpoint-down('xl') {
52
+ font-size: 3.75rem;
53
+ }
54
+
55
+ @media (min-width: 1400px) {
56
+ font-size: 4.875rem;
57
+ }
58
+ }
59
+
60
+ .welcome-to-platform {
61
+ margin-bottom: 0.5rem;
62
+ font-weight: 700;
63
+
64
+ @include paragon.media-breakpoint-down('xl') {
65
+ font-size: 1.375rem;
66
+ line-height: 1.75rem;
67
+ }
68
+
69
+ @media (min-width: 1400px) {
70
+ font-size: 2rem;
71
+ line-height: 2.25rem;
72
+ }
73
+ }
74
+
75
+ .large-screen-left-container {
76
+ @include paragon.media-breakpoint-down('xl') {
77
+ flex: 0 0 25%;
78
+ max-width: 25%;
79
+ }
80
+
81
+ @media (min-width: 1400px) {
82
+ flex: 0 0 33.3333333333%;
83
+ max-width: 33.3333333333%;
84
+ }
85
+ }
86
+
87
+ .small-screen-top-stripe {
88
+ height: 0.25rem;
89
+ background-image: linear-gradient(102.02deg,
90
+ paragon.$brand-700,
91
+ paragon.$brand-700 20%,
92
+ paragon.$brand 20%,
93
+ );
94
+ background-repeat: no-repeat;
95
+ }
96
+
97
+ @include paragon.media-breakpoint-only('md') {
98
+ .medium-screen-top-stripe {
99
+ display: flex;
100
+ height: 0.5rem;
101
+ background-image: linear-gradient(102.02deg,
102
+ paragon.$brand-700,
103
+ paragon.$brand-700 10%,
104
+ paragon.$brand 10%,
105
+ paragon.$brand 90%,
106
+ paragon.$primary-700 90%,
107
+ paragon.$primary-700 100%,
108
+ );
109
+ background-repeat: no-repeat;
110
+ }
111
+ }
112
+
113
+ @include paragon.media-breakpoint-only('lg') {
114
+ .medium-screen-top-stripe {
115
+ display: flex;
116
+ height: 0.5rem;
117
+ background-image: linear-gradient(102.02deg,
118
+ paragon.$brand-700 10%,
119
+ paragon.$brand 10%,
120
+ paragon.$brand 65%,
121
+ paragon.$primary-700 65%,
122
+ paragon.$primary-700 75%,
123
+ paragon.$accent-a 75%,
124
+ paragon.$accent-a 75%);
125
+ background-repeat: no-repeat;
126
+ }
127
+ }
128
+
129
+ .extra-large-screen-top-stripe {
130
+ display: none;
131
+ }
132
+
133
+ @include paragon.media-breakpoint-up('xl') {
134
+ .extra-large-screen-top-stripe {
135
+ display: flex;
136
+ height: 0.5rem;
137
+ background-image: linear-gradient(102.02deg,
138
+ paragon.$brand-700 10%,
139
+ paragon.$brand 10%,
140
+ paragon.$brand 45%,
141
+ paragon.$primary-700 45%,
142
+ paragon.$primary-700 55%,
143
+ paragon.$accent-a 55%,
144
+ paragon.$accent-a 75%,
145
+ paragon.$info-200 75%,
146
+ );
147
+ background-repeat: no-repeat;
148
+ }
149
+ }
150
+
151
+ .large-screen-svg-light,
152
+ .large-screen-svg-primary {
153
+ fill: paragon.$light-200;
154
+ overflow: hidden;
155
+ position: absolute;
156
+ }
157
+
158
+ .large-screen-svg-primary {
159
+ fill: paragon.$primary-400;
160
+ }
161
+
162
+ .medium-screen-svg-light,
163
+ .medium-screen-svg-primary {
164
+ fill: paragon.$light-200;
165
+ overflow: inherit;
166
+ position: absolute;
167
+ }
168
+
169
+ .medium-screen-svg-primary {
170
+ fill: paragon.$primary-400;
171
+ }
172
+
173
+ [dir=rtl] {
174
+
175
+ .medium-screen-svg-light,
176
+ .medium-screen-svg-primary,
177
+ .large-screen-svg-light,
178
+ .large-screen-svg-primary {
179
+ transform: scaleX(-1);
180
+ }
181
+ }
182
+
183
+ .small-yellow-line {
184
+ width: 80px;
185
+ height: 0;
186
+ border: 2px solid paragon.$accent-b;
187
+ transform: rotate(102.02deg);
188
+ }
189
+
190
+ .medium-yellow-line {
191
+ width: 120px;
192
+ height: 0;
193
+ border: 3px solid paragon.$accent-b;
194
+ transform: rotate(102.02deg);
195
+ }
196
+
197
+ .large-yellow-line {
198
+ width: 240px;
199
+ height: 0;
200
+ border: 3px solid paragon.$accent-b;
201
+ transform: rotate(102.02deg);
202
+ }
@@ -0,0 +1,3 @@
1
+ .forgot-password--button {
2
+ min-width: 6rem;
3
+ }
@@ -0,0 +1,3 @@
1
+ .login-button-width {
2
+ min-width: 6rem;
3
+ }
@@ -0,0 +1,30 @@
1
+ @use "@openedx/paragon/scss/core/core" as paragon;
2
+
3
+ .pp-page__button-width {
4
+ min-width: 6rem;
5
+ }
6
+
7
+ .pp-page__support-link {
8
+ font-size: 0.875rem;
9
+ font-weight: 400;
10
+ }
11
+
12
+ .pp-page__heading {
13
+ line-height: 1.75rem;
14
+ font-size: 1.375rem;
15
+ margin-bottom: 0.5rem;
16
+ font-weight: 700;
17
+
18
+ @include paragon.media-breakpoint-down('md') {
19
+ line-height: 1.5rem;
20
+ font-size: 1.125rem;
21
+ }
22
+ }
23
+
24
+ @media (max-width: 464px) {
25
+ .pp-page__support-link {
26
+ font-size: 0.688rem;
27
+ font-weight: normal;
28
+ line-height: 0.938rem;
29
+ }
30
+ }