@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,31 @@
1
+ import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
2
+ import { Hyperlink, Image } from '@openedx/paragon';
3
+
4
+ import messages from './messages';
5
+
6
+ const SmallLayout = () => {
7
+ const { formatMessage } = useIntl();
8
+
9
+ return (
10
+ <span
11
+ className="w-100 bg-primary-500 banner__image small-layout"
12
+ style={{ backgroundImage: `url(${useAppConfig().BANNER_IMAGE_SMALL})` }}
13
+ >
14
+ <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
15
+ <Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
16
+ </Hyperlink>
17
+ <div className="ml-5 mr-1 pb-3.5 pt-3.5">
18
+ <h1 className="display-2">
19
+ <span className="text-light-500">
20
+ {formatMessage(messages['your.career.turning.point'])}{' '}
21
+ </span>
22
+ <span className="text-warning-300">
23
+ {formatMessage(messages['is.here'])}
24
+ </span>
25
+ </h1>
26
+ </div>
27
+ </span>
28
+ );
29
+ };
30
+
31
+ export default SmallLayout;
@@ -0,0 +1,4 @@
1
+ export { default as ImageLargeLayout } from './LargeLayout';
2
+ export { default as ImageMediumLayout } from './MediumLayout';
3
+ export { default as ImageSmallLayout } from './SmallLayout';
4
+ export { default as ImageExtraSmallLayout } from './ExtraSmallLayout';
@@ -0,0 +1,37 @@
1
+ .company-logo {
2
+ width: 71px;
3
+ margin-top: 2rem;
4
+ margin-left: 1.5rem;
5
+ }
6
+
7
+ @media (max-width: 576px) {
8
+ .company-logo {
9
+ width: 44.67px;
10
+ margin-top: 1.25rem;
11
+ margin-left: 1.5rem;
12
+ }
13
+ }
14
+
15
+ .banner__image {
16
+ background-size: cover;
17
+ background-repeat: no-repeat;
18
+ border:none;
19
+ }
20
+
21
+ @media (min-width: 464px) and (max-width: 575.98px) {
22
+ .banner__heading {
23
+ font-size: 60px;
24
+ font-weight: 700;
25
+ line-height: 60px;
26
+ letter-spacing: -1.2px;
27
+ }
28
+ }
29
+
30
+ @media (min-width: 768px) and (max-width: 800px) {
31
+ .banner__heading {
32
+ font-size: 60px !important;
33
+ font-weight: 700 !important;
34
+ line-height: 60px !important;
35
+ letter-spacing: -2px !important;
36
+ }
37
+ }
@@ -0,0 +1,16 @@
1
+ import { defineMessages } from '@openedx/frontend-base';
2
+
3
+ const messages = defineMessages({
4
+ 'your.career.turning.point': {
5
+ id: 'your.career.turning.point',
6
+ defaultMessage: 'Your career turning point',
7
+ description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
8
+ },
9
+ 'is.here': {
10
+ id: 'is.here',
11
+ defaultMessage: 'is here.',
12
+ description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
13
+ },
14
+ });
15
+
16
+ export default messages;
@@ -0,0 +1,46 @@
1
+ import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
2
+ import { Hyperlink, Image } from '@openedx/paragon';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import messages from './messages';
6
+
7
+ const LargeLayout = ({ fullName }) => {
8
+ const { formatMessage } = useIntl();
9
+
10
+ return (
11
+ <div className="w-50 d-flex">
12
+ <div className="col-md-10 bg-light-200 p-0">
13
+ <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
14
+ <Image className="logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
15
+ </Hyperlink>
16
+ <div className="min-vh-100 d-flex align-items-center">
17
+ <div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
18
+ <div>
19
+ <h1 className="welcome-to-platform data-hj-suppress">
20
+ {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
21
+ </h1>
22
+ <h2 className="complete-your-profile">
23
+ {formatMessage(messages['complete.your.profile.1'])}
24
+ <div className="text-accent-a">
25
+ {formatMessage(messages['complete.your.profile.2'])}
26
+ </div>
27
+ </h2>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <div className="col-md-2 bg-white p-0">
32
+ <svg className="m1-n1 w-100 h-100 large-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
33
+ <g transform="skewX(171.6)">
34
+ <rect x="0" y="0" height="100%" width="100%" />
35
+ </g>
36
+ </svg>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ LargeLayout.propTypes = {
43
+ fullName: PropTypes.string.isRequired,
44
+ };
45
+
46
+ export default LargeLayout;
@@ -0,0 +1,49 @@
1
+ import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
2
+ import { Hyperlink, Image } from '@openedx/paragon';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import messages from './messages';
6
+
7
+ const MediumLayout = ({ fullName }) => {
8
+ const { formatMessage } = useIntl();
9
+
10
+ return (
11
+ <>
12
+ <div className="w-100 medium-screen-top-stripe" />
13
+ <div className="w-100 p-0 mb-3 d-flex">
14
+ <div className="col-md-10 bg-light-200">
15
+ <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
16
+ <Image className="logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
17
+ </Hyperlink>
18
+ <div className="d-flex align-items-center justify-content-center mb-4 ml-5">
19
+ <div className="medium-yellow-line mt-5 mr-n2" />
20
+ <div>
21
+ <h1 className="h3 data-hj-suppress mw-320">
22
+ {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
23
+ </h1>
24
+ <h2 className="display-1">
25
+ {formatMessage(messages['complete.your.profile.1'])}
26
+ <div className="text-accent-a">
27
+ {formatMessage(messages['complete.your.profile.2'])}
28
+ </div>
29
+ </h2>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div className="col-md-2 bg-white p-0">
34
+ <svg className="w-100 h-100 medium-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
35
+ <g transform="skewX(168)">
36
+ <rect x="0" y="0" height="100%" width="100%" />
37
+ </g>
38
+ </svg>
39
+ </div>
40
+ </div>
41
+ </>
42
+ );
43
+ };
44
+
45
+ MediumLayout.propTypes = {
46
+ fullName: PropTypes.string.isRequired,
47
+ };
48
+
49
+ export default MediumLayout;
@@ -0,0 +1,38 @@
1
+ import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
2
+ import { Hyperlink, Image } from '@openedx/paragon';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import messages from './messages';
6
+
7
+ const SmallLayout = ({ fullName }) => {
8
+ const { formatMessage } = useIntl();
9
+
10
+ return (
11
+ <div className="min-vw-100 bg-light-200">
12
+ <div className="col-md-12 small-screen-top-stripe" />
13
+ <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
14
+ <Image className="logo-small" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
15
+ </Hyperlink>
16
+ <div className="d-flex align-items-center m-3.5">
17
+ <div className="small-yellow-line mt-4.5" />
18
+ <div>
19
+ <h1 className="h5 data-hj-suppress">
20
+ {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
21
+ </h1>
22
+ <h2 className="h1">
23
+ {formatMessage(messages['complete.your.profile.1'])}
24
+ <div className="text-accent-a">
25
+ {formatMessage(messages['complete.your.profile.2'])}
26
+ </div>
27
+ </h2>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ };
33
+
34
+ SmallLayout.propTypes = {
35
+ fullName: PropTypes.string.isRequired,
36
+ };
37
+
38
+ export default SmallLayout;
@@ -0,0 +1,3 @@
1
+ export { default as AuthLargeLayout } from './LargeLayout';
2
+ export { default as AuthMediumLayout } from './MediumLayout';
3
+ export { default as AuthSmallLayout } from './SmallLayout';
@@ -0,0 +1,21 @@
1
+ import { defineMessages } from '@openedx/frontend-base';
2
+
3
+ const messages = defineMessages({
4
+ 'welcome.to.platform': {
5
+ id: 'welcome.to.platform',
6
+ defaultMessage: 'Welcome to {siteName}, {fullName}!',
7
+ description: 'Welcome message that appears on progressive profile page',
8
+ },
9
+ 'complete.your.profile.1': {
10
+ id: 'complete.your.profile.1',
11
+ defaultMessage: 'Complete',
12
+ description: 'part of text "complete your profile"',
13
+ },
14
+ 'complete.your.profile.2': {
15
+ id: 'complete.your.profile.2',
16
+ defaultMessage: 'your profile',
17
+ description: 'part of text "complete your profile"',
18
+ },
19
+ });
20
+
21
+ export default messages;
@@ -0,0 +1,70 @@
1
+ import { useAppConfig } from '@openedx/frontend-base';
2
+ import { breakpoints } from '@openedx/paragon';
3
+ import classNames from 'classnames';
4
+ import PropTypes from 'prop-types';
5
+ import MediaQuery from 'react-responsive';
6
+
7
+ import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './components/default-layout';
8
+ import {
9
+ ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
10
+ } from './components/image-layout';
11
+ import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
12
+
13
+ const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
14
+ const enableImageLayout = useAppConfig().ENABLE_IMAGE_LAYOUT;
15
+
16
+ if (enableImageLayout) {
17
+ return (
18
+ <div className="layout">
19
+ <MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
20
+ {showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageExtraSmallLayout />}
21
+ </MediaQuery>
22
+ <MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
23
+ {showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageSmallLayout />}
24
+ </MediaQuery>
25
+ <MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
26
+ {showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <ImageMediumLayout />}
27
+ </MediaQuery>
28
+ <MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
29
+ {showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <ImageLargeLayout />}
30
+ </MediaQuery>
31
+ <div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
32
+ {children}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <>
40
+ <div className="col-md-12 extra-large-screen-top-stripe" />
41
+ <div className="layout">
42
+ <MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
43
+ {showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <DefaultSmallLayout />}
44
+ </MediaQuery>
45
+ <MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
46
+ {showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <DefaultMediumLayout />}
47
+ </MediaQuery>
48
+ <MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
49
+ {showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <DefaultLargeLayout />}
50
+ </MediaQuery>
51
+ <div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
52
+ {children}
53
+ </div>
54
+ </div>
55
+ </>
56
+ );
57
+ };
58
+
59
+ BaseContainer.defaultProps = {
60
+ showWelcomeBanner: false,
61
+ fullName: null,
62
+ };
63
+
64
+ BaseContainer.propTypes = {
65
+ children: PropTypes.node.isRequired,
66
+ showWelcomeBanner: PropTypes.bool,
67
+ fullName: PropTypes.string,
68
+ };
69
+
70
+ export default BaseContainer;
@@ -0,0 +1,44 @@
1
+ import { IntlProvider, mergeAppConfig } from '@openedx/frontend-base';
2
+ import { render } from '@testing-library/react';
3
+ import { Context as ResponsiveContext } from 'react-responsive';
4
+
5
+ import BaseContainer from '../index';
6
+ import { appId } from '../../constants';
7
+
8
+ const LargeScreen = {
9
+ wrappingComponent: ResponsiveContext.Provider,
10
+ wrappingComponentProps: { value: { width: 1200 } },
11
+ };
12
+
13
+ describe('Base component tests', () => {
14
+ it('should show default layout', () => {
15
+ const { container } = render(
16
+ <IntlProvider locale="en">
17
+ <BaseContainer>
18
+ <div>Test Content</div>
19
+ </BaseContainer>
20
+ </IntlProvider>,
21
+ LargeScreen,
22
+ );
23
+
24
+ expect(container.querySelector('.banner__image')).toBeNull();
25
+ expect(container.querySelector('.large-screen-svg-primary')).toBeDefined();
26
+ });
27
+
28
+ it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
29
+ mergeAppConfig(appId, {
30
+ ENABLE_IMAGE_LAYOUT: true,
31
+ });
32
+
33
+ const { container } = render(
34
+ <IntlProvider locale="en">
35
+ <BaseContainer showWelcomeBanner={false}>
36
+ <div>Test Content</div>
37
+ </BaseContainer>
38
+ </IntlProvider>,
39
+ LargeScreen,
40
+ );
41
+
42
+ expect(container.querySelector('.banner__image')).toBeDefined();
43
+ });
44
+ });
@@ -0,0 +1,26 @@
1
+ import PropTypes from 'prop-types';
2
+ import { Navigate } from 'react-router-dom';
3
+
4
+ import { PAGE_NOT_FOUND } from '../data/constants';
5
+ import { isHostAvailableInQueryParams } from '../data/utils';
6
+
7
+ /**
8
+ * This wrapper redirects the requester to embedded register page only if host
9
+ * query param is present.
10
+ */
11
+ const EmbeddedRegistrationRoute = ({ children }) => {
12
+ const registrationEmbedded = isHostAvailableInQueryParams();
13
+
14
+ // Show registration page for embedded experience even if the user is authenticated
15
+ if (registrationEmbedded) {
16
+ return children;
17
+ }
18
+
19
+ return <Navigate to={PAGE_NOT_FOUND} replace />;
20
+ };
21
+
22
+ EmbeddedRegistrationRoute.propTypes = {
23
+ children: PropTypes.node.isRequired,
24
+ };
25
+
26
+ export default EmbeddedRegistrationRoute;
@@ -0,0 +1,110 @@
1
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2
+ import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
3
+ import {
4
+ Button, Form,
5
+ Icon,
6
+ } from '@openedx/paragon';
7
+ import { Login } from '@openedx/paragon/icons';
8
+ import PropTypes from 'prop-types';
9
+
10
+ import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
11
+ import messages from './messages';
12
+
13
+ /**
14
+ * This component renders the Single sign-on (SSO) button only for the tpa provider passed
15
+ * */
16
+ const EnterpriseSSO = (props) => {
17
+ const { formatMessage } = useIntl();
18
+ const tpaProvider = props.provider;
19
+ const hideRegistrationLink = useAppConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
20
+ || useAppConfig().SHOW_REGISTRATION_LINKS === false;
21
+
22
+ const handleSubmit = (e, url) => {
23
+ e.preventDefault();
24
+ window.location.href = getSiteConfig().lmsBaseUrl + url;
25
+ };
26
+
27
+ const handleClick = (e) => {
28
+ e.preventDefault();
29
+ window.location.href = LOGIN_PAGE;
30
+ };
31
+
32
+ if (tpaProvider) {
33
+ return (
34
+ <div className="d-flex justify-content-center m-4">
35
+ <div className="d-flex flex-column">
36
+ <div className="mw-450">
37
+ <Form className="m-0">
38
+ <p>{formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}</p>
39
+ <Button
40
+ id={tpaProvider.id}
41
+ key={tpaProvider.id}
42
+ type="submit"
43
+ variant="link"
44
+ className={`btn-tpa btn-${tpaProvider.id}`}
45
+ onClick={(e) => handleSubmit(e, tpaProvider.loginUrl)}
46
+ >
47
+ {tpaProvider.iconImage ? (
48
+ <div aria-hidden="true">
49
+ <img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
50
+ <span className="pl-2" aria-hidden="true">{tpaProvider.name}</span>
51
+ </div>
52
+ )
53
+ : (
54
+ <>
55
+ <div className="btn-tpa__font-container" aria-hidden="true">
56
+ {SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? (
57
+ <FontAwesomeIcon icon={['fab', tpaProvider.iconClass]} />)
58
+ : (
59
+ <Icon className="h-75" src={Login} />
60
+ )}
61
+ </div>
62
+ <span className="pl-2" aria-hidden="true">{tpaProvider.name}</span>
63
+ </>
64
+ )}
65
+ </Button>
66
+ <div className="mb-4" />
67
+ <Button
68
+ type="submit"
69
+ id="other-ways-to-sign-in"
70
+ variant="outline-primary"
71
+ state="Complete"
72
+ className="w-100"
73
+ onClick={(e) => handleClick(e)}
74
+ >
75
+ {hideRegistrationLink
76
+ ? formatMessage(messages['enterprisetpa.login.button.text.public.account.creation.disabled'])
77
+ : formatMessage(messages['enterprisetpa.login.button.text'])}
78
+ </Button>
79
+ </Form>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+ return <div />;
86
+ };
87
+
88
+ EnterpriseSSO.defaultProps = {
89
+ provider: {
90
+ id: '',
91
+ name: '',
92
+ iconClass: '',
93
+ iconImage: '',
94
+ loginUrl: '',
95
+ registerUrl: '',
96
+ },
97
+ };
98
+
99
+ EnterpriseSSO.propTypes = {
100
+ provider: PropTypes.shape({
101
+ id: PropTypes.string,
102
+ name: PropTypes.string,
103
+ iconClass: PropTypes.string,
104
+ iconImage: PropTypes.string,
105
+ loginUrl: PropTypes.string,
106
+ registerUrl: PropTypes.string,
107
+ }),
108
+ };
109
+
110
+ export default EnterpriseSSO;
@@ -0,0 +1,106 @@
1
+ import { useState } from 'react';
2
+
3
+ import {
4
+ Form, TransitionReplace,
5
+ } from '@openedx/paragon';
6
+ import PropTypes from 'prop-types';
7
+
8
+ const FormGroup = (props) => {
9
+ const [hasFocus, setHasFocus] = useState(false);
10
+
11
+ const handleFocus = (e) => {
12
+ setHasFocus(true);
13
+ if (props.handleFocus) { props.handleFocus(e); }
14
+ };
15
+ const handleClick = (e) => {
16
+ if (props.handleClick) { props.handleClick(e); }
17
+ };
18
+ const handleOnBlur = (e) => {
19
+ setHasFocus(false);
20
+ if (props.handleBlur) { props.handleBlur(e); }
21
+ };
22
+
23
+ return (
24
+ <Form.Group controlId={props.name} className={props.className} isInvalid={props.errorMessage !== ''}>
25
+ <Form.Control
26
+ as={props.as}
27
+ readOnly={props.readOnly}
28
+ type={props.type}
29
+ aria-invalid={props.errorMessage !== ''}
30
+ className="form-group__form-field"
31
+ autoComplete={props.autoComplete}
32
+ spellCheck={props.spellCheck}
33
+ name={props.name}
34
+ value={props.value}
35
+ onFocus={handleFocus}
36
+ onBlur={handleOnBlur}
37
+ onClick={handleClick}
38
+ onChange={props.handleChange}
39
+ controlClassName={props.borderClass}
40
+ trailingElement={props.trailingElement}
41
+ floatingLabel={props.floatingLabel}
42
+ >
43
+ {props.options ? props.options() : null}
44
+ </Form.Control>
45
+ <TransitionReplace>
46
+ {hasFocus && props.helpText ? (
47
+ <Form.Control.Feedback type="default" key="help-text" className="d-block form-text-size">
48
+ {props.helpText.map((message, index) => (
49
+ <span key={`help-text-${index.toString()}`}>
50
+ {message}
51
+ <br />
52
+ </span>
53
+ ))}
54
+ </Form.Control.Feedback>
55
+ ) : <div key="empty" />}
56
+ </TransitionReplace>
57
+ {props.errorMessage !== '' && (
58
+ <Form.Control.Feedback key="error" className="form-text-size" hasIcon={false} feedback-for={props.name} type="invalid">{props.errorMessage}</Form.Control.Feedback>
59
+ )}
60
+ {props.children}
61
+ </Form.Group>
62
+ );
63
+ };
64
+
65
+ FormGroup.defaultProps = {
66
+ as: 'input',
67
+ autoComplete: null,
68
+ borderClass: '',
69
+ children: null,
70
+ className: '',
71
+ errorMessage: '',
72
+ handleBlur: null,
73
+ handleChange: () => {},
74
+ handleClick: null,
75
+ handleFocus: null,
76
+ helpText: [],
77
+ options: null,
78
+ readOnly: false,
79
+ spellCheck: null,
80
+ trailingElement: null,
81
+ type: 'text',
82
+ };
83
+
84
+ FormGroup.propTypes = {
85
+ as: PropTypes.string,
86
+ autoComplete: PropTypes.string,
87
+ borderClass: PropTypes.string,
88
+ children: PropTypes.element,
89
+ className: PropTypes.string,
90
+ errorMessage: PropTypes.string,
91
+ floatingLabel: PropTypes.string.isRequired,
92
+ handleBlur: PropTypes.func,
93
+ handleChange: PropTypes.func,
94
+ handleClick: PropTypes.func,
95
+ handleFocus: PropTypes.func,
96
+ helpText: PropTypes.arrayOf(PropTypes.string),
97
+ name: PropTypes.string.isRequired,
98
+ options: PropTypes.func,
99
+ readOnly: PropTypes.bool,
100
+ spellCheck: PropTypes.string,
101
+ trailingElement: PropTypes.element,
102
+ type: PropTypes.string,
103
+ value: PropTypes.string.isRequired,
104
+ };
105
+
106
+ export default FormGroup;