Remove willSecure and didGetSecured data hooks

This commit is contained in:
soupette 2019-09-09 18:44:41 +02:00
parent 76560a1c9c
commit b3bc4a0941
33 changed files with 102 additions and 1665 deletions

View File

@ -81,37 +81,39 @@ export class Admin extends React.Component {
componentDidMount() {
/* istanbul ignore next */
// const { getHook } = this.props;
// getHook('didGetSecuredData');
// Retrieve the main settings of the application
this.props.getInitData();
// this.props.getInitData();
}
/* istanbul ignore next */
componentDidUpdate(prevProps) {
const {
admin: { didGetSecuredData, isLoading, isSecured },
getHook,
getSecuredData,
location: { pathname },
} = this.props;
// componentDidUpdate(prevProps) {
// const {
// admin: { didGetSecuredData, isLoading, isSecured },
// getHook,
// getSecuredData,
// location: { pathname },
// } = this.props;
if (!isLoading && this.state.shouldSecureAfterAllPluginsAreMounted) {
if (!this.hasApluginNotReady(this.props)) {
getHook('willSecure');
}
}
// if (!isLoading && this.state.shouldSecureAfterAllPluginsAreMounted) {
// if (!this.hasApluginNotReady(this.props)) {
// getHook('willSecure');
// }
// }
if (prevProps.location.pathname !== pathname) {
getHook('willSecure');
}
// if (prevProps.location.pathname !== pathname) {
// getHook('willSecure');
// }
if (prevProps.admin.isSecured !== isSecured && isSecured) {
getSecuredData();
}
// if (prevProps.admin.isSecured !== isSecured && isSecured) {
// getSecuredData();
// }
if (prevProps.admin.didGetSecuredData !== didGetSecuredData) {
getHook('didGetSecuredData');
}
}
// if (prevProps.admin.didGetSecuredData !== didGetSecuredData) {
// getHook('didGetSecuredData');
// }
// }
/* istanbul ignore next */
componentDidCatch(error, info) {
@ -129,13 +131,7 @@ export class Admin extends React.Component {
// TODO remove
getContentWrapperStyle = () => {
const {
admin: { showMenu },
} = this.props;
return showMenu
? { main: {}, sub: styles.content }
: { main: { width: '100%' }, sub: styles.wrapper };
return { main: {}, sub: styles.content };
};
hasApluginNotReady = props => {
@ -195,7 +191,6 @@ export class Admin extends React.Component {
render() {
const {
admin: { isLoading, showLogoutComponent, showMenu },
global: {
blockApp,
overlayBlockerData,
@ -205,10 +200,6 @@ export class Admin extends React.Component {
},
} = this.props;
if (isLoading) {
return <LoadingIndicatorPage />;
}
// We need the admin data in order to make the initializers work
if (this.showLoader()) {
return (
@ -221,17 +212,17 @@ export class Admin extends React.Component {
return (
<div className={styles.adminPage}>
{showMenu && <LeftMenu version={strapiVersion} plugins={plugins} />}
<LeftMenu version={strapiVersion} plugins={plugins} />
<NavTopRightWrapper>
{/* Injection zone not ready yet */}
{showLogoutComponent && <Logout />}
<Logout />
<LocaleToggle isLogged />
</NavTopRightWrapper>
<div
className={styles.adminPageRightWrapper}
style={this.getContentWrapperStyle().main}
>
{showMenu ? <Header /> : ''}
<Header />
<div className={this.getContentWrapperStyle().sub}>
<Switch>
<Route
@ -266,7 +257,7 @@ export class Admin extends React.Component {
isOpen={blockApp && showGlobalAppBlocker}
{...overlayBlockerData}
/>
{showLogoutComponent && SHOW_TUTORIALS && <Onboarding />}
{SHOW_TUTORIALS && <Onboarding />}
</div>
);
}

View File

@ -21,15 +21,10 @@ const initialState = fromJS({
autoReload: false,
appError: false,
currentEnvironment: 'development',
didGetSecuredData: false,
isLoading: true,
isSecured: false,
layout: Map({}),
// NOTE: This should be the models and our stuffs
// Since this api is not implemented yet I just set this vague key ATM
securedData: {},
showMenu: true,
showLogoutComponent: false,
strapiVersion: '3',
uuid: false,
});

View File

@ -19,11 +19,11 @@ import { bindActionCreators, compose } from 'redux';
import { LoadingIndicatorPage, request } from 'strapi-helper-plugin';
import Admin from '../Admin';
import AppLoader from '../AppLoader';
import AuthPage from '../AuthPage';
import NotFoundPage from '../NotFoundPage';
import NotificationProvider from '../NotificationProvider';
import AppLoader from '../AppLoader';
import styles from './styles.scss';
import AuthPage from '../AuthPage';
import PrivateRoute from '../PrivateRoute';
import { getDataSucceeded } from './actions';
@ -58,7 +58,7 @@ function App(props) {
}
return (
<div className={styles.container}>
<div style={{ display: 'block' }}>
<Switch>
<Route
path="/auth/:authType"
@ -71,10 +71,7 @@ function App(props) {
)}
exact
/>
<Route
path="/"
render={router => <Admin {...props} {...router} />}
/>
<PrivateRoute {...props} path="/" component={Admin} />
<Route path="" component={NotFoundPage} />
</Switch>
</div>

View File

@ -1,11 +0,0 @@
/**
* styles.css
*
* App container styles
*/
@import "../../styles/variables/variables";
.container {
display: block;
}

View File

@ -4,6 +4,7 @@ import { Route } from 'react-router-dom';
import AppLoader from '../../AppLoader';
import { App } from '../../App';
import PrivateRoute from '../../PrivateRoute';
describe('<App />', () => {
it('should render the <AppLoader />', () => {
@ -17,7 +18,8 @@ describe('<App />', () => {
topComp.find(AppLoader).prop('children')({ shouldLoad: false })
);
expect(insideAppLoaderNotLoading.find(Route).length).toBe(3);
expect(insideAppLoaderNotLoading.find(Route).length).toBe(2);
expect(insideAppLoaderNotLoading.find(PrivateRoute)).toHaveLength(1);
});
it('should not render the <Switch /> if the app is loading', () => {

View File

@ -23,7 +23,6 @@ import formatErrorFromRequest from './utils/formatErrorFromRequest';
const AuthPage = ({
hasAdminUser,
history: { push },
location: { search },
match: {
params: { authType },
@ -31,6 +30,9 @@ const AuthPage = ({
}) => {
const [reducerState, dispatch] = useReducer(reducer, initialState);
const codeRef = useRef();
const aborController = new AbortController();
const { signal } = aborController;
codeRef.current = getQueryParameters(search, 'code');
useEffect(() => {
// Set the reset code provided by the url
@ -47,7 +49,10 @@ const AuthPage = ({
});
}
return () => {};
return () => {
aborController.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authType, codeRef]);
const {
didCheckErrors,
@ -77,6 +82,7 @@ const AuthPage = ({
await request('https://analytics.strapi.io/register', {
method: 'POST',
body: omit(modifiedData, ['password', 'confirmPassword']),
signal,
});
}
} catch (err) {
@ -95,6 +101,7 @@ const AuthPage = ({
const { jwt, user, ok } = await request(requestURL, {
method: 'POST',
body,
signal,
});
if (authType === 'forgot-password' && ok === true) {
@ -105,7 +112,6 @@ const AuthPage = ({
} else {
auth.setToken(jwt, modifiedData.rememberMe);
auth.setUserInfo(user, modifiedData.rememberMe);
push('/');
}
} catch (err) {
const formattedError = formatErrorFromRequest(err);
@ -259,9 +265,6 @@ const AuthPage = ({
AuthPage.propTypes = {
hasAdminUser: PropTypes.bool.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}),
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}),

View File

@ -4,25 +4,46 @@
*
*/
import React from 'react';
import { memo, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { request } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
class Initializer extends React.PureComponent {
// eslint-disable-line react/prefer-stateless-function
componentDidMount() {
// Emit the event 'pluginReady'
this.props.updatePlugin(pluginId, 'isReady', true);
}
const Initializer = ({ updatePlugin }) => {
const ref = useRef();
ref.current = updatePlugin;
render() {
return null;
}
}
useEffect(() => {
const getData = async () => {
const requestURL = `/${pluginId}/content-types`;
try {
const { data } = await request(requestURL, { method: 'GET' });
const menu = [
{
name: 'Content Types',
links: data,
},
];
ref.current(pluginId, 'leftMenuSections', menu);
} catch (err) {
strapi.notification.error('content-manager.error.model.fetch');
}
};
getData();
ref.current(pluginId, 'isReady', true);
return () => {};
}, []);
return null;
};
Initializer.propTypes = {
updatePlugin: PropTypes.func.isRequired,
};
export default Initializer;
export default memo(Initializer);

View File

@ -8,14 +8,12 @@
*
*/
import didGetSecuredData from './lifecycles/didGetSecuredData';
function lifecycles() {
// Set hooks for the AdminPage container.
// Note: we don't need to specify the first argument because we already know what "willSecure" refers to.
this.setHooks({
didGetSecuredData,
});
// this.setHooks({
// didGetSecuredData,
// });
}
export default lifecycles;

View File

@ -1,26 +0,0 @@
import { request } from 'strapi-helper-plugin';
import pluginId from '../pluginId';
// TODO: update needs to be updated when the models are retrieved from the admin.
async function didGetSecuredData() {
const { updatePlugin } = this.props;
const requestURL = `/${pluginId}/content-types`;
try {
const { data } = await request(requestURL, { method: 'GET' });
const menu = [
{
name: 'Content Types',
links: data,
},
];
updatePlugin(pluginId, 'leftMenuSections', menu);
} catch (err) {
strapi.notification.error('content-manager.error.model.fetch');
}
}
export default didGetSecuredData;

View File

@ -10,8 +10,6 @@ import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
import pluginId from '../../pluginId';
// Containers
import AuthPage from '../AuthPage';
import EditPage from '../EditPage';
import HomePage from '../HomePage';
import NotFoundPage from '../NotFoundPage';
@ -23,17 +21,10 @@ class App extends React.Component {
}
}
renderRoute = props => <AuthPage {...this.props} {...props} />;
render() {
return (
<div className={pluginId}>
<Switch>
<Route
path={`/plugins/${pluginId}/auth/:authType/:id?`}
render={this.renderRoute}
exact
/>
<Route
path={`/plugins/${pluginId}/:settingType/:actionType/:id?`}
component={EditPage}

View File

@ -1,107 +0,0 @@
/*
*
* AuthPage actions
*
*/
import {
HIDE_LOGIN_ERRORS_INPUT,
ON_CHANGE_INPUT,
SET_ERRORS,
SET_FORM,
SUBMIT,
SUBMIT_ERROR,
SUBMIT_SUCCEEDED,
} from './constants';
export function hideLoginErrorsInput(value) {
return {
type: HIDE_LOGIN_ERRORS_INPUT,
value,
};
}
export function onChangeInput({ target }) {
return {
type: ON_CHANGE_INPUT,
key: target.name,
value: target.value,
};
}
export function setErrors(formErrors) {
return {
type: SET_ERRORS,
formErrors,
};
}
export function setForm(formType, email) {
let data;
switch (formType) {
case 'forgot-password':
data = {
email: '',
};
break;
case 'login':
data = {
identifier: '',
password: '',
rememberMe: true,
};
break;
case 'register':
data = {
username: '',
password: '',
confirmPassword: '',
email: '',
news: false,
};
break;
case 'register-success':
data = {
email,
};
break;
case 'reset-password':
data = {
password: '',
passwordConfirmation: '',
code: email,
};
break;
default:
data = {};
}
return {
type: SET_FORM,
data,
formType,
};
}
export function submit(context) {
return {
type: SUBMIT,
context,
};
}
export function submitError(formErrors) {
return {
type: SUBMIT_ERROR,
formErrors,
};
}
export function submitSucceeded() {
return {
type: SUBMIT_SUCCEEDED,
};
}

View File

@ -1,14 +0,0 @@
/*
*
* AuthPage constants
*
*/
export const HIDE_LOGIN_ERRORS_INPUT = 'UsersPermissions/AuthPage/HIDE_LOGIN_ERRORS_INPUT';
export const ON_CHANGE_INPUT = 'UsersPermissions/AuthPage/ON_CHANGE_INPUT';
export const SET_ERRORS = 'UsersPermissions/AuthPage/SET_ERRORS';
export const SET_FORM = 'UsersPermissions/AuthPage/SET_FORM';
export const SUBMIT = 'UsersPermissions/AuthPage/SUBMIT';
export const SUBMIT_ERROR = 'UsersPermissions/AuthPage/SUBMIT_ERROR';
export const SUBMIT_SUCCEEDED = 'UsersPermissions/AuthPage/SUBMIT_SUCCEEDED';

View File

@ -1,116 +0,0 @@
{
"form": {
"forgot-password": [
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.forgot-password.email.label"
},
"name": "email",
"type": "email",
"placeholder": "users-permissions.Auth.form.forgot-password.email.placeholder"
}
],
"login": [
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.login.username.label"
},
"name": "identifier",
"type": "text",
"placeholder": "users-permissions.Auth.form.login.username.placeholder"
},
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.login.password.label"
},
"name": "password",
"type": "password"
},
{
"customBootstrapClass": "col-md-6",
"label": {
"id": "users-permissions.Auth.form.login.rememberMe.label"
},
"name": "rememberMe",
"type": "checkbox"
}
],
"register": [
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.register.username.label"
},
"name": "username",
"type": "text",
"placeholder": "users-permissions.Auth.form.register.username.placeholder"
},
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.register.password.label"
},
"name": "password",
"type": "password"
},
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.register.confirmPassword.label"
},
"name": "confirmPassword",
"type": "password"
},
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.register.email.label"
},
"name": "email",
"placeholder": "users-permissions.Auth.form.register.email.placeholder",
"type": "email"
},
{
"customBootstrapClass": "col-md-12",
"label": {
"id": "users-permissions.Auth.form.register.news.label"
},
"name": "news",
"type": "checkbox",
"value": false
}
],
"register-success": [
{
"customBootstrapClass": "col-md-12",
"name": "email",
"type": "email",
"label": {
"id": "users-permissions.Auth.form.register-success.email.label"
},
"placeholder": "users-permissions.Auth.form.register-success.email.placeholder"
}
],
"reset-password": [
{
"customBootstrapClass": "col-md-12",
"name": "password",
"type": "password",
"label": {
"id": "users-permissions.Auth.form.register.password.label"
}
},
{
"customBootstrapClass": "col-md-12",
"name": "passwordConfirmation",
"type": "password",
"label": {
"id": "users-permissions.Auth.form.register.confirmPassword.label"
}
}
]
}
}

View File

@ -1,452 +0,0 @@
/**
*
* AuthPage
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { findIndex, get, isBoolean, isEmpty, map, replace } from 'lodash';
import cn from 'classnames';
// Design
import { auth, Button, InputsIndex as Input } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
// Logo
import LogoStrapi from '../../assets/images/logo_strapi.png';
import {
hideLoginErrorsInput,
onChangeInput,
setErrors,
setForm,
submit,
} from './actions';
import form from './form.json';
import reducer from './reducer';
import saga from './saga';
import makeSelectAuthPage from './selectors';
import styles from './styles.scss';
export class AuthPage extends React.Component {
// eslint-disable-line react/prefer-stateless-function
componentDidMount() {
auth.clearAppStorage();
this.setForm();
}
componentDidUpdate(prevProps) {
const {
hideLoginErrorsInput,
match: {
params: { authType },
},
submitSuccess,
} = this.props;
if (authType !== prevProps.match.params.authType) {
this.setForm();
hideLoginErrorsInput(false);
}
if (submitSuccess) {
switch (authType) {
case 'login':
case 'reset-password':
// Check if we have token to handle redirection to login or admin.
// Done to prevent redirection to admin after reset password if user should
// not have access.
auth.getToken()
? this.redirect('/')
: this.redirect('/plugins/users-permissions/auth/login');
break;
case 'register':
this.redirect('/');
// NOTE: prepare for comfirm email;
// this.redirect(`/plugins/users-permissions/auth/register-success/${this.props.modifiedData.email}`);
break;
default:
}
}
}
// Get form Errors shortcut.
getFormErrors = () => {
const { formErrors } = this.props;
return get(formErrors, ['0', 'errors', '0', 'id']);
};
getLogo = () => {
const {
assets: { loginLogo },
} = this.props;
return loginLogo || LogoStrapi;
};
setForm = () => {
const {
location: { search },
match: {
params: { authType, id },
},
setForm,
} = this.props;
const params = search ? replace(search, '?code=', '') : id;
setForm(authType, params);
};
isAuthType = type => {
const {
match: {
params: { authType },
},
} = this.props;
return authType === type;
};
handleSubmit = e => {
const { modifiedData, setErrors, submit } = this.props;
e.preventDefault();
const formErrors = Object.keys(modifiedData).reduce((acc, key) => {
if (
isEmpty(get(modifiedData, key)) &&
!isBoolean(get(modifiedData, key))
) {
acc.push({
name: key,
errors: [{ id: 'components.Input.error.validation.required' }],
});
}
if (
!isEmpty(get(modifiedData, 'password')) &&
!isEmpty(get(modifiedData, 'confirmPassword')) &&
findIndex(acc, ['name', 'confirmPassword']) === -1
) {
if (modifiedData.password.length < 6) {
acc.push({
name: 'password',
errors: [
{
id: 'users-permissions.components.Input.error.password.length',
},
],
});
}
if (
get(modifiedData, 'password') !== get(modifiedData, 'confirmPassword')
) {
acc.push({
name: 'confirmPassword',
errors: [
{
id: 'users-permissions.components.Input.error.password.noMatch',
},
],
});
}
}
return acc;
}, []);
setErrors(formErrors);
if (isEmpty(formErrors)) {
submit(this.context);
}
};
redirect = path => this.props.history.push(path);
renderButton = () => {
const {
match: {
params: { authType },
},
submitSuccess,
} = this.props;
if (this.isAuthType('login')) {
return (
<div className={cn('col-md-6', styles.loginButton)}>
<Button
primary
label="users-permissions.Auth.form.button.login"
type="submit"
/>
</div>
);
}
const isEmailForgotSent =
this.isAuthType('forgot-password') && submitSuccess;
const label = isEmailForgotSent
? 'users-permissions.Auth.form.button.forgot-password.success'
: `users-permissions.Auth.form.button.${authType}`;
return (
<div className={cn('col-md-12', styles.buttonContainer)}>
<Button
className={cn(isEmailForgotSent && styles.buttonForgotSuccess)}
label={label}
style={{ width: '100%' }}
primary={!isEmailForgotSent}
type="submit"
/>
</div>
);
};
renderLogo = () =>
this.isAuthType('register') && (
<div className={styles.logoContainer}>
<img src={this.getLogo()} alt="logo" />
</div>
);
renderLink = () => {
if (this.isAuthType('login')) {
return (
<Link to="/plugins/users-permissions/auth/forgot-password">
<FormattedMessage id="users-permissions.Auth.link.forgot-password" />
</Link>
);
}
if (
this.isAuthType('forgot-password') ||
this.isAuthType('register-success')
) {
return (
<Link to="/plugins/users-permissions/auth/login">
<FormattedMessage id="users-permissions.Auth.link.ready" />
</Link>
);
}
return <div />;
};
renderInputs = () => {
const {
didCheckErrors,
formErrors,
match: {
params: { authType },
},
modifiedData,
noErrorsDescription,
onChangeInput,
submitSuccess,
} = this.props;
const inputs = get(form, ['form', authType]);
const isForgotEmailSent =
this.isAuthType('forgot-password') && submitSuccess;
return map(inputs, (input, key) => {
let label = isForgotEmailSent
? {
id:
'users-permissions.Auth.form.forgot-password.email.label.success',
}
: get(input, 'label');
if (input.name === 'news') {
const handleClick = (e, to) => {
e.preventDefault();
e.stopPropagation();
const win = window.open(`https://strapi.io/${to}`, '_blank');
win.focus();
};
const terms = (
<FormattedMessage
id={`${pluginId}.Auth.privacy-policy-agreement.terms`}
>
{content => (
<span
style={{ color: '#0097f7', cursor: 'pointer' }}
onClick={e => handleClick(e, 'terms')}
>
{content}
</span>
)}
</FormattedMessage>
);
const policy = (
<FormattedMessage
id={`${pluginId}.Auth.privacy-policy-agreement.policy`}
>
{content => (
<span
style={{ color: '#0097f7', cursor: 'pointer' }}
onClick={e => handleClick(e, 'policy')}
>
{content}
</span>
)}
</FormattedMessage>
);
label = () => (
<FormattedMessage id={input.label.id} values={{ terms, policy }} />
);
}
return (
<Input
autoFocus={key === 0}
customBootstrapClass={get(input, 'customBootstrapClass')}
didCheckErrors={didCheckErrors}
errors={get(formErrors, [
findIndex(formErrors, ['name', input.name]),
'errors',
])}
key={get(input, 'name')}
label={label}
name={get(input, 'name')}
onChange={onChangeInput}
placeholder={get(input, 'placeholder')}
type={get(input, 'type')}
validations={{ required: true }}
value={get(modifiedData, get(input, 'name'), get(input, 'value'))}
noErrorsDescription={noErrorsDescription}
/>
);
});
};
render() {
const { modifiedData, noErrorsDescription, submitSuccess } = this.props;
let divStyle = this.isAuthType('register')
? { marginTop: '3.2rem' }
: { marginTop: '.9rem' };
if (this.isAuthType('forgot-password') && submitSuccess) {
divStyle = { marginTop: '.9rem', minHeight: '18.2rem' };
}
return (
<div className={styles.authPage}>
<div className={styles.wrapper}>
<div className={styles.headerContainer}>
{this.isAuthType('register') ? (
<FormattedMessage id="users-permissions.Auth.form.header.register" />
) : (
<img src={this.getLogo()} alt="logo" />
)}
</div>
<div className={styles.headerDescription}>
{this.isAuthType('register') && (
<FormattedMessage id="users-permissions.Auth.header.register.description" />
)}
</div>
<div
className={cn(
styles.formContainer,
this.isAuthType('forgot-password') && submitSuccess
? styles.borderedSuccess
: styles.bordered
)}
style={divStyle}
>
<form onSubmit={this.handleSubmit}>
<div className="container-fluid">
{noErrorsDescription && !isEmpty(this.getFormErrors()) ? (
<div className={styles.errorsContainer}>
<FormattedMessage id={this.getFormErrors()} />
</div>
) : (
''
)}
<div className="row" style={{ textAlign: 'start' }}>
{!submitSuccess && this.renderInputs()}
{this.isAuthType('forgot-password') && submitSuccess && (
<div className={styles.forgotSuccess}>
<FormattedMessage id="users-permissions.Auth.form.forgot-password.email.label.success" />
<br />
<p>{get(modifiedData, 'email', '')}</p>
</div>
)}
{this.renderButton()}
</div>
</div>
</form>
</div>
<div className={styles.linkContainer}>{this.renderLink()}</div>
</div>
{this.renderLogo()}
</div>
);
}
}
AuthPage.contextTypes = {
updatePlugin: PropTypes.func,
};
AuthPage.defaultProps = {
assets: { loginLogo: null },
};
AuthPage.propTypes = {
assets: PropTypes.object,
didCheckErrors: PropTypes.bool.isRequired,
formErrors: PropTypes.array.isRequired,
hideLoginErrorsInput: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
modifiedData: PropTypes.object.isRequired,
noErrorsDescription: PropTypes.bool.isRequired,
onChangeInput: PropTypes.func.isRequired,
setErrors: PropTypes.func.isRequired,
setForm: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
submitSuccess: PropTypes.bool.isRequired,
};
const mapStateToProps = makeSelectAuthPage();
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
hideLoginErrorsInput,
onChangeInput,
setErrors,
setForm,
submit,
},
dispatch
);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps
);
const withReducer = window.strapi.injectReducer({
key: 'authPage',
reducer,
pluginId,
});
const withSaga = window.strapi.injectSaga({ key: 'authPage', saga, pluginId });
export default compose(
withReducer,
withSaga,
withConnect
)(AuthPage);

View File

@ -1,54 +0,0 @@
/*
*
* AuthPage reducer
*
*/
import { fromJS, List, Map } from 'immutable';
import {
HIDE_LOGIN_ERRORS_INPUT,
ON_CHANGE_INPUT,
SET_ERRORS,
SET_FORM,
SUBMIT_ERROR,
SUBMIT_SUCCEEDED,
} from './constants';
const initialState = fromJS({
didCheckErrors: false,
formErrors: List([]),
formType: 'login',
noErrorsDescription: false,
modifiedData: Map({}),
submitSuccess: false,
});
function authPageReducer(state = initialState, action) {
switch (action.type) {
case HIDE_LOGIN_ERRORS_INPUT:
return state.set('noErrorsDescription', action.value);
case ON_CHANGE_INPUT:
return state
.updateIn(['modifiedData', action.key], () => action.value);
case SET_ERRORS:
case SUBMIT_ERROR:
return state
.set('didCheckErrors', !state.get('didCheckErrors'))
.set('formErrors', List(action.formErrors));
case SET_FORM:
return state
.set('formErrors', List([]))
.set('noErrorsDescription', false)
.set('formType', action.formType)
.set('submitSuccess', false)
.set('modifiedData', Map(action.data));
case SUBMIT_SUCCEEDED:
return state
.set('noErrorsDescription', false)
.set('submitSuccess', true);
default:
return state;
}
}
export default authPageReducer;

View File

@ -1,133 +0,0 @@
import { get, includes, isArray, omit, set } from 'lodash';
import { call, fork, put, select, takeLatest } from 'redux-saga/effects';
import { auth, request } from 'strapi-helper-plugin';
import { updateHasAdmin } from '../Initializer/actions';
import { hideLoginErrorsInput, submitError, submitSucceeded } from './actions';
import { SUBMIT } from './constants';
import { makeSelectFormType, makeSelectModifiedData } from './selectors';
export function* submitForm(action) {
try {
const body = yield select(makeSelectModifiedData());
const formType = yield select(makeSelectFormType());
const isRegister = formType === 'register';
let requestURL;
switch (formType) {
case 'login':
requestURL = '/admin/auth/local';
break;
case 'register':
requestURL = '/admin/auth/local/register';
break;
case 'reset-password':
requestURL = '/admin/auth/reset-password';
break;
case 'forgot-password':
requestURL = '/admin/auth/forgot-password';
set(
body,
'url',
`${
strapi.backendURL
}/admin/plugins/users-permissions/auth/reset-password`,
);
break;
default:
}
const response = yield call(request, requestURL, {
method: 'POST',
body: omit(body, 'news'),
});
if (
get(response, 'user.role.name', '') === 'Administrator' ||
isRegister ||
get(response, 'user.isAdmin', false)
) {
yield call(auth.setToken, response.jwt, body.rememberMe);
yield call(auth.setUserInfo, response.user, body.rememberMe);
}
if (isRegister) {
action.context.updatePlugin('users-permissions', 'hasAdminUser', true);
yield put(updateHasAdmin(true));
if (body.news) {
try {
yield call(request, 'https://analytics.strapi.io/register', {
method: 'POST',
body: omit(body, ['password', 'confirmPassword']),
});
} catch (e) {
// Silent.
}
}
}
yield put(submitSucceeded());
} catch (error) {
const formType = yield select(makeSelectFormType());
if (isArray(get(error, ['response', 'payload', 'message']))) {
const errors = error.response.payload.message.reduce((acc, key) => {
const err = key.messages.reduce(
(acc, key) => {
acc.id = `users-permissions.${key.id}`;
return acc;
},
{ id: '' },
);
acc.push(err);
return acc;
}, []);
let formErrors;
switch (formType) {
case 'forgot-password':
formErrors = [{ name: 'email', errors }];
break;
case 'login':
formErrors = [
{ name: 'identifier', errors },
{ name: 'password', errors },
];
yield put(hideLoginErrorsInput(true));
break;
case 'reset-password':
if (
errors[0].id === 'users-permissions.Auth.form.error.code.provide'
) {
strapi.notification.error(errors[0].id);
} else {
formErrors = [{ name: 'password', errors }];
}
break;
case 'register': {
const target = includes(get(errors, ['0', 'id']), 'username')
? 'username'
: 'email';
formErrors = [{ name: target, errors }];
break;
}
default:
}
yield put(submitError(formErrors));
} else {
strapi.notification.error('notification.error');
}
}
}
export default function* defaultSaga() {
yield fork(takeLatest, SUBMIT, submitForm);
}

View File

@ -1,37 +0,0 @@
import { createSelector } from 'reselect';
import pluginId from '../../pluginId';
/**
* Direct selector to the authPage state domain
*/
const selectAuthPageDomain = () => (state) => state.get(`${pluginId}_authPage`);
/**
* Default selector used by AuthPage
*/
const makeSelectAuthPage = () => createSelector(
selectAuthPageDomain(),
(substate) => substate.toJS()
);
/**
* Other specific selectors
*/
const makeSelectFormType = () => createSelector(
selectAuthPageDomain(),
(substate) => substate.get('formType'),
);
const makeSelectModifiedData = () => createSelector(
selectAuthPageDomain(),
(substate) => substate.get('modifiedData').toJS(),
);
export default makeSelectAuthPage;
export {
makeSelectFormType,
makeSelectModifiedData,
selectAuthPageDomain,
};

View File

@ -1,118 +0,0 @@
.authPage {
display: flex;
justify-content: space-around;
text-align: center;
padding: 6.2rem 0;
background: #FAFAFB;
height: 100vh;
-webkit-font-smoothing: antialiased;
}
.wrapper {
height: 22.1rem;
width: 685px;
text-align: center;
background-image: url('../../assets/images/background_empty.svg');
background-position-x: center;
font-size: 1.4rem;
font-family: Lato;
}
.errorsContainer {
margin-top: -21px;
margin-bottom: 18px;
color: #ff203c;
}
.headerContainer {
> span {
line-height: 36px;
font-size: 24px;
font-weight: 600;
}
> img {
margin-top: 1px;
height: 40px;
}
}
.headerDescription {
width: 41.6rem;
text-align: center;
margin: auto;
padding: 13px 30px 0 30px;
line-height: 18px;
color: #333740;
}
.formContainer {
min-height: 20rem;
width: 41.6rem;
margin: 1.4rem auto;
margin-bottom: 0;
padding: 3.9rem 1.5rem 1.5rem 1.5rem;
border-radius: 2px;
background-color: #FFFFFF;
box-shadow: 0 2px 4px 0 #E3E9F3;
}
.loginButton {
margin-top: -6px;
padding-right: 0;
text-align: right;
> button {
margin-right: 1.6rem;
}
}
.buttonContainer {
padding-top: 1rem;
}
.linkContainer {
padding-top: 1.8rem;
> a {
color: #262931;
font-size: 13px;
&:hover, &:active, &:focus {
text-decoration: none;
outline: 0;
}
}
}
.bordered {
border-top: 2px solid #1C5DE7;
}
.borderedSuccess {
border-top: 2px solid #5A9E06;
}
.logoContainer {
position: absolute;
left: 30px; bottom: 30px;
> img {
height: 34px;
}
}
.buttonForgotSuccess {
border: 1px solid #5A9E06;
color: #5A9E06;
}
.forgotSuccess {
width: 100%;
// margin-top: -2px;
text-align: center;
color: #5A9E06;
font-size: 13px;
font-weight: 500;
> p {
margin-top: 17px;
margin-bottom: 18px;
color: #333740;
}
}

View File

@ -1,31 +0,0 @@
/*
*
* Initializer actions
*
*/
import {
INITIALIZE,
INITIALIZE_SUCCEEDED,
UPDATE_HAS_ADMIN,
} from './constants';
export function initialize() {
return {
type: INITIALIZE,
};
}
export function initializeSucceeded(data) {
return {
type: INITIALIZE_SUCCEEDED,
data,
};
}
export function updateHasAdmin(value) {
return {
type: UPDATE_HAS_ADMIN,
value,
};
}

View File

@ -1,10 +0,0 @@
/*
*
* Initializer constants
*
*/
export const INITIALIZE = 'UsersPermissions/Initializer/INITIALIZE';
export const INITIALIZE_SUCCEEDED =
'UsersPermissions/Initializer/INITIALIZE_SUCCEEDED';
export const UPDATE_HAS_ADMIN = 'UsersPermissions/Initializer/UPDATE_HAS_ADMIN';

View File

@ -4,76 +4,24 @@
*
*/
import React from 'react';
import { memo, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import pluginId from '../../pluginId';
import { initialize } from './actions';
const Initializer = ({ updatePlugin }) => {
const ref = useRef();
ref.current = updatePlugin;
import makeSelectInitializer from './selectors';
import reducer from './reducer';
import saga from './saga';
useEffect(() => {
ref.current(pluginId, 'isReady', true);
}, []);
class Initializer extends React.Component {
// eslint-disable-line react/prefer-stateless-function
componentDidMount() {
this.props.initialize();
this.props.unsetAppSecured();
}
componentDidUpdate(prevProps) {
const { shouldUpdate, updatePlugin } = this.props;
if (prevProps.shouldUpdate !== shouldUpdate) {
// Emit the event 'pluginReady' so the app can start
updatePlugin('users-permissions', 'isReady', true);
}
}
render() {
return null;
}
}
return null;
};
Initializer.propTypes = {
initialize: PropTypes.func.isRequired,
shouldUpdate: PropTypes.bool.isRequired,
unsetAppSecured: PropTypes.func.isRequired,
updatePlugin: PropTypes.func.isRequired,
};
const mapStateToProps = makeSelectInitializer();
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
initialize,
},
dispatch,
);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
/* Remove this line if the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withReducer = strapi.injectReducer({ key: 'initializer', reducer, pluginId });
/* Remove the line below the container doesn't have a route and
* check the documentation to see how to create the container's store
*/
const withSaga = strapi.injectSaga({ key: 'initializer', saga, pluginId });
export default memo(Initializer);
export { Initializer };
export default compose(
withReducer,
withSaga,
withConnect,
)(Initializer);

View File

@ -1,28 +0,0 @@
/*
*
* Initializer reducer
*
*/
import { fromJS } from 'immutable';
import { INITIALIZE_SUCCEEDED, UPDATE_HAS_ADMIN } from './constants';
const initialState = fromJS({
hasAdminUser: false,
shouldUpdate: false,
});
function initializerReducer(state = initialState, action) {
switch (action.type) {
case INITIALIZE_SUCCEEDED:
return state
.updateIn(['hasAdminUser'], () => action.data)
.update('shouldUpdate', v => !v);
case UPDATE_HAS_ADMIN:
return state.update('hasAdminUser', () => action.value);
default:
return state;
}
}
export default initializerReducer;

View File

@ -1,24 +0,0 @@
import { fork, takeLatest, call, put } from 'redux-saga/effects';
import { request } from 'strapi-helper-plugin';
import { INITIALIZE } from './constants';
import { initializeSucceeded } from './actions';
export function* initialize() {
try {
const requestURL = '/users-permissions/init';
const { hasAdmin } = yield call(request, requestURL, { method: 'GET' });
yield put(initializeSucceeded(hasAdmin));
} catch (err) {
strapi.notification.error('notification.error');
}
}
// Individual exports for testing
export default function* defaultSaga() {
// See example in containers/HomePage/saga.js
yield fork(takeLatest, INITIALIZE, initialize);
}

View File

@ -1,24 +0,0 @@
import { createSelector } from 'reselect';
import pluginId from '../../pluginId';
/**
* Direct selector to the initializer state domain
*/
const selectInitializerDomain = () => state => state.get(`${pluginId}_initializer`);
/**
* Other specific selectors
*/
/**
* Default selector used by Initializer
*/
const makeSelectInitializer = () =>
createSelector(
selectInitializerDomain(),
substate => substate.toJS(),
);
export default makeSelectInitializer;
export { selectInitializerDomain };

View File

@ -1,33 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`initialize Saga should call the strapi.notification action if the response errors 1`] = `
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/users-permissions/init",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
}
`;
exports[`initialize Saga should dispatch the initializeSucceeded action if it requests the data successfully 1`] = `
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/users-permissions/init",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
}
`;

View File

@ -1,24 +0,0 @@
import { initialize, initializeSucceeded } from '../actions';
import { INITIALIZE, INITIALIZE_SUCCEEDED } from '../constants';
describe('Initializer actions', () => {
describe('Initialize Action', () => {
it('has a type of INITIALIZE', () => {
const expected = {
type: INITIALIZE,
};
expect(initialize()).toEqual(expected);
});
});
describe('InitializeSucceeded Action', () => {
it('has a type of INITIALIZE_SUCCEEDED and returns the given data', () => {
const data = { hasAdmin: true };
const expected = {
type: INITIALIZE_SUCCEEDED,
data,
};
expect(initializeSucceeded(data)).toEqual(expected);
});
});
});

View File

@ -1,73 +1,21 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { Initializer, mapDispatchToProps } from '../index';
import { initialize } from '../actions';
import { Initializer } from '../index';
describe('<Initializer />', () => {
let defaultProps;
beforeEach(() => {
defaultProps = {
initialize: jest.fn(),
shouldUpdate: false,
unsetAppSecured: jest.fn(),
updatePlugin: jest.fn(),
};
it('should not crash', () => {
shallow(<Initializer updatePlugin={jest.fn()} />);
});
it('Should not crash', () => {
shallow(<Initializer {...defaultProps} />);
});
it('should call the updatePlugin props on mount', () => {
const props = { updatePlugin: jest.fn() };
mount(<Initializer {...props} />);
it('should call the initialize prop on mount', () => {
mount(<Initializer {...defaultProps} />);
expect(defaultProps.initialize).toHaveBeenCalled();
});
it('should call the unsetAppSecured prop on mount', () => {
mount(<Initializer {...defaultProps} />);
expect(defaultProps.unsetAppSecured).toHaveBeenCalled();
});
it('should call the updatePlugin prop when the shouldUpdate prop has changed', () => {
const renderedComponent = mount(<Initializer {...defaultProps} />);
renderedComponent.setProps({ shouldUpdate: true });
expect(renderedComponent.instance().props.updatePlugin).toHaveBeenCalledWith(
expect(props.updatePlugin).toHaveBeenCalledWith(
'users-permissions',
'isReady',
true,
true
);
});
it('should not call the updatePlugin prop when the shouldUpdate prop has changed', () => {
const renderedComponent = mount(<Initializer {...defaultProps} />);
renderedComponent.setProps({ shouldUpdate: false });
expect(renderedComponent.instance().props.updatePlugin).not.toHaveBeenCalled();
});
});
describe('<Initializer />, mapDispatchToProps', () => {
describe('initialize', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.initialize).toBeDefined();
});
it('should dispatch the initialize action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.initialize();
expect(dispatch).toHaveBeenCalledWith(initialize());
});
});
});

View File

@ -1,27 +0,0 @@
import { fromJS } from 'immutable';
import { initializeSucceeded } from '../actions';
import initializerReducer from '../reducer';
describe('initializerReducer', () => {
let state;
beforeEach(() => {
state = fromJS({
hasAdminUser: false,
shouldUpdate: false,
});
});
it('returns the initial state', () => {
const expected = state;
expect(initializerReducer(undefined, {})).toEqual(expected);
});
it('should handle the initializeSucceeded action correctly', () => {
const expected = state.set('hasAdminUser', true).set('shouldUpdate', true);
expect(initializerReducer(state, initializeSucceeded(true))).toEqual(expected);
});
});

View File

@ -1,47 +0,0 @@
/**
* Test sagas
*/
/* eslint-disable redux-saga/yield-effects */
/* eslint-disable redux-saga/no-unhandled-errors */
import { fork, put, takeLatest } from 'redux-saga/effects';
import defaultSaga, { initialize } from '../saga';
import { initializeSucceeded } from '../actions';
import { INITIALIZE } from '../constants';
describe('initialize Saga', () => {
let initializeGenerator;
beforeEach(() => {
initializeGenerator = initialize();
const data = { hasAdmin: true };
const callDescriptor = initializeGenerator.next(data).value;
expect(callDescriptor).toMatchSnapshot();
});
it('should dispatch the initializeSucceeded action if it requests the data successfully', () => {
const response = { hasAdmin: true };
const putDescriptor = initializeGenerator.next(response).value;
expect(putDescriptor).toEqual(put(initializeSucceeded(response.hasAdmin)));
});
it('should call the strapi.notification action if the response errors', () => {
const response = new Error('Some error');
initializeGenerator.throw(response).value;
expect(strapi.notification.error).toHaveBeenCalled();
});
});
describe('defaultSaga Saga', () => {
const defaultSagaSaga = defaultSaga();
it('should start task to watch for INITIALIZE action', () => {
const forkDescriptor = defaultSagaSaga.next().value;
expect(forkDescriptor).toEqual(fork(takeLatest, INITIALIZE, initialize));
});
});

View File

@ -1,27 +0,0 @@
import { fromJS } from 'immutable';
import makeSelectInitializerDomain, { selectInitializerDomain } from '../selectors';
import pluginId from '../../../pluginId';
describe('<Initializer /> selectors', () => {
describe('selectInitializerDomain', () => {
it('should select the global state', () => {
const initializerState = fromJS({});
const mockedState = fromJS({
[`${pluginId}_initializer`]: initializerState,
});
expect(selectInitializerDomain()(mockedState)).toEqual(initializerState);
});
});
describe('makeSelectInitiazerDomain', () => {
it('should select the global state (.toJS())', () => {
const initializerState = fromJS({});
const mockedState = fromJS({
[`${pluginId}_initializer`]: initializerState,
});
expect(makeSelectInitializerDomain()(mockedState)).toEqual(initializerState.toJS());
});
});
});

View File

@ -8,9 +8,6 @@
*
*/
// import didGetSecuredData from './lifecycles/didGetSecuredData';
import willSecure from './lifecycles/willSecure';
function lifecycles() {
// TODO: Make it work and remove it when the split-admin PR has been merged
// const componentsToAdd = [
@ -23,10 +20,6 @@ function lifecycles() {
// this.setComponents(componentsToAdd);
// Set hooks for the AdminPage container.
// Note: we don't need to specify the first argument because we already know what "willSecure" refers to.
this.setHooks({
// didGetSecuredData,
willSecure,
});
// Set hooks for the App container of the Content Manager.
// Note: we have to specify the first argument to select a specific container which is located in a plugin, or not.
// this.setHooks('content-manager.App', {

View File

@ -1,24 +0,0 @@
// const pluginId = require('../pluginId');
import pluginId from '../pluginId';
function didGetSecuredData() {
const {
props: { updatePlugin },
} = this;
const leftMenuSections = [
{
links: [
{
label: 'Users',
destination: 'user',
plugin: 'content-manager',
},
],
name: 'Content Types',
},
];
updatePlugin(pluginId, 'leftMenuSections', leftMenuSections);
}
export default didGetSecuredData;

View File

@ -1,83 +0,0 @@
import { auth } from 'strapi-helper-plugin';
function willSecure() {
const {
props: {
hideLeftMenu,
hideLogout,
history,
location: { pathname },
resetLocaleDefaultClassName,
setAppSecured,
setLocaleCustomClassName,
showLeftMenu,
showLogout,
store,
unsetAppSecured,
},
} = this;
const cb = () =>
this.setState({
shouldSecureAfterAllPluginsAreMounted: false,
});
const initializerReducer = store
.getState()
.getIn(['users-permissions_initializer']);
const nonProtectedURLs = '/plugins/users-permissions/auth';
const redirectEndPoint = initializerReducer.get('hasAdminUser')
? '/plugins/users-permissions/auth/login'
: '/plugins/users-permissions/auth/register';
if (auth.getToken()) {
resetLocaleDefaultClassName(); // NOTE: Temporary should be removed when we switch to administrators
setAppSecured();
showLeftMenu();
showLogout();
return cb();
}
if (!pathname.includes(nonProtectedURLs)) {
hideLeftMenu();
hideLogout();
setLocaleCustomClassName('localeDropdownMenuNotLogged'); // NOTE: Temporary should be removed when we switch to administrators
unsetAppSecured();
history.push(redirectEndPoint);
return cb();
}
if (
pathname === '/plugins/users-permissions/auth/login' ||
pathname === '/plugins/users-permissions/auth/register'
) {
hideLeftMenu();
hideLogout();
setLocaleCustomClassName('localeDropdownMenuNotLogged'); // NOTE: Temporary should be removed when we switch to administrators
unsetAppSecured();
history.push(redirectEndPoint);
return cb();
}
if (pathname.includes(nonProtectedURLs)) {
hideLeftMenu();
hideLogout();
setLocaleCustomClassName('localeDropdownMenuNotLogged'); // NOTE: Temporary should be removed when we switch to administrators
unsetAppSecured();
return cb();
}
resetLocaleDefaultClassName(); // NOTE: Temporary should be removed when we switch to administrators
showLeftMenu();
showLogout();
setAppSecured();
return cb();
}
export default willSecure;