forked from Alex/Pterodactyl-Panel
Convert all of the login components into hook based ones
This commit is contained in:
parent
aabf9b8a70
commit
328347fab7
11
package.json
11
package.json
@ -2,11 +2,11 @@
|
||||
"name": "pterodactyl-panel",
|
||||
"dependencies": {
|
||||
"@hot-loader/react-dom": "^16.8.6",
|
||||
"@types/react-redux": "^7.0.9",
|
||||
"axios": "^0.18.0",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^1.29.0",
|
||||
"easy-peasy": "^2.5.0",
|
||||
"feather-icons": "^4.10.0",
|
||||
"jquery": "^3.3.1",
|
||||
"lodash": "^4.17.11",
|
||||
@ -14,12 +14,10 @@
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-hot-loader": "^4.9.0",
|
||||
"react-redux": "^7.1.0",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-transition-group": "^4.1.0",
|
||||
"redux": "^4.0.1",
|
||||
"redux-persist": "^5.10.0",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"use-react-router": "^1.0.7",
|
||||
"ws-wrapper": "^2.0.0",
|
||||
"xterm": "^3.5.1"
|
||||
},
|
||||
@ -36,12 +34,10 @@
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/react-router-dom": "^4.3.3",
|
||||
"@types/react-transition-group": "^2.9.2",
|
||||
"@types/redux-persist": "^4.3.1",
|
||||
"@types/webpack-env": "^1.13.6",
|
||||
"@typescript-eslint/eslint-plugin": "^1.10.1",
|
||||
"@typescript-eslint/parser": "^1.10.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"css-loader": "^2.1.0",
|
||||
"cssnano": "^4.0.3",
|
||||
"eslint": "^5.16.0",
|
||||
@ -74,10 +70,11 @@
|
||||
"webpack-manifest-plugin": "^2.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf public/assets/*.js && rm -rf public/assets/*.css",
|
||||
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
|
||||
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
|
||||
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
|
||||
"serve": "NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot",
|
||||
"serve": "yarn run clean && NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot",
|
||||
"v:serve": "PUBLIC_PATH=http://pterodactyl.test:8080 yarn run serve"
|
||||
}
|
||||
}
|
||||
|
@ -2,32 +2,27 @@ import * as React from 'react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||
import AuthenticationRouter from '@/routers/AuthenticationRouter';
|
||||
import { Provider } from 'react-redux';
|
||||
import { persistor, store } from '@/redux/configure';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import AccountRouter from '@/routers/AccountRouter';
|
||||
import ServerOverviewContainer from '@/components/ServerOverviewContainer';
|
||||
import { StoreProvider } from 'easy-peasy';
|
||||
import { store } from '@/state';
|
||||
|
||||
class App extends React.PureComponent {
|
||||
render () {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<PersistGate persistor={persistor} loading={this.renderLoading()}>
|
||||
<Router basename={'/'}>
|
||||
<div className={'mx-auto px-10 w-auto'} style={{ maxWidth: '1000px' }}>
|
||||
<Route exact path="/" component={ServerOverviewContainer}/>
|
||||
<Route path="/auth" component={AuthenticationRouter}/>
|
||||
<Route path="/account" component={AccountRouter}/>
|
||||
</div>
|
||||
</Router>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
);
|
||||
componentDidMount () {
|
||||
|
||||
}
|
||||
|
||||
renderLoading () {
|
||||
render () {
|
||||
return (
|
||||
<div className={'spinner spinner-lg'}></div>
|
||||
<StoreProvider store={store}>
|
||||
<Router basename={'/'}>
|
||||
<div className={'mx-auto px-10 w-auto'} style={{ maxWidth: '1000px' }}>
|
||||
<Route exact path="/" component={ServerOverviewContainer}/>
|
||||
<Route path="/auth" component={AuthenticationRouter}/>
|
||||
<Route path="/account" component={AccountRouter}/>
|
||||
</div>
|
||||
</Router>
|
||||
</StoreProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { FlashMessage, ReduxState } from '@/redux/types';
|
||||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
import MessageBox from '@/components/MessageBox';
|
||||
import { State, useStoreState } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
|
||||
type Props = Readonly<{
|
||||
spacerClass?: string;
|
||||
flashes: FlashMessage[];
|
||||
withBottomSpace?: boolean;
|
||||
}>;
|
||||
|
||||
class FlashMessageRender extends React.PureComponent<Props> {
|
||||
render () {
|
||||
if (this.props.flashes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
export default ({ withBottomSpace, spacerClass }: Props) => {
|
||||
const flashes = useStoreState((state: State<ApplicationState>) => state.flashes.items);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
this.props.flashes.map((flash, index) => (
|
||||
<React.Fragment key={flash.id || flash.type + flash.message}>
|
||||
{index > 0 && <div className={this.props.spacerClass || 'mt-2'}></div>}
|
||||
<MessageBox type={flash.type} title={flash.title}>
|
||||
{flash.message}
|
||||
</MessageBox>
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
if (flashes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: ReduxState) => ({
|
||||
flashes: state.flashes,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(FlashMessageRender);
|
||||
// noinspection PointlessBooleanExpressionJS
|
||||
return (
|
||||
<div className={withBottomSpace === false ? undefined : 'mb-2'}>
|
||||
{
|
||||
flashes.map((flash, index) => (
|
||||
<React.Fragment key={flash.id || flash.type + flash.message}>
|
||||
{index > 0 && <div className={spacerClass || 'mt-2'}></div>}
|
||||
<MessageBox type={flash.type} title={flash.title}>
|
||||
{flash.message}
|
||||
</MessageBox>
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,109 +1,78 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
|
||||
type Props = Readonly<{
|
||||
pushFlashMessage: typeof pushFlashMessage;
|
||||
clearAllFlashMessages: typeof clearAllFlashMessages;
|
||||
}>;
|
||||
export default () => {
|
||||
const [ isSubmitting, setSubmitting ] = React.useState(false);
|
||||
const [ email, setEmail ] = React.useState('');
|
||||
|
||||
type State = Readonly<{
|
||||
email: string;
|
||||
isSubmitting: boolean;
|
||||
}>;
|
||||
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
|
||||
|
||||
class ForgotPasswordContainer extends React.PureComponent<Props, State> {
|
||||
emailField = React.createRef<HTMLInputElement>();
|
||||
const handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value);
|
||||
|
||||
state: State = {
|
||||
email: '',
|
||||
isSubmitting: false,
|
||||
};
|
||||
|
||||
handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||
email: e.target.value,
|
||||
});
|
||||
|
||||
handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ isSubmitting: true }, () => {
|
||||
this.props.clearAllFlashMessages();
|
||||
requestPasswordResetEmail(this.state.email)
|
||||
.then(response => {
|
||||
if (this.emailField.current) {
|
||||
this.emailField.current.value = '';
|
||||
}
|
||||
|
||||
this.props.pushFlashMessage({
|
||||
type: 'success', title: 'Success', message: response,
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
this.props.pushFlashMessage({
|
||||
type: 'error',
|
||||
title: 'Error',
|
||||
message: httpErrorToHuman(error),
|
||||
});
|
||||
})
|
||||
.then(() => this.setState({ isSubmitting: false }));
|
||||
});
|
||||
setSubmitting(true);
|
||||
clearFlashes();
|
||||
requestPasswordResetEmail(email)
|
||||
.then(response => {
|
||||
setEmail('');
|
||||
addFlash({ type: 'success', title: 'Success', message: response });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
|
||||
})
|
||||
.then(() => setSubmitting(false));
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Request Password Reset
|
||||
</h2>
|
||||
<LoginFormContainer onSubmit={this.handleSubmission}>
|
||||
<label htmlFor={'email'}>Email</label>
|
||||
<input
|
||||
ref={this.emailField}
|
||||
id={'email'}
|
||||
type={'email'}
|
||||
required={true}
|
||||
className={'input'}
|
||||
onChange={this.handleFieldUpdate}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<p className={'input-help'}>
|
||||
Enter your account email address to receive instructions on resetting your password.
|
||||
</p>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
className={'btn btn-primary btn-jumbo flex justify-center'}
|
||||
disabled={this.state.isSubmitting || this.state.email.length < 5}
|
||||
>
|
||||
{this.state.isSubmitting ?
|
||||
<div className={'spinner-circle spinner-sm spinner-white'}></div>
|
||||
:
|
||||
'Send Email'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
pushFlashMessage,
|
||||
clearAllFlashMessages,
|
||||
return (
|
||||
<div>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Request Password Reset
|
||||
</h2>
|
||||
<FlashMessageRender/>
|
||||
<LoginFormContainer onSubmit={handleSubmission}>
|
||||
<label htmlFor={'email'}>Email</label>
|
||||
<input
|
||||
id={'email'}
|
||||
type={'email'}
|
||||
required={true}
|
||||
className={'input'}
|
||||
value={email}
|
||||
onChange={handleFieldUpdate}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<p className={'input-help'}>
|
||||
Enter your account email address to receive instructions on resetting your password.
|
||||
</p>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
className={'btn btn-primary btn-jumbo flex justify-center'}
|
||||
disabled={isSubmitting || email.length < 5}
|
||||
>
|
||||
{isSubmitting ?
|
||||
<div className={'spinner-circle spinner-sm spinner-white'}></div>
|
||||
:
|
||||
'Send Email'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ForgotPasswordContainer);
|
||||
|
@ -1,112 +1,91 @@
|
||||
import * as React from 'react';
|
||||
import { RouteComponentProps, StaticContext } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
|
||||
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||
import MessageBox from '@/components/MessageBox';
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import loginCheckpoint from '@/api/auth/loginCheckpoint';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
import useRouter from 'use-react-router';
|
||||
import { StaticContext } from 'react-router';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
|
||||
type State = Readonly<{
|
||||
isLoading: boolean;
|
||||
errorMessage?: string;
|
||||
code: string;
|
||||
}>;
|
||||
export default () => {
|
||||
const [ code, setCode ] = useState('');
|
||||
const [ isLoading, setIsLoading ] = useState(false);
|
||||
|
||||
class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps<{}, StaticContext, { token: string }>, State> {
|
||||
state: State = {
|
||||
code: '',
|
||||
isLoading: false,
|
||||
};
|
||||
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
|
||||
const { history, location: { state } } = useRouter<{}, StaticContext, { token?: string }>();
|
||||
|
||||
componentDidMount () {
|
||||
const { state } = this.props.location;
|
||||
if (!state || !state.token) {
|
||||
this.props.history.replace('/login');
|
||||
}
|
||||
if (!state || !state.token) {
|
||||
return history.replace('/login');
|
||||
}
|
||||
|
||||
onChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.value.length > 6) {
|
||||
e.target.value = e.target.value.substring(0, 6);
|
||||
return e.preventDefault();
|
||||
const onChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.value.length <= 6) {
|
||||
setCode(e.target.value);
|
||||
}
|
||||
|
||||
this.setState({ code: e.target.value });
|
||||
};
|
||||
|
||||
submit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const submit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ isLoading: true }, () => {
|
||||
loginCheckpoint(this.props.location.state.token, this.state.code)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
window.location = response.intended || '/';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
this.setState({ errorMessage: httpErrorToHuman(error), isLoading: false });
|
||||
});
|
||||
});
|
||||
setIsLoading(true);
|
||||
clearFlashes();
|
||||
|
||||
loginCheckpoint(state.token!, code)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
window.location = response.intended || '/';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Device Checkpoint
|
||||
</h2>
|
||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||
<LoginFormContainer onSubmit={this.submit}>
|
||||
<MessageBox type={'warning'}>
|
||||
This account is protected with two-factor authentication. A valid authentication token must
|
||||
be provided in order to continue.
|
||||
</MessageBox>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'authentication_code'}>Authentication Code</label>
|
||||
<input
|
||||
id={'authentication_code'}
|
||||
type={'number'}
|
||||
autoFocus={true}
|
||||
className={'input'}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={this.state.isLoading || this.state.code.length !== 6}
|
||||
>
|
||||
{this.state.isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Continue'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
pushFlashMessage,
|
||||
clearAllFlashMessages,
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Device Checkpoint
|
||||
</h2>
|
||||
<FlashMessageRender/>
|
||||
<LoginFormContainer onSubmit={submit}>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'authentication_code'}>Authentication Code</label>
|
||||
<input
|
||||
id={'authentication_code'}
|
||||
type={'number'}
|
||||
autoFocus={true}
|
||||
className={'input'}
|
||||
value={code}
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={isLoading || code.length !== 6}
|
||||
>
|
||||
{isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Continue'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LoginCheckpointContainer);
|
||||
|
@ -1,111 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import login from '@/api/auth/login';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
import useRouter from 'use-react-router';
|
||||
|
||||
type State = Readonly<{
|
||||
errorMessage?: string;
|
||||
isLoading: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}>;
|
||||
export default () => {
|
||||
const [ username, setUsername ] = useState('');
|
||||
const [ password, setPassword ] = useState('');
|
||||
const [ isLoading, setLoading ] = useState(false);
|
||||
const { history } = useRouter();
|
||||
|
||||
export default class LoginContainer extends React.PureComponent<RouteComponentProps, State> {
|
||||
state: State = {
|
||||
isLoading: false,
|
||||
};
|
||||
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
|
||||
|
||||
submit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const submit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { username, password } = this.state;
|
||||
setLoading(true);
|
||||
clearFlashes();
|
||||
|
||||
this.setState({ isLoading: true }, () => {
|
||||
login(username!, password!)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
window.location = response.intended || '/';
|
||||
return;
|
||||
}
|
||||
login(username!, password!)
|
||||
.then(response => {
|
||||
if (response.complete) {
|
||||
// @ts-ignore
|
||||
window.location = response.intended || '/';
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.history.replace('/login/checkpoint', {
|
||||
token: response.confirmationToken,
|
||||
});
|
||||
})
|
||||
.catch(error => this.setState({
|
||||
isLoading: false,
|
||||
errorMessage: httpErrorToHuman(error),
|
||||
}, () => console.error(error)));
|
||||
});
|
||||
history.replace('/login/checkpoint', { token: response.confirmationToken });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
||||
setLoading(false);
|
||||
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
|
||||
});
|
||||
};
|
||||
|
||||
canSubmit () {
|
||||
if (!this.state.username || !this.state.password) {
|
||||
return false;
|
||||
}
|
||||
const canSubmit = () => username && password && username.length > 0 && password.length > 0;
|
||||
|
||||
return this.state.username.length > 0 && this.state.password.length > 0;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||
[e.target.id]: e.target.value,
|
||||
});
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Login to Continue
|
||||
</h2>
|
||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||
<LoginFormContainer onSubmit={this.submit}>
|
||||
<label htmlFor={'username'}>Username or Email</label>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Login to Continue
|
||||
</h2>
|
||||
<FlashMessageRender/>
|
||||
<LoginFormContainer onSubmit={submit}>
|
||||
<label htmlFor={'username'}>Username or Email</label>
|
||||
<input
|
||||
id={'username'}
|
||||
autoFocus={true}
|
||||
required={true}
|
||||
className={'input'}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'password'}>Password</label>
|
||||
<input
|
||||
id={'username'}
|
||||
autoFocus={true}
|
||||
id={'password'}
|
||||
required={true}
|
||||
type={'password'}
|
||||
className={'input'}
|
||||
onChange={this.handleFieldUpdate}
|
||||
disabled={this.state.isLoading}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'password'}>Password</label>
|
||||
<input
|
||||
id={'password'}
|
||||
required={true}
|
||||
type={'password'}
|
||||
className={'input'}
|
||||
onChange={this.handleFieldUpdate}
|
||||
disabled={this.state.isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={this.state.isLoading || !this.canSubmit()}
|
||||
>
|
||||
{this.state.isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Login'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/password'}
|
||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={isLoading || !canSubmit()}
|
||||
>
|
||||
{isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Login'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/password'}
|
||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ export default ({ className, ...props }: React.DetailedHTMLProps<React.FormHTMLA
|
||||
paddingLeft: 0,
|
||||
}}
|
||||
>
|
||||
<div className={'flex-none'}>
|
||||
<div className={'flex-none select-none'}>
|
||||
<img src={'/assets/pterodactyl.svg'} className={'w-64'}/>
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
|
@ -1,151 +1,109 @@
|
||||
import * as React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { parse } from 'query-string';
|
||||
import { Link } from 'react-router-dom';
|
||||
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||
import performPasswordReset from '@/api/auth/performPasswordReset';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
|
||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
|
||||
type State = Readonly<{
|
||||
email?: string;
|
||||
password?: string;
|
||||
passwordConfirm?: string;
|
||||
isLoading: boolean;
|
||||
errorMessage?: string;
|
||||
}>;
|
||||
type Props = Readonly<RouteComponentProps<{ token: string }> & {}>;
|
||||
|
||||
type Props = Readonly<RouteComponentProps<{ token: string }> & {
|
||||
pushFlashMessage: typeof pushFlashMessage;
|
||||
clearAllFlashMessages: typeof clearAllFlashMessages;
|
||||
}>;
|
||||
export default (props: Props) => {
|
||||
const [ isLoading, setIsLoading ] = useState(false);
|
||||
const [ email, setEmail ] = useState('');
|
||||
const [ password, setPassword ] = useState('');
|
||||
const [ passwordConfirm, setPasswordConfirm ] = useState('');
|
||||
|
||||
class ResetPasswordContainer extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
isLoading: false,
|
||||
};
|
||||
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
|
||||
|
||||
componentDidMount () {
|
||||
const parsed = parse(this.props.location.search);
|
||||
|
||||
this.setState({ email: parsed.email as string || undefined });
|
||||
const parsed = parse(props.location.search);
|
||||
if (email.length === 0 && parsed.email) {
|
||||
setEmail(parsed.email as string);
|
||||
}
|
||||
|
||||
canSubmit () {
|
||||
if (!this.state.password || !this.state.email) {
|
||||
return false;
|
||||
}
|
||||
const canSubmit = () => password && email && password.length >= 8 && password === passwordConfirm;
|
||||
|
||||
return this.state.password.length >= 8 && this.state.password === this.state.passwordConfirm;
|
||||
}
|
||||
|
||||
onPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||
password: e.target.value,
|
||||
});
|
||||
|
||||
onPasswordConfirmChange = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||
passwordConfirm: e.target.value,
|
||||
});
|
||||
|
||||
onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const submit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { password, passwordConfirm, email } = this.state;
|
||||
if (!password || !email || !passwordConfirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.clearAllFlashMessages();
|
||||
this.setState({ isLoading: true }, () => {
|
||||
performPasswordReset(email, {
|
||||
token: this.props.match.params.token,
|
||||
password: password,
|
||||
passwordConfirmation: passwordConfirm,
|
||||
})
|
||||
.then(response => {
|
||||
if (response.redirectTo) {
|
||||
// @ts-ignore
|
||||
window.location = response.redirectTo;
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
clearFlashes();
|
||||
|
||||
this.props.pushFlashMessage({
|
||||
type: 'success',
|
||||
message: 'Your password has been reset, please login to continue.',
|
||||
});
|
||||
this.props.history.push('/login');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
this.setState({ errorMessage: httpErrorToHuman(error) });
|
||||
})
|
||||
.then(() => this.setState({ isLoading: false }));
|
||||
});
|
||||
performPasswordReset(email, {
|
||||
token: props.match.params.token, password, passwordConfirmation: passwordConfirm,
|
||||
})
|
||||
.then(() => {
|
||||
addFlash({ type: 'success', message: 'Your password has been reset, please login to continue.' });
|
||||
props.history.push('/login');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
|
||||
})
|
||||
.then(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Reset Password
|
||||
</h2>
|
||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||
<LoginFormContainer onSubmit={this.onSubmit}>
|
||||
<label>Email</label>
|
||||
<input value={this.state.email || ''} disabled={true}/>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'new_password'}>New Password</label>
|
||||
<input
|
||||
id={'new_password'}
|
||||
type={'password'}
|
||||
required={true}
|
||||
onChange={this.onPasswordChange}
|
||||
/>
|
||||
<p className={'input-help'}>
|
||||
Passwords must be at least 8 characters in length.
|
||||
</p>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'new_password_confirm'}>Confirm New Password</label>
|
||||
<input
|
||||
id={'new_password_confirm'}
|
||||
type={'password'}
|
||||
required={true}
|
||||
onChange={this.onPasswordConfirmChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={this.state.isLoading || !this.canSubmit()}
|
||||
>
|
||||
{this.state.isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Reset Password'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
pushFlashMessage,
|
||||
clearAllFlashMessages,
|
||||
return (
|
||||
<div>
|
||||
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||
Reset Password
|
||||
</h2>
|
||||
<FlashMessageRender/>
|
||||
<LoginFormContainer onSubmit={submit}>
|
||||
<label>Email</label>
|
||||
<input className={'input'} value={email} disabled={true}/>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'new_password'}>New Password</label>
|
||||
<input
|
||||
id={'new_password'}
|
||||
className={'input'}
|
||||
type={'password'}
|
||||
required={true}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
<p className={'input-help'}>
|
||||
Passwords must be at least 8 characters in length.
|
||||
</p>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<label htmlFor={'new_password_confirm'}>Confirm New Password</label>
|
||||
<input
|
||||
id={'new_password_confirm'}
|
||||
className={'input'}
|
||||
type={'password'}
|
||||
required={true}
|
||||
onChange={e => setPasswordConfirm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
type={'submit'}
|
||||
className={'btn btn-primary btn-jumbo'}
|
||||
disabled={isLoading || !canSubmit()}
|
||||
>
|
||||
{isLoading ?
|
||||
<span className={'spinner white'}> </span>
|
||||
:
|
||||
'Reset Password'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div className={'mt-6 text-center'}>
|
||||
<Link
|
||||
to={'/login'}
|
||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||
>
|
||||
Return to Login
|
||||
</Link>
|
||||
</div>
|
||||
</LoginFormContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ResetPasswordContainer);
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { FlashMessage } from '@/redux/types';
|
||||
|
||||
export const PUSH_FLASH_MESSAGE = 'PUSH_FLASH_MESSAGE';
|
||||
export const REMOVE_FLASH_MESSAGE = 'REMOVE_FLASH_MESSAGE';
|
||||
export const CLEAR_ALL_FLASH_MESSAGES = 'CLEAR_ALL_FLASH_MESSAGES';
|
||||
|
||||
export const pushFlashMessage = (payload: FlashMessage) => ({
|
||||
type: PUSH_FLASH_MESSAGE, payload,
|
||||
});
|
||||
|
||||
export const removeFlashMessage = (id: string) => ({
|
||||
type: REMOVE_FLASH_MESSAGE, payload: id,
|
||||
});
|
||||
|
||||
export const clearAllFlashMessages = () => ({
|
||||
type: CLEAR_ALL_FLASH_MESSAGES,
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { createStore } from 'redux';
|
||||
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
|
||||
import storage from 'redux-persist/lib/storage';
|
||||
import { reducers } from './reducers';
|
||||
|
||||
const persistConfig: PersistConfig = {
|
||||
key: 'root',
|
||||
storage,
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, reducers);
|
||||
|
||||
export const store = createStore(persistedReducer);
|
||||
export const persistor = persistStore(store);
|
@ -1,7 +0,0 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import flashReducer from './reducers/flash';
|
||||
import { ReduxState } from '@/redux/types';
|
||||
|
||||
export const reducers = combineReducers<ReduxState>({
|
||||
flashes: flashReducer,
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
import { FlashMessage, ReduxReducerAction } from '@/redux/types';
|
||||
import { CLEAR_ALL_FLASH_MESSAGES, PUSH_FLASH_MESSAGE, REMOVE_FLASH_MESSAGE } from '@/redux/actions/flash';
|
||||
|
||||
export default (state: FlashMessage[] = [], action: ReduxReducerAction) => {
|
||||
switch (action.type) {
|
||||
case PUSH_FLASH_MESSAGE:
|
||||
return [ ...state.filter(flash => {
|
||||
if (action.payload.id && flash.id) {
|
||||
return flash.id !== action.payload.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}), action.payload ];
|
||||
case REMOVE_FLASH_MESSAGE:
|
||||
return [ ...state.filter(flash => flash.id !== action.payload) ];
|
||||
case CLEAR_ALL_FLASH_MESSAGES:
|
||||
return [];
|
||||
default:
|
||||
return [ ...state ];
|
||||
}
|
||||
};
|
17
resources/scripts/redux/types.d.ts
vendored
17
resources/scripts/redux/types.d.ts
vendored
@ -1,17 +0,0 @@
|
||||
import { FlashMessageType } from '@/components/MessageBox';
|
||||
|
||||
export interface ReduxReducerAction {
|
||||
type: string;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface FlashMessage {
|
||||
id?: string;
|
||||
type: FlashMessageType;
|
||||
title?: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ReduxState {
|
||||
flashes: FlashMessage[];
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import DesignElements from '@/components/account/DesignElements';
|
||||
|
||||
export default class AccountRouter extends React.PureComponent {
|
||||
@ -13,7 +12,6 @@ export default class AccountRouter extends React.PureComponent {
|
||||
<TransitionGroup className={'route-transition-group'}>
|
||||
<CSSTransition key={location.key} timeout={150} classNames={'fade'}>
|
||||
<section>
|
||||
<FlashMessageRender/>
|
||||
<Switch location={location}>
|
||||
<Route path={'/'} component={DesignElements} exact/>
|
||||
<Route path={'/design'} component={DesignElements} exact/>
|
||||
|
@ -3,7 +3,6 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import LoginContainer from '@/components/auth/LoginContainer';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer';
|
||||
import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer';
|
||||
|
||||
@ -16,7 +15,6 @@ export default class AuthenticationRouter extends React.PureComponent {
|
||||
<TransitionGroup className={'route-transition-group mt-32'}>
|
||||
<CSSTransition key={location.key} timeout={150} classNames={'fade'}>
|
||||
<section>
|
||||
<FlashMessageRender/>
|
||||
<Switch location={location}>
|
||||
<Route path={'/login'} component={LoginContainer} exact/>
|
||||
<Route path={'/login/checkpoint'} component={LoginCheckpointContainer}/>
|
||||
|
16
resources/scripts/state/index.ts
Normal file
16
resources/scripts/state/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { action, createStore } from 'easy-peasy';
|
||||
import { ApplicationState } from '@/state/types';
|
||||
|
||||
const state: ApplicationState = {
|
||||
flashes: {
|
||||
items: [],
|
||||
addFlash: action((state, payload) => {
|
||||
state.items.push(payload);
|
||||
}),
|
||||
clearFlashes: action(state => {
|
||||
state.items = [];
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const store = createStore(state);
|
19
resources/scripts/state/types.d.ts
vendored
Normal file
19
resources/scripts/state/types.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { FlashMessageType } from '@/components/MessageBox';
|
||||
import { Action } from 'easy-peasy';
|
||||
|
||||
export interface ApplicationState {
|
||||
flashes: FlashState;
|
||||
}
|
||||
|
||||
export interface FlashState {
|
||||
items: FlashMessage[];
|
||||
addFlash: Action<FlashState, FlashMessage>;
|
||||
clearFlashes: Action<FlashState>;
|
||||
}
|
||||
|
||||
export interface FlashMessage {
|
||||
id?: string;
|
||||
type: FlashMessageType;
|
||||
title?: string;
|
||||
message: string;
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"lib": ["es2015", "dom"],
|
||||
|
@ -4,7 +4,6 @@ const tailwind = require('tailwindcss');
|
||||
const glob = require('glob-all');
|
||||
|
||||
const AssetsManifestPlugin = require('webpack-assets-manifest');
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const PurgeCssPlugin = require('purgecss-webpack-plugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
@ -13,7 +12,6 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
let plugins = [
|
||||
new CleanPlugin(path.resolve(__dirname, 'public/assets')),
|
||||
new MiniCssExtractPlugin({ filename: isProduction ? 'bundle.[chunkhash:8].css' : 'bundle.[hash:8].css' }),
|
||||
new AssetsManifestPlugin({
|
||||
writeToDisk: true,
|
||||
|
101
yarn.lock
101
yarn.lock
@ -605,7 +605,7 @@
|
||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
|
||||
dependencies:
|
||||
@ -751,13 +751,6 @@
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220"
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/lodash@^4.14.119":
|
||||
version "4.14.119"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39"
|
||||
@ -778,15 +771,6 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.0.9":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.0.9.tgz#4825ee2872c44768916304b6bb8df5b46d443b88"
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^4.3.3":
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.3.tgz#7837e3e9fefbc84a8f6c8a51dca004f4e83e94e3"
|
||||
@ -815,12 +799,6 @@
|
||||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/redux-persist@^4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/redux-persist/-/redux-persist-4.3.1.tgz#aa4c876859e0bea5155e5f7980e5b8c4699dc2e6"
|
||||
dependencies:
|
||||
redux-persist "*"
|
||||
|
||||
"@types/webpack-env@^1.13.6":
|
||||
version "1.13.6"
|
||||
resolved "http://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"
|
||||
@ -1908,12 +1886,6 @@ clean-css@4.1.x:
|
||||
dependencies:
|
||||
source-map "0.5.x"
|
||||
|
||||
clean-webpack-plugin@^0.1.19:
|
||||
version "0.1.19"
|
||||
resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz#ceda8bb96b00fe168e9b080272960d20fdcadd6d"
|
||||
dependencies:
|
||||
rimraf "^2.6.1"
|
||||
|
||||
cli-cursor@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
||||
@ -2671,6 +2643,18 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
easy-peasy@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/easy-peasy/-/easy-peasy-2.5.0.tgz#5366ff59c71c980cc31ec230dc8855f72b8d2875"
|
||||
dependencies:
|
||||
immer "^3.1.3"
|
||||
memoizerific "^1.11.3"
|
||||
redux "^4.0.1"
|
||||
redux-thunk "^2.3.0"
|
||||
shallowequal "^1.1.0"
|
||||
type-zoo "^3.3.0"
|
||||
typelevel-ts "^0.3.5"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@ -3773,6 +3757,10 @@ ignore@^5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558"
|
||||
|
||||
immer@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-3.1.3.tgz#59bc742b2aab6e2c676445edb653e588a23c70fc"
|
||||
|
||||
import-cwd@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||
@ -3884,7 +3872,7 @@ interpret@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||
|
||||
invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4:
|
||||
invariant@^2.2.0, invariant@^2.2.2:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
dependencies:
|
||||
@ -4535,6 +4523,10 @@ map-cache@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
|
||||
|
||||
map-or-similar@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08"
|
||||
|
||||
map-visit@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
|
||||
@ -4570,6 +4562,12 @@ mem@^4.0.0:
|
||||
mimic-fn "^1.0.0"
|
||||
p-is-promise "^2.0.0"
|
||||
|
||||
memoizerific@^1.11.3:
|
||||
version "1.11.3"
|
||||
resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a"
|
||||
dependencies:
|
||||
map-or-similar "^1.5.0"
|
||||
|
||||
memory-fs@^0.4.0, memory-fs@~0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
||||
@ -5978,7 +5976,7 @@ promise-inflight@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
|
||||
|
||||
prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.6.1, prop-types@^15.6.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
dependencies:
|
||||
@ -6148,7 +6146,7 @@ react-hot-loader@^4.9.0:
|
||||
shallowequal "^1.0.2"
|
||||
source-map "^0.7.3"
|
||||
|
||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
|
||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.8.6"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
||||
|
||||
@ -6156,17 +6154,6 @@ react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
|
||||
react-redux@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.0.tgz#72af7cf490a74acdc516ea9c1dd80e25af9ea0b2"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.5"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
invariant "^2.2.4"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.8.6"
|
||||
|
||||
react-router-dom@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be"
|
||||
@ -6291,11 +6278,11 @@ reduce-css-calc@^2.0.0:
|
||||
css-unit-converter "^1.1.1"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
redux-persist@*, redux-persist@^5.10.0:
|
||||
version "5.10.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-5.10.0.tgz#5d8d802c5571e55924efc1c3a9b23575283be62b"
|
||||
redux-thunk@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
|
||||
redux@^4.0.0, redux@^4.0.1:
|
||||
redux@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"
|
||||
dependencies:
|
||||
@ -6734,7 +6721,7 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallowequal@^1.0.2:
|
||||
shallowequal@^1.0.2, shallowequal@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
|
||||
@ -7378,10 +7365,18 @@ type-is@~1.6.16:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.18"
|
||||
|
||||
type-zoo@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/type-zoo/-/type-zoo-3.3.0.tgz#d5b59393b09a48ac30380c50e2369e828817e9b3"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typelevel-ts@^0.3.5:
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/typelevel-ts/-/typelevel-ts-0.3.5.tgz#9f242c193fa6c5489d2342c642ab8912a065af9d"
|
||||
|
||||
typescript@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b"
|
||||
@ -7537,6 +7532,16 @@ url@^0.11.0:
|
||||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
|
||||
use-force-update@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/use-force-update/-/use-force-update-1.0.5.tgz#9b7192f73f6cb90d592b225858c1562719d7c184"
|
||||
|
||||
use-react-router@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/use-react-router/-/use-react-router-1.0.7.tgz#04216066d87e45040309f24d2fd5e9f28308b3e2"
|
||||
dependencies:
|
||||
use-force-update "^1.0.5"
|
||||
|
||||
use@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544"
|
||||
|
Loading…
Reference in New Issue
Block a user