Compare commits

..

1 Commits

Author SHA1 Message Date
Muhammad Abdullah Waheed
826f631201 Merge pull request #1319 from openedx/abdullahwaheed/update-2u-main-temp
[test] Abdullahwaheed/update 2u main temp
2024-09-13 18:03:08 +05:00
67 changed files with 6112 additions and 18635 deletions

5
.env
View File

@@ -16,9 +16,6 @@ SITE_NAME=null
INFO_EMAIL=''
# ***** Cookies *****
USER_RETENTION_COOKIE_NAME=null
# ***** Cohesion Keys *****
COHESION_WRITE_KEY=''
COHESION_SOURCE_KEY=''
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK=''
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
@@ -44,5 +41,3 @@ BANNER_IMAGE_EXTRA_SMALL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -25,9 +25,6 @@ ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
# ***** Cookies *****
SESSION_COOKIE_DOMAIN='localhost'
USER_INFO_COOKIE_NAME='edx-user-info'
# ***** Cohesion Keys *****
COHESION_WRITE_KEY=''
COHESION_SOURCE_KEY=''
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
@@ -44,5 +41,3 @@ APP_ID=''
MFE_CONFIG_API_URL=''
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -18,6 +18,3 @@ SEGMENT_KEY=''
SITE_NAME='Your Platform Name Here'
APP_ID=''
MFE_CONFIG_API_URL=''
COHESION_WRITE_KEY=''
COHESION_SOURCE_KEY=''
PARAGON_THEME_URLS={}

View File

@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@openedx/frontend-build');
const config = createConfig('eslint', {
module.exports = createConfig('eslint', {
rules: {
// Temporarily update the 'indent', 'template-curly-spacing' and
// 'no-multiple-empty-lines' rules since they are causing eslint
@@ -50,14 +50,3 @@ const config = createConfig('eslint', {
'function-paren-newline': 'off',
},
});
config.settings = {
'import/resolver': {
node: {
paths: ['src', 'node_modules'],
extensions: ['.js', '.jsx'],
},
},
};
module.exports = config;

View File

@@ -1,7 +0,0 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -10,7 +10,7 @@ on:
jobs:
autoupdate:
name: autoupdate
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1
env:

View File

@@ -10,15 +10,18 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
strategy:
matrix:
node: [18, 20]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Nodejs
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
node-version: ${{ matrix.node }}
- name: Install Dependencies
run: npm ci
@@ -39,7 +42,7 @@ jobs:
run: npm run build
- name: Run Code Coverage
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

View File

@@ -29,13 +29,7 @@ Getting Started
Installation
============
`Tutor`_ is currently recommended as a development environment for your new MFE. Please refer to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
Devstack (Deprecated) instructions
==================================
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
@@ -57,7 +51,7 @@ Devstack (Deprecated) instructions
Environment Variables/Setup Notes
=================================
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
The authentication micro-frontend also requires the following additional variable:
@@ -148,13 +142,13 @@ Furthermore, there are several edX-specific environment variables that enable in
- ``true`` | ``''`` (empty strings are falsy)
For more information see the document: `Micro-frontend applications in Open
edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
How To Contribute
=================
Contributions are very welcome, and strongly encouraged! We've
put together `some documentation that describes our contribution process <https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html>`_.
put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
Even though they were written with edx-platform in mind, the guidelines should be followed for Open edX code in general.

View File

@@ -12,7 +12,6 @@ metadata:
icon: 'Article'
annotations:
openedx.org/arch-interest-groups: ""
openedx.org/release: "master"
spec:
owner: group:2u-infinity
type: 'service'

View File

@@ -3,7 +3,7 @@ Enable Social Auth Locally
Please follow the steps below to enable social auth (SSO) locally.
1. Follow `Enabling Third Party Authentication <https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/tpa/index.html>`_ for backend configuration.
1. Follow `Enabling Third Party Authentication <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/tpa/index.html>`_ for backend configuration.
2. Authn has a component for rendering Social Auth providers at frontend which goes through each provider.

View File

@@ -1,60 +0,0 @@
/*
Authn MFE is now able to handle JS-based configuration!
For the time being, the `.env.*` files are still made available when cloning down this repo or pulling from
the master branch. To switch to using `env.config.js`, make a copy of `example.env.config.js` and configure as needed.
For testing with Jest Snapshot, there is a mock in `/src/setupTest.jsx` for `getConfig` that will need to be
uncommented.
Note: having both .env and env.config.js files will follow a predictable order, in which non-empty values in the
JS-based config will overwrite the .env environment variables.
frontend-platform's getConfig loads configuration in the following sequence:
- .env file config
- optional handlers (commonly used to merge MFE-specific config in via additional process.env variables)
- env.config.js file config
- runtime config
*/
module.exports = {
NODE_ENV: 'development',
NODE_PATH: './src',
PORT: 1999,
ACCESS_TOKEN_COOKIE_NAME: 'edx-jwt-cookie-header-payload',
BASE_URL: 'http://localhost:1999',
CREDENTIALS_BASE_URL: 'http://localhost:18150',
CSRF_TOKEN_API_PATH: '/csrf/api/v1/token',
ECOMMERCE_BASE_URL: 'http://localhost:18130',
LANGUAGE_PREFERENCE_COOKIE_NAME: 'openedx-language-preference',
LMS_BASE_URL: 'http://localhost:18000',
LOGIN_URL: 'http://localhost:1999/login',
LOGOUT_URL: 'http://localhost:18000/logout',
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg',
LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg',
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
MARKETING_SITE_BASE_URL: 'http://localhost:18000',
ORDER_HISTORY_URL: 'http://localhost:1996/orders',
REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login_refresh',
SEGMENT_KEY: '',
SITE_NAME: 'Your Platform Name Here',
INFO_EMAIL: 'info@example.com',
ENABLE_DYNAMIC_REGISTRATION_FIELDS: 'true',
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: 'true',
SESSION_COOKIE_DOMAIN: 'localhost',
USER_INFO_COOKIE_NAME: 'edx-user-info',
LOGIN_ISSUE_SUPPORT_LINK: 'http://localhost:18000/login-issue-support-url',
TOS_AND_HONOR_CODE: 'http://localhost:18000/honor',
TOS_LINK: 'http://localhost:18000/tos',
PRIVACY_POLICY: 'http://localhost:18000/privacy',
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: 'http://localhost:1999/welcome',
BANNER_IMAGE_LARGE: '',
BANNER_IMAGE_MEDIUM: '',
BANNER_IMAGE_SMALL: '',
BANNER_IMAGE_EXTRA_SMALL: '',
APP_ID: '',
MFE_CONFIG_API_URL: '',
ZENDESK_KEY: '',
ZENDESK_LOGO_URL: '',
};

8
openedx.yaml Normal file
View File

@@ -0,0 +1,8 @@
# This file describes this Open edX repo, as described in OEP-2:
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
nick: Authn MFE
oeps: {}
owner: openedx/2u-infinity
openedx-release:
ref: master

23647
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,8 @@
"build": "fedx-scripts webpack",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/authn/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"husky": {
@@ -35,56 +33,53 @@
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-platform": "^8.3.1",
"@edx/frontend-platform": "^8.0.0",
"@edx/openedx-atlas": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@openedx/frontend-plugin-framework": "^1.3.0",
"@openedx/paragon": "^23.4.2",
"@openedx/paragon": "^22.1.1",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.3.0",
"@testing-library/react": "^16.2.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"algoliasearch": "^4.14.3",
"algoliasearch-helper": "^3.26.0",
"algoliasearch-helper": "^3.14.0",
"classnames": "2.5.1",
"core-js": "3.43.0",
"core-js": "3.38.1",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "6.1.5",
"prop-types": "15.8.1",
"query-string": "7.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "6.1.0",
"react-loading-skeleton": "3.5.0",
"react-loading-skeleton": "3.4.0",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "6.30.1",
"react-router-dom": "6.30.1",
"react-router": "6.26.1",
"react-router-dom": "6.26.1",
"react-zendesk": "^0.1.13",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.5",
"redux-mock-store": "1.5.4",
"redux-saga": "1.3.0",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1",
"reselect": "5.1.1",
"universal-cookie": "7.2.2"
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/reactifex": "1.1.0",
"@openedx/frontend-build": "^14.6.1",
"copy-webpack-plugin": "^11.0.0",
"babel-plugin-formatjs": "10.5.39",
"eslint-plugin-import": "2.32.0",
"@openedx/frontend-build": "^14.0.3",
"babel-plugin-formatjs": "10.5.16",
"eslint-plugin-import": "2.29.1",
"glob": "7.2.3",
"history": "5.3.0",
"husky": "9.1.7",
"jest": "30.0.4",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.4.0"
"husky": "7.0.4",
"jest": "29.7.0",
"react-test-renderer": "^17.0.2"
}
}

View File

@@ -1,32 +1,25 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en-us">
<head>
<title>
<%= (process.env.SITE_NAME && process.env.SITE_NAME !='null' ) ?
'Authentication | ' + process.env.SITE_NAME : 'Authentication' %>
</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<link
rel="shortcut icon"
href="<%=htmlWebpackPlugin.options.FAVICON_URL%>"
type="image/x-icon"
/>
<script defer src="https://www.edx.org/beam-wrapper.js" ></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.4.4/iframeResizer.contentWindow.min.js"
integrity="sha512-IWwZFBvHzN41wNI6etRLLuLrDDj/6AwJcPt7cmKJAzluYTIHHQ1PF8wh0rSy05jxEvvjflVvH2MxeV6riyEEXg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<% if (process.env.OPTIMIZELY_URL) { %>
<script src="<%= process.env.OPTIMIZELY_URL %>"></script>
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
<script src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"></script>
<% } %>
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.4.4/iframeResizer.contentWindow.min.js"
integrity="sha512-IWwZFBvHzN41wNI6etRLLuLrDDj/6AwJcPt7cmKJAzluYTIHHQ1PF8wh0rSy05jxEvvjflVvH2MxeV6riyEEXg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<% if (process.env.OPTIMIZELY_URL) { %>
<script
src="<%= process.env.OPTIMIZELY_URL %>"
></script>
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
<script
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
></script>
<% } %>
</head>
<body>
<div id="root"></div>
</body>

View File

@@ -1,2 +0,0 @@
User-agent: *
Disallow: /

View File

@@ -6,7 +6,7 @@ import { Helmet } from 'react-helmet';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, RouteTracker, UnAuthOnlyRoute,
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
} from './common-components';
import configureStore from './data/configureStore';
import {
@@ -22,7 +22,6 @@ import {
import { updatePathWithQueryParams } from './data/utils';
import { ForgotPasswordPage } from './forgot-password';
import Logistration from './logistration/Logistration';
import MainAppSlot from './plugin-slots/MainAppSlot';
import { ProgressiveProfiling } from './progressive-profiling';
import { RecommendationsPage } from './recommendations';
import { RegistrationPage } from './register';
@@ -37,6 +36,7 @@ const MainApp = () => (
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
{getConfig().ZENDESK_KEY && <Zendesk />}
<Routes>
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
<Route
@@ -57,8 +57,6 @@ const MainApp = () => (
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
</Routes>
<RouteTracker />
<MainAppSlot />
</AppProvider>
);

View File

@@ -1,22 +0,0 @@
export const PAGE_TYPES = {
ACCOUNT_CREATION: 'account-creation',
SIGN_IN: 'sign-in',
};
export const ELEMENT_TYPES = {
BUTTON: 'BUTTON',
};
export const EVENT_TYPES = { ElementClicked: 'redventures.usertracking.v3.ElementClicked' };
export const ELEMENT_TEXT = {
CREATE_ACCOUNT: 'create-account',
OPT_IN_TEXT: 'I agree that edx may send me marketing messages',
SIGN_IN: 'Sign In',
};
export const ELEMENT_NAME = {
SIGN_IN: PAGE_TYPES.SIGN_IN,
OPT_OUT: 'opt-out',
CREATE_ACCOUNT: 'Create an account for free',
};

View File

@@ -1,6 +0,0 @@
export const SET_COHESION_EVENT_ELEMENT_STATES = 'SET_COHESION_EVENT_ELEMENT_STATES';
export const setCohesionEventStates = (eventData) => ({
type: SET_COHESION_EVENT_ELEMENT_STATES,
payload: eventData,
});

View File

@@ -1,17 +0,0 @@
import { SET_COHESION_EVENT_ELEMENT_STATES } from './actions';
export const storeName = 'cohesion';
export const defaultState = {
eventData: {},
};
export const reducer = (state = defaultState, action = {}) => {
if (action.type === SET_COHESION_EVENT_ELEMENT_STATES) {
return {
...state,
eventData: action.payload,
};
}
return state;
};

View File

@@ -1,24 +0,0 @@
import { EVENT_TYPES } from './constants';
/**
* Tracks cohesion events by setting the page type and tracking a click event.
*
* @param {string} pageType - The type of page where the event occurred.
* @param {string} elementType - The type of the web element (e.g., 'BUTTON', 'LINK').
* @param {string} webElementText - The text content of the web element.
* @param {string} webElementName - The name of the web element.
*/
const trackCohesionEvent = (eventData) => {
window.chsn_pageType = eventData.pageType;
const webElement = {
elementType: eventData.elementType,
text: eventData.webElementText,
name: eventData.webElementName,
};
window.tagular?.('beam', {
'@type': EVENT_TYPES.ElementClicked,
webElement,
});
};
export default trackCohesionEvent;

View File

@@ -1,6 +0,0 @@
const mockTagular = () => {
const getTagular = jest.fn();
window.tagular = getTagular;
};
export default mockTagular;

View File

@@ -1,15 +1,11 @@
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import { Navigate } from 'react-router-dom';
import trackCohesionEvent from '../cohesion/trackers';
import {
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
} from '../data/constants';
import setCookie from '../data/utils/cookies';
import { redirectWithDelay } from '../data/utils/dataUtils';
const RedirectLogistration = (props) => {
const {
@@ -24,16 +20,10 @@ const RedirectLogistration = (props) => {
userId,
registrationEmbedded,
host,
currectProvider,
} = props;
const cohesionEventData = useSelector(state => state.cohesion.eventData);
let finalRedirectUrl = '';
if (success) {
// This event is used by cohesion upon successful login and registration
if (!currectProvider) {
trackCohesionEvent(cohesionEventData);
}
// If we're in a third party auth pipeline, we must complete the pipeline
// once user has successfully logged in. Otherwise, redirect to the specified redirect url.
// Note: For multiple enterprise use case, we need to make sure that user first visits the
@@ -85,7 +75,8 @@ const RedirectLogistration = (props) => {
/>
);
}
redirectWithDelay(finalRedirectUrl);
window.location.href = finalRedirectUrl;
}
return null;
@@ -103,7 +94,6 @@ RedirectLogistration.defaultProps = {
userId: null,
registrationEmbedded: false,
host: '',
currectProvider: '',
};
RedirectLogistration.propTypes = {
@@ -118,7 +108,6 @@ RedirectLogistration.propTypes = {
userId: PropTypes.number,
registrationEmbedded: PropTypes.bool,
host: PropTypes.string,
currectProvider: PropTypes.string,
};
export default RedirectLogistration;

View File

@@ -1,15 +0,0 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const RouteTracker = () => {
const location = useLocation();
useEffect(() => {
window.tagular?.('pageView');
}, [location]);
return null;
};
export default RouteTracker;

View File

@@ -9,35 +9,22 @@ import { Login } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
import messages from './messages';
import { ELEMENT_TYPES, PAGE_TYPES } from '../cohesion/constants';
import trackCohesionEvent from '../cohesion/trackers';
import {
LOGIN_PAGE, REGISTER_PAGE, SUPPORTED_ICON_CLASSES,
} from '../data/constants';
import { LOGIN_PAGE, REGISTER_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import { setCookie } from '../data/utils';
import { redirectWithDelay } from '../data/utils/dataUtils';
const SocialAuthProviders = (props) => {
const { formatMessage } = useIntl();
const { referrer, socialAuthProviders } = props;
const registrationFields = useSelector(state => state.register.registrationFormData);
function handleSubmit(e, providerName) {
function handleSubmit(e) {
e.preventDefault();
const eventData = {
pageType: referrer === LOGIN_PAGE ? PAGE_TYPES.SIGN_IN : PAGE_TYPES.ACCOUNT_CREATION,
elementType: ELEMENT_TYPES.BUTTON,
webElementText: providerName,
webElementName: providerName.toLowerCase(),
};
// This event is used by cohesion upon successful login
trackCohesionEvent(eventData);
if (referrer === REGISTER_PAGE) {
setCookie('marketingEmailsOptIn', registrationFields?.configurableFormFields?.marketingEmailsOptIn);
}
const url = e.currentTarget.dataset.providerUrl;
redirectWithDelay(getConfig().LMS_BASE_URL + url);
window.location.href = getConfig().LMS_BASE_URL + url;
}
const socialAuth = socialAuthProviders.map((provider, index) => (
@@ -47,7 +34,7 @@ const SocialAuthProviders = (props) => {
type="button"
className={`btn-social btn-${provider.id} ${index % 2 === 0 ? 'mr-3' : ''}`}
data-provider-url={referrer === LOGIN_PAGE ? provider.loginUrl : provider.registerUrl}
onClick={(event) => handleSubmit(event, provider?.name)}
onClick={handleSubmit}
>
{provider.iconImage ? (
<div aria-hidden="true">

View File

@@ -13,15 +13,9 @@ export const getThirdPartyAuthContextBegin = () => ({
type: THIRD_PARTY_AUTH_CONTEXT.BEGIN,
});
export const getThirdPartyAuthContextSuccess = (
fieldDescriptions,
optionalFields,
thirdPartyAuthContext,
countriesCodesList) => ({
export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalFields, thirdPartyAuthContext) => ({
type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
payload: {
fieldDescriptions, optionalFields, thirdPartyAuthContext, countriesCodesList,
},
payload: { fieldDescriptions, optionalFields, thirdPartyAuthContext },
});
export const getThirdPartyAuthContextFailure = () => ({

View File

@@ -77,7 +77,3 @@ export const progressiveProfilingFields = {
},
},
};
export const FIELD_LABELS = {
COUNTRY: 'country',
};

View File

@@ -35,7 +35,6 @@ const reducer = (state = defaultState, action = {}) => {
optionalFields: action.payload.optionalFields,
thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
thirdPartyAuthApiStatus: COMPLETE_STATE,
countriesCodesList: action.payload.countriesCodesList,
};
}
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:

View File

@@ -10,7 +10,6 @@ import {
} from './actions';
import { progressiveProfilingFields, registerFields } from './constants';
import {
getCountryList,
getThirdPartyAuthContext,
} from './service';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
@@ -21,7 +20,6 @@ export function* fetchThirdPartyAuthContext(action) {
const {
fieldDescriptions, optionalFields, thirdPartyAuthContext,
} = yield call(getThirdPartyAuthContext, action.payload.urlParams);
const countriesCodesList = (yield call(getCountryList)) || [];
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
// hard code country field, level of education and gender fields
@@ -30,15 +28,9 @@ export function* fetchThirdPartyAuthContext(action) {
registerFields,
progressiveProfilingFields,
thirdPartyAuthContext,
countriesCodesList,
));
} else {
yield put(getThirdPartyAuthContextSuccess(
fieldDescriptions,
optionalFields,
thirdPartyAuthContext,
countriesCodesList,
));
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext));
}
} catch (e) {
yield put(getThirdPartyAuthContextFailure());

View File

@@ -1,8 +1,5 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logError } from '@edx/frontend-platform/logging';
import { FIELD_LABELS } from './constants';
// eslint-disable-next-line import/prefer-default-export
export async function getThirdPartyAuthContext(urlParams) {
@@ -26,28 +23,3 @@ export async function getThirdPartyAuthContext(urlParams) {
thirdPartyAuthContext: data.contextData || {},
};
}
function extractCountryList(data) {
return data?.fields
.find(({ name }) => name === FIELD_LABELS.COUNTRY)
?.options?.map(({ value }) => (value)) || [];
}
export async function getCountryList() {
try {
const requestConfig = {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
isPublic: true,
};
const { data } = await getAuthenticatedHttpClient()
.get(
`${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`,
requestConfig,
);
return extractCountryList(data);
} catch (e) {
logError(e);
return [];
}
}

View File

@@ -8,11 +8,6 @@ import * as api from '../service';
const { loggingService } = initializeMockLogging();
jest.mock('../service', () => ({
getCountryList: jest.fn(),
getThirdPartyAuthContext: jest.fn(),
}));
describe('fetchThirdPartyAuthContext', () => {
const params = {
payload: { urlParams: {} },
@@ -36,7 +31,6 @@ describe('fetchThirdPartyAuthContext', () => {
thirdPartyAuthContext: data,
fieldDescriptions: {},
optionalFields: {},
countriesCodesList: [],
}));
const dispatched = [];
@@ -50,7 +44,7 @@ describe('fetchThirdPartyAuthContext', () => {
expect(dispatched).toEqual([
actions.getThirdPartyAuthContextBegin(),
setCountryFromThirdPartyAuthContext(),
actions.getThirdPartyAuthContextSuccess({}, {}, data, []),
actions.getThirdPartyAuthContextSuccess({}, {}, data),
]);
getThirdPartyAuthContext.mockClear();
});

View File

@@ -2,7 +2,6 @@ export { default as RedirectLogistration } from './RedirectLogistration';
export { default as registerIcons } from './RegisterFaIcons';
export { default as EmbeddedRegistrationRoute } from './EmbeddedRegistrationRoute';
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
export { default as RouteTracker } from './RouteTracker';
export { default as NotFoundPage } from './NotFoundPage';
export { default as SocialAuthProviders } from './SocialAuthProviders';
export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert';

View File

@@ -1,6 +1,5 @@
import { combineReducers } from 'redux';
import { reducer as cohesionReducer, storeName as cohesionStoreName } from '../cohesion/data/reducers';
import {
reducer as commonComponentsReducer,
storeName as commonComponentsStoreName,
@@ -32,7 +31,6 @@ const createRootReducer = () => combineReducers({
[commonComponentsStoreName]: commonComponentsReducer,
[forgotPasswordStoreName]: forgotPasswordReducer,
[resetPasswordStoreName]: resetPasswordReducer,
[cohesionStoreName]: cohesionReducer,
[authnProgressiveProfilingStoreName]: authnProgressiveProfilingReducers,
});
export default createRootReducer;

View File

@@ -81,9 +81,3 @@ export const isHostAvailableInQueryParams = () => {
const queryParams = getAllPossibleQueryParams();
return 'host' in queryParams;
};
export const redirectWithDelay = (redirectUrl) => {
setTimeout(() => {
window.location.href = redirectUrl;
}, 1000);
};

View File

@@ -1,36 +1,27 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import React, { StrictMode } from 'react';
import React from 'react';
import ReactDOM from 'react-dom';
import {
APP_INIT_ERROR, APP_READY, initialize, mergeConfig, subscribe,
} from '@edx/frontend-platform';
import { ErrorPage } from '@edx/frontend-platform/react';
import { createRoot } from 'react-dom/client';
import configuration from './config';
import messages from './i18n';
import MainApp from './MainApp';
subscribe(APP_READY, () => {
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<MainApp />
</StrictMode>,
ReactDOM.render(
<MainApp />,
document.getElementById('root'),
);
});
subscribe(APP_INIT_ERROR, (error) => {
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<ErrorPage message={error.message} />
</StrictMode>,
);
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
});
initialize({

View File

@@ -1,2 +1,6 @@
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
@import "~@edx/brand/paragon/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "sass/style";

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert, Hyperlink } from '@openedx/paragon';
import { Alert } from '@openedx/paragon';
import { CheckCircle, Error } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
@@ -36,17 +36,19 @@ const AccountActivationMessage = ({ messageType }) => {
break;
}
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
const supportEmail = (
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
const supportLink = (
<Alert.Link href={getConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
{formatMessage(messages['account.activation.support.link'])}
</Alert.Link>
);
heading = formatMessage(messages[`account.${activationOrConfirmation}.error.message.title`]);
activationMessage = (
<FormattedMessage
id="account.activation.error.message"
defaultMessage="Something went wrong, please contact {supportEmail} to resolve this issue."
defaultMessage="Something went wrong, please {supportLink} to resolve this issue."
description="Account activation error message"
values={{ supportEmail }}
values={{ supportLink }}
/>
);
break;

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
@@ -11,23 +10,19 @@ import PropTypes from 'prop-types';
import { Link, useNavigate } from 'react-router-dom';
import messages from './messages';
import trackCohesionEvent from '../cohesion/trackers';
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
import { updatePathWithQueryParams } from '../data/utils';
import { redirectWithDelay } from '../data/utils/dataUtils';
import useMobileResponsive from '../data/utils/useMobileResponsive';
const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
const isMobileView = useMobileResponsive();
const [redirectToResetPasswordPage, setRedirectToResetPasswordPage] = useState(false);
const cohesionEventData = useSelector(state => state.cohesion.eventData);
const handlers = {
handleToggleOff: () => {
if (variant === 'block') {
setRedirectToResetPasswordPage(true);
} else {
trackCohesionEvent(cohesionEventData);
redirectWithDelay(redirectUrl || getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL));
window.location.href = redirectUrl || getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
}
},
};

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { connect, useDispatch } from 'react-redux';
import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
@@ -20,10 +20,6 @@ import {
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
import {
ELEMENT_NAME, ELEMENT_TEXT, ELEMENT_TYPES, PAGE_TYPES,
} from '../cohesion/constants';
import { setCohesionEventStates } from '../cohesion/data/actions';
import {
FormGroup,
InstitutionLogistration,
@@ -35,7 +31,9 @@ import { getThirdPartyAuthContext } from '../common-components/data/actions';
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
import { DEFAULT_STATE, PENDING_STATE, RESET_PAGE } from '../data/constants';
import {
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
} from '../data/constants';
import {
getActivationStatus,
getAllPossibleQueryParams,
@@ -74,7 +72,6 @@ const LoginPage = (props) => {
getTPADataFromBackend,
} = props;
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const activationMsgType = getActivationStatus();
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
@@ -90,6 +87,7 @@ const LoginPage = (props) => {
useEffect(() => {
if (loginResult.success) {
trackLoginSuccess();
// Remove this cookie that was set to capture marketingEmailsOptIn for the onboarding component
removeCookie('ssoPipelineRedirectionDone');
}
@@ -154,15 +152,6 @@ const LoginPage = (props) => {
const handleSubmit = (event) => {
event.preventDefault();
const eventData = {
pageType: PAGE_TYPES.SIGN_IN,
elementType: ELEMENT_TYPES.BUTTON,
webElementText: ELEMENT_TEXT.SIGN_IN,
webElementName: ELEMENT_NAME.SIGN_IN,
};
dispatch(setCohesionEventStates(eventData));
if (showResetPasswordSuccessBanner) {
props.dismissPasswordResetBanner();
}
@@ -228,7 +217,6 @@ const LoginPage = (props) => {
success={loginResult.success}
redirectUrl={loginResult.redirectUrl}
finishAuthUrl={finishAuthUrl}
currentProvider={currentProvider}
/>
<div className="mw-xs mt-3 mb-2">
<LoginFailureMessage

View File

@@ -95,6 +95,11 @@ const messages = defineMessages({
defaultMessage: 'Your account could not be activated',
description: 'Account Activation error message title',
},
'account.activation.support.link': {
id: 'account.activation.support.link',
defaultMessage: 'contact support',
description: 'Link text used in account activation error message to go to learner help center',
},
// Email Confirmation Strings
'account.confirmation.success.message.title': {
id: 'account.confirmation.success.message.title',

View File

@@ -58,7 +58,7 @@ describe('AccountActivationMessage', () => {
);
const expectedMessage = 'Your account could not be activated'
+ 'Something went wrong, please contact to resolve this issue.';
+ 'Something went wrong, please contact support to resolve this issue.';
expect(screen.getByText(
'',
@@ -121,7 +121,7 @@ describe('EmailConfirmationMessage', () => {
</IntlProvider>,
);
const expectedMessage = 'Your email could not be confirmed'
+ 'Something went wrong, please contact to resolve this issue.';
+ 'Something went wrong, please contact support to resolve this issue.';
expect(screen.getByText(
'',
{ selector: '#account-activation-message' },

View File

@@ -1,30 +1,18 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
fireEvent, render, screen,
} from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import mockTagular from '../../cohesion/utils';
import { RESET_PAGE } from '../../data/constants';
import ChangePasswordPrompt from '../ChangePasswordPrompt';
const IntlChangePasswordPrompt = injectIntl(ChangePasswordPrompt);
const mockedNavigator = jest.fn();
const mockStore = configureStore();
mockTagular();
const eventData = {
pageType: 'test-page',
elementType: 'test-element-type',
webElementText: 'test-element-text',
webElementName: 'test-element-name',
};
jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom')),
@@ -33,14 +21,8 @@ jest.mock('react-router-dom', () => ({
describe('ChangePasswordPromptTests', () => {
let props = {};
let store = {};
const initialState = {
cohesion: { eventData: {} },
};
beforeAll(() => {
store = mockStore(initialState);
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
@@ -49,56 +31,38 @@ describe('ChangePasswordPromptTests', () => {
});
});
it('[nudge modal] should redirect to next url when user clicks close button', async () => {
it('[nudge modal] should redirect to next url when user clicks close button', () => {
const dashboardUrl = getConfig().BASE_URL.concat('/dashboard');
props = {
variant: 'nudge',
redirectUrl: dashboardUrl,
};
store = mockStore({
...initialState,
cohesion: {
eventData,
},
});
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</Provider>
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</IntlProvider>,
);
fireEvent.click(screen.getByText('Close'));
await waitFor(() => {
expect(window.location.href).toBe(dashboardUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(dashboardUrl);
});
it('[block modal] should redirect to reset password page when user clicks outside modal', async () => {
props = {
variant: 'block',
};
store = mockStore({
...initialState,
cohesion: {
eventData,
},
});
render(
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</Provider>
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</IntlProvider>,
);

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
render, screen,
} from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import {
ACCOUNT_LOCKED_OUT,
@@ -27,27 +25,13 @@ import LoginFailureMessage from '../LoginFailure';
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
const mockStore = configureStore();
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
const eventData = {
pageType: 'test-page',
elementType: 'test-element-type',
webElementText: 'test-element-text',
webElementName: 'test-element-name',
};
describe('LoginFailureMessage', () => {
let props = {};
let store = {};
const initialState = {
cohesion: { eventData: {} },
};
beforeAll(() => {
store = mockStore(initialState);
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
@@ -314,19 +298,11 @@ describe('LoginFailureMessage', () => {
errorCount: 0,
};
store = mockStore({
...initialState,
cohesion: {
eventData,
},
});
render(
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</Provider>
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);
@@ -347,20 +323,12 @@ describe('LoginFailureMessage', () => {
errorCode: REQUIRE_PASSWORD_CHANGE,
errorCount: 0,
};
store = mockStore({
...initialState,
cohesion: {
eventData,
},
});
render(
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</Provider>
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);

View File

@@ -11,7 +11,6 @@ import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import mockTagular from '../../cohesion/utils';
import {
APP_NAME, COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE,
} from '../../data/constants';
@@ -26,7 +25,6 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
mockTagular();
const IntlLoginPage = injectIntl(LoginPage);
const mockStore = configureStore();
@@ -60,7 +58,6 @@ describe('LoginPage', () => {
register: {
validationApiRateLimited: false,
},
cohesion: { eventData: {} },
};
const secondaryProviders = {
@@ -515,7 +512,7 @@ describe('LoginPage', () => {
// ******** test redirection ********
it('should redirect to url returned by login endpoint after successful authentication', async () => {
it('should redirect to url returned by login endpoint after successful authentication', () => {
const dashboardURL = 'https://test.com/testing-dashboard/';
store = mockStore({
...initialState,
@@ -531,12 +528,10 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(dashboardURL);
}, { timeout: 1100 });
expect(window.location.href).toBe(dashboardURL);
});
it('should redirect to finishAuthUrl upon successful login via SSO', async () => {
it('should redirect to finishAuthUrl upon successful login via SSO', () => {
const authCompleteUrl = '/auth/complete/google-oauth2/';
store = mockStore({
...initialState,
@@ -560,12 +555,10 @@ describe('LoginPage', () => {
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
it('should redirect to social auth provider url on SSO button click', async () => {
it('should redirect to social auth provider url on SSO button click', () => {
store = mockStore({
...initialState,
commonComponents: {
@@ -586,12 +579,10 @@ describe('LoginPage', () => {
'',
{ selector: '#oa2-apple-id' },
));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + ssoProvider.loginUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + ssoProvider.loginUrl);
});
it('should redirect to finishAuthUrl upon successful authentication via SSO', async () => {
it('should redirect to finishAuthUrl upon successful authentication via SSO', () => {
const finishAuthUrl = '/auth/complete/google-oauth2/';
store = mockStore({
...initialState,
@@ -612,9 +603,7 @@ describe('LoginPage', () => {
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + finishAuthUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + finishAuthUrl);
});
// ******** test hinted third party auth ********

View File

@@ -70,7 +70,6 @@ const Logistration = (props) => {
if (tabKey === currentTab) {
return;
}
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement', app_name: APP_NAME });
props.clearThirdPartyAuthContextErrorMessage();
if (tabKey === LOGIN_PAGE) {

View File

@@ -69,7 +69,6 @@ describe('Logistration', () => {
usernameSuggestions: [],
validationApiRateLimited: false,
},
cohesion: { eventData: {} },
commonComponents: {
thirdPartyAuthContext: {
providers: [],

View File

@@ -1,29 +0,0 @@
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { render } from '@testing-library/react';
import MainAppSlot from './index';
jest.mock('@openedx/frontend-plugin-framework', () => ({
PluginSlot: jest.fn(() => null),
}));
describe('MainAppSlot', () => {
it('renders without crashing', () => {
render(<MainAppSlot />);
});
it('renders a PluginSlot component', () => {
render(<MainAppSlot />);
expect(PluginSlot).toHaveBeenCalled();
});
it('passes the correct id prop to PluginSlot', () => {
render(<MainAppSlot />);
expect(PluginSlot).toHaveBeenCalledWith({ id: 'main_app_slot' }, {});
});
it('does not render any children', () => {
const { container } = render(<MainAppSlot />);
expect(container.firstChild).toBeNull();
});
});

View File

@@ -1,41 +0,0 @@
# Main App Slot
### Slot ID: `main_app_slot`
## Description
This slot is used for adding content at the root level.
## Example
The following `env.config.jsx` will render a component at the MFE root level.
![Screenshot of Content added after the Main App Slot](./images/main_app_slot.png)
```js
import {
DIRECT_PLUGIN,
PLUGIN_OPERATIONS,
} from "@openedx/frontend-plugin-framework";
import { ExampleComponent } from "@openedx/frontend-plugin-example";
const config = {
pluginSlots: {
main_app_slot: {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: "example-component",
type: DIRECT_PLUGIN,
priority: 60,
RenderWidget: ExampleComponent,
},
},
],
},
},
};
export default config;
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

View File

@@ -1,7 +0,0 @@
import { PluginSlot } from '@openedx/frontend-plugin-framework';
const MainAppSlot = () => (
<PluginSlot id="main_app_slot" />
);
export default MainAppSlot;

View File

@@ -1,3 +0,0 @@
# `frontend-app-authn` Plugin Slots
- [`main_app_slot`](./MainAppSlot/)

View File

@@ -6,12 +6,11 @@ import { identifyAuthenticatedUser, sendTrackEvent } from '@edx/frontend-platfor
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
fireEvent, render, screen,
} from '@testing-library/react';
import { MemoryRouter, mockNavigate, useLocation } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import mockTagular from '../../cohesion/utils';
import {
APP_NAME,
AUTHN_PROGRESSIVE_PROFILING,
@@ -26,7 +25,6 @@ import ProgressiveProfiling from '../ProgressiveProfiling';
const IntlProgressiveProfilingPage = injectIntl(ProgressiveProfiling);
const mockStore = configureStore();
mockTagular();
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
@@ -57,13 +55,6 @@ jest.mock('react-router-dom', () => {
};
});
const eventData = {
pageType: 'test-page',
elementType: 'test-element-type',
webElementText: 'test-element-text',
webElementName: 'test-element-name',
};
describe('ProgressiveProfilingTests', () => {
let store = {};
@@ -261,9 +252,6 @@ describe('ProgressiveProfilingTests', () => {
...initialState.welcomePage,
success: true,
},
cohesion: {
eventData,
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const nextButton = container.querySelector('button.btn-brand');
@@ -290,18 +278,13 @@ describe('ProgressiveProfilingTests', () => {
...initialState.welcomePage,
success: true,
},
cohesion: {
eventData,
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const nextButton = container.querySelector('button.btn-brand');
expect(nextButton.textContent).toEqual('Submit');
await waitFor(() => {
expect(window.location.href).toEqual(redirectUrl);
}, { timeout: 1100 });
expect(window.location.href).toEqual(redirectUrl);
});
});
@@ -416,7 +399,7 @@ describe('ProgressiveProfilingTests', () => {
expect(window.location.href).toBe(DASHBOARD_URL);
});
it('should redirect to provided redirect url', async () => {
it('should redirect to provided redirect url', () => {
const redirectUrl = 'https://redirect-test.com';
delete window.location;
window.location = {
@@ -438,17 +421,12 @@ describe('ProgressiveProfilingTests', () => {
...initialState.welcomePage,
success: true,
},
cohesion: {
eventData,
},
});
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
await waitFor(() => {
expect(window.location.href).toBe(redirectUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(redirectUrl);
});
});
});

View File

@@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import algoliasearchHelper from 'algoliasearch-helper';
import mockedRecommendedProducts from './mockedData';

View File

@@ -11,12 +11,6 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import Skeleton from 'react-loading-skeleton';
import {
InstitutionLogistration,
PasswordField,
RedirectLogistration,
ThirdPartyAuthAlert,
} from '../common-components';
import ConfigurableRegistrationForm from './components/ConfigurableRegistrationForm';
import RegistrationFailure from './components/RegistrationFailure';
import {
@@ -40,20 +34,22 @@ import {
import messages from './messages';
import { EmailField, NameField, UsernameField } from './RegistrationFields';
import {
ELEMENT_NAME, ELEMENT_TEXT, ELEMENT_TYPES, PAGE_TYPES,
} from '../cohesion/constants';
import { setCohesionEventStates } from '../cohesion/data/actions';
InstitutionLogistration,
PasswordField,
RedirectLogistration,
ThirdPartyAuthAlert,
} from '../common-components';
import { getThirdPartyAuthContext as getRegistrationDataFromBackend } from '../common-components/data/actions';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
import {
APP_NAME, COMPLETE_STATE, PENDING_STATE,
REGISTER_PAGE,
APP_NAME, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
} from '../data/constants';
import {
getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, removeCookie, setCookie,
} from '../data/utils';
import { trackRegistrationPageViewed, trackRegistrationSuccess } from '../tracking/trackers/register';
/**
* Main Registration Page component
*/
@@ -93,7 +89,6 @@ const RegistrationPage = (props) => {
const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers);
const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders);
const pipelineUserDetails = useSelector(state => state.commonComponents.thirdPartyAuthContext.pipelineUserDetails);
const countriesCodesList = useSelector(state => state.commonComponents.countriesCodesList);
const backendValidations = useSelector(getBackendValidations);
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
@@ -273,14 +268,6 @@ const RegistrationPage = (props) => {
const handleSubmit = (e) => {
e.preventDefault();
const eventData = {
pageType: PAGE_TYPES.ACCOUNT_CREATION,
elementType: ELEMENT_TYPES.BUTTON,
webElementText: ELEMENT_TEXT.CREATE_ACCOUNT,
webElementName: ELEMENT_NAME.CREATE_ACCOUNT,
};
dispatch(setCohesionEventStates(eventData));
registerUser();
};
@@ -315,7 +302,6 @@ const RegistrationPage = (props) => {
redirectToProgressiveProfilingPage={
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && !!Object.keys(optionalFields.fields).length
}
currentProvider={currentProvider}
/>
{(autoSubmitRegForm && !errorCode.type)
|| (!autoGeneratedUsernameExpVariation && !(
@@ -393,7 +379,6 @@ const RegistrationPage = (props) => {
setFormFields={setConfigurableFormFields}
autoSubmitRegisterForm={autoSubmitRegForm}
fieldDescriptions={fieldDescriptions}
countriesCodesList={countriesCodesList}
/>
<StatefulButton
id="register-user"

View File

@@ -6,7 +6,7 @@ import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'
import {
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { mockNavigate, BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -17,7 +17,6 @@ import {
setUserPipelineDataLoaded,
} from './data/actions';
import { INTERNAL_SERVER_ERROR } from './data/constants';
import mockTagular from '../cohesion/utils';
import { NOT_INITIALIZED } from './data/optimizelyExperiment/helper';
import useAutoGeneratedUsernameExperimentVariation
from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
@@ -38,7 +37,6 @@ jest.mock('./data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariati
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
mockTagular();
jest.mock('react-router-dom', () => {
const mockNavigation = jest.fn();
@@ -108,7 +106,6 @@ describe('RegistrationPage', () => {
usernameSuggestions: [],
},
cohesion: { eventData: {} },
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext,
@@ -508,7 +505,7 @@ describe('RegistrationPage', () => {
expect(document.cookie).toMatch(`${getConfig().USER_RETENTION_COOKIE_NAME}=true`);
});
it('should redirect to url returned in registration result after successful account creation', async () => {
it('should redirect to url returned in registration result after successful account creation', () => {
const dashboardURL = 'https://test.com/testing-dashboard/';
store = mockStore({
...initialState,
@@ -523,12 +520,10 @@ describe('RegistrationPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(dashboardURL);
}, { timeout: 1100 });
expect(window.location.href).toBe(dashboardURL);
});
it('should redirect to dashboard if features flags are configured but no optional fields are configured', async () => {
it('should redirect to dashboard if features flags are configured but no optional fields are configured', () => {
mergeConfig({
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: true,
});
@@ -552,9 +547,7 @@ describe('RegistrationPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(dashboardUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(dashboardUrl);
});
it('should redirect to progressive profiling page if optional fields are configured', () => {

View File

@@ -1,14 +1,10 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { getCountryList, getLocale, useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import {
ELEMENT_NAME, ELEMENT_TEXT, ELEMENT_TYPES, PAGE_TYPES,
} from '../../cohesion/constants';
import trackCohesionEvent from '../../cohesion/trackers';
import { FormFieldRenderer } from '../../field-renderer';
import { backupRegistrationFormBegin } from '../data/actions';
import { FIELDS } from '../data/constants';
@@ -37,7 +33,6 @@ const ConfigurableRegistrationForm = (props) => {
setFieldErrors,
setFormFields,
autoSubmitRegistrationForm,
countriesCodesList,
} = props;
const dispatch = useDispatch();
@@ -46,6 +41,10 @@ const ConfigurableRegistrationForm = (props) => {
confused and unable to create an account. So we added the United States entry in the dropdown list.
*/
const countryList = useMemo(() => (
getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]).filter(country => country.code !== 'RU')
), []);
let showTermsOfServiceAndHonorCode = false;
let showCountryField = false;
@@ -79,16 +78,6 @@ const ConfigurableRegistrationForm = (props) => {
}
}, [autoSubmitRegistrationForm]); // eslint-disable-line react-hooks/exhaustive-deps
const removeDisabledCountries = useCallback((countryList) => {
if (!countriesCodesList.length) {
return countryList;
}
return countryList.filter(({ code }) => countriesCodesList.find(x => x === code));
}, [countriesCodesList]);
const countryList = useMemo(() => removeDisabledCountries(
getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }])), [removeDisabledCountries]);
const handleErrorChange = (fieldName, error) => {
if (fieldName) {
setFieldErrors(prevErrors => ({
@@ -111,15 +100,6 @@ const ConfigurableRegistrationForm = (props) => {
}
// setting marketingEmailsOptIn state for SSO authentication flow for register API call
if (name === 'marketingEmailsOptIn') {
if (!value) {
const cohesionEventData = {
pageType: PAGE_TYPES.ACCOUNT_CREATION,
elementType: ELEMENT_TYPES.BUTTON,
webElementText: ELEMENT_TEXT.OPT_IN_TEXT,
webElementName: ELEMENT_NAME.OPT_OUT,
};
trackCohesionEvent(cohesionEventData);
}
dispatch(backupRegistrationFormBegin({
...backedUpFormData,
configurableFormFields: {
@@ -269,16 +249,11 @@ ConfigurableRegistrationForm.propTypes = {
setFieldErrors: PropTypes.func.isRequired,
setFormFields: PropTypes.func.isRequired,
autoSubmitRegistrationForm: PropTypes.bool,
countriesCodesList: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})),
};
ConfigurableRegistrationForm.defaultProps = {
fieldDescriptions: {},
autoSubmitRegistrationForm: false,
countriesCodesList: [],
};
export default ConfigurableRegistrationForm;

View File

@@ -99,7 +99,6 @@ describe('ConfigurableRegistrationForm', () => {
registrationFormData,
usernameSuggestions: [],
},
cohesion: { eventData: {} },
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext,
@@ -192,7 +191,6 @@ describe('ConfigurableRegistrationForm', () => {
},
},
autoSubmitRegistrationForm: true,
countriesCodesList: [{ code: 'AX', name: 'Åland Islands' }, { code: 'AL', name: 'Albania' }],
};
render(routerWrapper(reduxWrapper(

View File

@@ -99,7 +99,6 @@ describe('RegistrationFailure', () => {
registrationFormData,
usernameSuggestions: [],
},
cohesion: { eventData: {} },
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext,

View File

@@ -5,11 +5,10 @@ import { getConfig, mergeConfig } from '@edx/frontend-platform';
import {
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import mockTagular from '../../../cohesion/utils';
import {
COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
} from '../../../data/constants';
@@ -27,7 +26,6 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
mockTagular();
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
@@ -100,7 +98,6 @@ describe('ThirdPartyAuth', () => {
registrationFormData,
usernameSuggestions: [],
},
cohesion: { eventData: {} },
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext,
@@ -342,7 +339,7 @@ describe('ThirdPartyAuth', () => {
expect(headingElement).toBeTruthy();
});
it('should redirect to social auth provider url on SSO button click', async () => {
it('should redirect to social auth provider url on SSO button click', () => {
const registerUrl = '/auth/login/apple-id/?auth_entry=register&next=/dashboard';
store = mockStore({
...initialState,
@@ -368,12 +365,10 @@ describe('ThirdPartyAuth', () => {
const ssoButton = container.querySelector('button#oa2-apple-id');
fireEvent.click(ssoButton);
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + registerUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + registerUrl);
});
it('should redirect to finishAuthUrl upon successful registration via SSO', async () => {
it('should redirect to finishAuthUrl upon successful registration via SSO', () => {
const authCompleteUrl = '/auth/complete/google-oauth2/';
store = mockStore({
...initialState,
@@ -396,9 +391,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
}, { timeout: 1100 });
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
// ******** test alert messages ********

View File

@@ -2,19 +2,19 @@
.layout {
display: flex;
@media (--pgn-size-breakpoint-max-width-lg) {
@include media-breakpoint-down('lg') {
flex-direction: column;
justify-content: center;
align-items: center;
}
@media (--pgn-size-breakpoint-min-width-xl) {
@include media-breakpoint-up('xl') {
justify-content: space-between;
}
}
.content {
@media (--pgn-size-breakpoint-min-width-xl) {
@include media-breakpoint-up('xl') {
display: flex;
justify-content: center;
width: 50vw;
@@ -47,7 +47,7 @@
font-weight: 700;
line-height: 1;
@media (--pgn-size-breakpoint-max-width-xl) {
@include media-breakpoint-down('xl') {
font-size: 3.75rem;
}
@@ -60,7 +60,7 @@
margin-bottom: 0.5rem;
font-weight: 700;
@media (-pgn-size-breakpoint-max-width-xl) {
@include media-breakpoint-down('xl') {
font-size: 1.375rem;
line-height: 1.75rem;
}
@@ -72,7 +72,7 @@
}
.large-screen-left-container {
@media (-pgn-size-breakpoint-max-width-xl) {
@include media-breakpoint-down('xl') {
flex: 0 0 25%;
max-width: 25%;
}
@@ -87,43 +87,43 @@
height: 0.25rem;
background-image: linear-gradient(
102.02deg,
var(--pgn-color-brand-700),
var(--pgn-color-brand-700) 20%,
var(--pgn-color-brand-base) 20%,
$brand-700,
$brand-700 20%,
$brand 20%,
);
background-repeat: no-repeat;
}
@media (--pgn-size-breakpoint-min-width-md) and (--pgn-size-breakpoint-max-width-md) {
@include media-breakpoint-only('md') {
.medium-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
var(--pgn-color-brand-700),
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 90%,
var(--pgn-color-primary-700) 90%,
var(--pgn-color-primary-700) 100%,
$brand-700,
$brand-700 10%,
$brand 10%,
$brand 90%,
$primary-700 90%,
$primary-700 100%,
);
background-repeat: no-repeat;
}
}
@media (--pgn-size-breakpoint-min-width-lg) and (--pgn-size-breakpoint-max-width-lg){
@include media-breakpoint-only('lg') {
.medium-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 65%,
var(--pgn-color-primary-700) 65%,
var(--pgn-color-primary-700) 75%,
var(--pgn-color-accent-a) 75%,
var(--pgn-color-accent-a) 75%
$brand-700 10%,
$brand 10%,
$brand 65%,
$primary-700 65%,
$primary-700 75%,
$accent-a 75%,
$accent-a 75%
);
background-repeat: no-repeat;
}
@@ -131,20 +131,20 @@
.extra-large-screen-top-stripe { display: none; }
@media (--pgn-size-breakpoint-min-width-xl) {
@include media-breakpoint-up('xl') {
.extra-large-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 45%,
var(--pgn-color-primary-700) 45%,
var(--pgn-color-primary-700) 55%,
var(--pgn-color-accent-a) 55%,
var(--pgn-color-accent-a) 75%,
var(--pgn-color-info-200) 75%,
$brand-700 10%,
$brand 10%,
$brand 45%,
$primary-700 45%,
$primary-700 55%,
$accent-a 55%,
$accent-a 75%,
$info-200 75%,
);
background-repeat: no-repeat;
}
@@ -152,24 +152,24 @@
.large-screen-svg-light,
.large-screen-svg-primary {
fill: var(--pgn-color-light-200);
fill: $light-200;
overflow: hidden;
position: absolute;
}
.large-screen-svg-primary {
fill: var(--pgn-color-primary-400);
fill: $primary-400;
}
.medium-screen-svg-light,
.medium-screen-svg-primary {
fill: var(--pgn-color-light-200);
fill: $light-200;
overflow: inherit;
position: absolute;
}
.medium-screen-svg-primary {
fill: var(--pgn-color-primary-400);
fill: $primary-400;
}
[dir=rtl]{
@@ -184,20 +184,20 @@
.small-yellow-line {
width: 80px;
height: 0;
border: 2px solid var(--pgn-color-accent-b);
border: 2px solid $accent-b;
transform: rotate(102.02deg);
}
.medium-yellow-line {
width: 120px;
height: 0;
border: 3px solid var(--pgn-color-accent-b);
border: 3px solid $accent-b;
transform: rotate(102.02deg);
}
.large-yellow-line {
width: 240px;
height: 0;
border: 3px solid var(--pgn-color-accent-b);
border: 3px solid $accent-b;
transform: rotate(102.02deg);
}

View File

@@ -11,7 +11,7 @@
margin-bottom: 0.5rem;
font-weight: 700;
@media (--pgn-size-breakpoint-max-width-md) {
@include media-breakpoint-down('md') {
line-height: 1.5rem;
font-size: 1.125rem;
}

View File

@@ -64,52 +64,52 @@ $header-height: 104px;
}
&.light {
background-color: var(--pgn-color-white);
background-color: $white;
.title {
color: var(--pgn-color-black);
color: $black;
}
.subtitle {
color: var(--pgn-color-gray-700);
color: $gray-700;
}
.badge {
background-color: var(--pgn-color-light-500);
color: var(--pgn-color-black);
background-color: $light-500;
color: $black;
}
.footer-content {
color: var(--pgn-color-gray-700);
color: $gray-700;
}
}
&.dark {
background-color: var(--pgn-color-primary-500);
background-color: $primary-500;
.pgn__card-header-title-md {
color: var(--pgn-color-white);
color: $white;
}
.pgn__card-header-subtitle-md {
color: var(--pgn-color-light-200);
color: $light-200;
}
.title {
color: var(--pgn-color-white);
color: $white;
}
.subtitle {
color: var(--pgn-color-light-200);
color: $light-200;
}
.badge {
background-color: var(--pgn-color-dark-200);
color: var(--pgn-color-white);
background-color: $dark-200;
color: $white;
}
.footer-content {
color: var(--pgn-color-light-200);
color: $light-200;
}
}
}

View File

@@ -3,7 +3,7 @@ $card-gap: 24px;
.recommendations-container__card-list {
gap: $card-gap $card-gap;
@media (-pgn-size-breakpoint-max-width-sm) {
@include media-breakpoint-down(sm) {
margin-bottom: 0 !important;
}
@@ -11,15 +11,15 @@ $card-gap: 24px;
flex: 0 1 100%;
cursor: pointer;
@media (--pgn-size-breakpoint-min-width-sm) {
@include media-breakpoint-up(sm) {
flex: 0 1 calc(50% - #{$card-gap - 12});
}
@media (--pgn-size-breakpoint-min-width-md) {
@include media-breakpoint-up(md) {
flex: 0 1 calc(33.333% - #{$card-gap - 8});
}
@media (--pgn-size-breakpoint-min-width-lg) {
@include media-breakpoint-up(lg) {
flex: 0 1 calc(25% - #{$card-gap - 6});
}
}

View File

@@ -23,21 +23,21 @@
}
.alert-link {
color: var(--pgn-color-primary-base) !important;
color: $primary !important;
&:hover {
text-decoration: underline;
color: var(--pgn-color-info-700) !important;
color: $info-700 !important;
}
}
}
.email-suggestion-alert-warning {
color: var(--pgn-color-info-500) !important;
color: $info-500 !important;
&:hover {
text-decoration: underline;
color: var(--pgn-color-info-700) !important;
color: $info-700 !important;
}
}
@@ -56,7 +56,7 @@
line-height: 24px;
font-size: 12px;
font-weight: normal;
color: var(--pgn-color-primary-700);
color: $primary-700;
}
.username-suggestion--label {
@@ -99,7 +99,7 @@
}
}
@media (--pgn-size-breakpoint-max-width-sm) {
@media (max-width: map-get($grid-breakpoints, "sm")) {
.username-scroll-suggested--form-field {
width: 15rem;
}

View File

@@ -40,7 +40,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.main-content {
padding-top: calc(var(--pgn-spacing-spacer-base) * 1.5) !important;
@extend .pt-4;
min-width: 464px !important;
}
@@ -80,15 +80,15 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
.alert-link {
font-weight: normal;
text-decoration: underline;
color: var(--pgn-color-info-300) !important;
color: $info-300 !important;
&:hover {
color: var(--pgn-color-info-500) !important;
color: $info-500 !important;
}
}
.form-control {
background-color: var(--pgn-color-white) !important;
background-color: $white !important;
font-size: 0.875rem;
line-height: 1.5;
height: 2.75rem;
@@ -103,11 +103,11 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
margin-bottom: 1rem;
font-size: 14px;
background-color: var(--pgn-color-white);
border: 1px solid var(--pgn-color-primary-base);
background-color: $white;
border: 1px solid $primary;
width: 224px;
height: 36px;
color: var(--pgn-color-primary-base);
color: $primary;
.btn-tpa__image-icon{
background-color: transparent;
@@ -132,8 +132,8 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.btn-tpa__font-container {
background-color: var(--pgn-color-primary-base);
color: var(--pgn-color-white);
background-color: $primary;
color: $white;
font-size: 11px;
margin-left: -6px;
@@ -143,7 +143,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.btn-oa2-facebook {
color: var(--pgn-color-white);
color: $white;
border-color: $facebook-blue;
background-color: $facebook-blue;
@@ -151,12 +151,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $facebook-focus-blue;
border: 1px solid $facebook-focus-blue;
color: var(--pgn-color-white);
color: $white;
}
}
.btn-oa2-google-oauth2 {
color: var(--pgn-color-white);
color: $white;
border-color: $google-blue;
background-color: $google-blue;
@@ -171,12 +171,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $google-focus-blue;
border: 1px solid $google-focus-blue;
color: var(--pgn-color-white);
color: $white;
}
}
.btn-oa2-apple-id {
color: var(--pgn-color-white);
color: $white;
border-color: $apple-black;
background-color: $apple-black;
font-size: 16px;
@@ -190,12 +190,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $apple-focus-black;
border: 1px solid $apple-focus-black;
color: var(--pgn-color-white);
color: $white;
}
}
.btn-oa2-azuread-oauth2 {
color: var(--pgn-color-white);
color: $white;
border-color: $microsoft-black;
background-color: $microsoft-black;
@@ -203,7 +203,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $microsoft-focus-black;
border: 1px solid $microsoft-focus-black;
color: var(--pgn-color-white);
color: $white;
}
}
@@ -214,8 +214,9 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.institute-icon {
margin: calc(var(--pgn-spacing-spacer-base) * 0.25) !important;
color: var(--pgn-color-gray-base) !important;
@extend .mr-1;
@extend .text-gray;
display: inline-block;
margin-bottom: 0.25rem;
height: 18px;
@@ -231,7 +232,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.invalid-feedback {
color: var(--pgn-color-red);
color: $red;
}
.full-vertical-height {
@@ -289,22 +290,22 @@ select.form-control {
#password-requirement-left {
opacity: 1;
font-size: var(--pgn-typography-font-size-xs) !important;
@extend .x-small;
filter: drop-shadow($elevation-level-2-shadow) drop-shadow($elevation-level-2-shadow) !important;
right: 0.2rem !important;
.tooltip-inner {
background: var(--pgn-color-white);
background: $white;
display: block;
color: var(--pgn-color-gray-500);
color: $gray-500;
}
.arrow::before {
border-left-color: var(--pgn-color-white);
border-left-color: $white;
}
}
#password-requirement-top {
font-size: var(--pgn-typography-font-size-xs) !important;
filter: drop-shadow(var(--pgn-elevation-box-shadow-level-2)) drop-shadow(var(--pgn-elevation-box-shadow-level-2)) !important;
@extend .x-small;
filter: drop-shadow($elevation-level-2-shadow) drop-shadow($elevation-level-2-shadow) !important;
opacity: 1;
width: 90%;
bottom: 10px !important;
@@ -313,30 +314,30 @@ select.form-control {
.tooltip-inner {
min-width: 464px !important;
background: var(--pgn-color-white);
background: $white;
display: block;
color: var(--pgn-color-gray-500);
color: $gray-500;
}
.arrow::before {
border-top-color: var(--pgn-color-white);
border-top-color: $white;
}
}
.yellow-border {
border: 2px solid var(--pgn-color-accent-b);
border: 2px solid $accent-b;
}
.institutions__heading {
color: var(--pgn-color-primary-700);
color: $primary-700;
}
.logistration-button {
color: var(--pgn-color-gray-700);
color: $gray-700;
}
.logistration-button:hover{
color: var(--pgn-color-gray-700);
color: $gray-700;
text-decoration: none;
}
@@ -351,7 +352,7 @@ select.form-control {
width: 2.3rem;
}
.has-floating-label {
color: var(--pgn-color-gray-500);
color: $gray-500;
}
.pgn__form-control-floating-label .pgn__form-control-floating-label-content {
@@ -365,7 +366,7 @@ select.form-control {
.form-group__form-field .form-control:focus ~ .pgn__form-control-floating-label .pgn__form-control-floating-label-content {
font-size: 16px;
color: var(--pgn-color-primary-700);
color: $primary-700;
}
.form-group__form-field .form-control:not([value='']):not(:focus) ~
@@ -443,14 +444,14 @@ select.form-control {
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: var(--pgn-color-light-200);
background-color: $light-200;
}
.institutions--provider-link {
font-weight: normal;
font-size: 0.875rem;
line-height: 1.5rem;
color: var(--pgn-color-primary-700)
color: $primary-700
}
.pgn__form-control-decorator-trailing {

View File

@@ -1,26 +0,0 @@
const path = require('path');
const { createConfig } = require('@openedx/frontend-build');
const CopyPlugin = require('copy-webpack-plugin');
const config = createConfig('webpack-dev');
config.resolve.modules = [
path.resolve(__dirname, './src'),
'node_modules',
];
config.module.rules[0].exclude = /node_modules\/(?!(fastest-levenshtein|@edx))/;
config.plugins.push(
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, './public/robots.txt'),
to: path.resolve(__dirname, './dist/robots.txt'),
},
],
}),
);
module.exports = config;

View File

@@ -1,26 +1,7 @@
const path = require('path');
const { createConfig } = require('@openedx/frontend-build');
const CopyPlugin = require('copy-webpack-plugin');
const config = createConfig('webpack-prod');
config.resolve.modules = [
path.resolve(__dirname, './src'),
'node_modules',
];
config.module.rules[0].exclude = /node_modules\/(?!(fastest-levenshtein|@edx))/;
config.plugins.push(
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, './public/robots.txt'),
to: path.resolve(__dirname, './dist/robots.txt'),
},
],
}),
);
module.exports = config;