diff --git a/package.json b/package.json
index 987d66ef..bb3dd4aa 100644
--- a/package.json
+++ b/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"
     }
 }
diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx
index c5b4d9f8..db8d6c3e 100644
--- a/resources/scripts/components/App.tsx
+++ b/resources/scripts/components/App.tsx
@@ -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>
         );
     }
 }
diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx
index 3d2a5ce4..ae964878 100644
--- a/resources/scripts/components/FlashMessageRender.tsx
+++ b/resources/scripts/components/FlashMessageRender.tsx
@@ -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>
+    );
+};
diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx
index 38741f7f..5a8e1676 100644
--- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx
+++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx
@@ -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);
diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx
index b4c3efb0..e3a0857f 100644
--- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx
+++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx
@@ -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'}>&nbsp;</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'}>&nbsp;</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);
diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx
index e111a030..b5d61d34 100644
--- a/resources/scripts/components/auth/LoginContainer.tsx
+++ b/resources/scripts/components/auth/LoginContainer.tsx
@@ -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'}>&nbsp;</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'}>&nbsp;</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>
+    );
+};
diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx
index f6f0223c..8f364e33 100644
--- a/resources/scripts/components/auth/LoginFormContainer.tsx
+++ b/resources/scripts/components/auth/LoginFormContainer.tsx
@@ -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'}>
diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx
index dd4664b3..22734bb7 100644
--- a/resources/scripts/components/auth/ResetPasswordContainer.tsx
+++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx
@@ -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'}>&nbsp;</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'}>&nbsp;</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);
diff --git a/resources/scripts/redux/actions/flash.ts b/resources/scripts/redux/actions/flash.ts
deleted file mode 100644
index 56f860a1..00000000
--- a/resources/scripts/redux/actions/flash.ts
+++ /dev/null
@@ -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,
-});
diff --git a/resources/scripts/redux/configure.ts b/resources/scripts/redux/configure.ts
deleted file mode 100644
index 70f88a83..00000000
--- a/resources/scripts/redux/configure.ts
+++ /dev/null
@@ -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);
diff --git a/resources/scripts/redux/reducers.ts b/resources/scripts/redux/reducers.ts
deleted file mode 100644
index 6ae666b0..00000000
--- a/resources/scripts/redux/reducers.ts
+++ /dev/null
@@ -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,
-});
diff --git a/resources/scripts/redux/reducers/flash.ts b/resources/scripts/redux/reducers/flash.ts
deleted file mode 100644
index 3dc9a7d8..00000000
--- a/resources/scripts/redux/reducers/flash.ts
+++ /dev/null
@@ -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 ];
-    }
-};
diff --git a/resources/scripts/redux/types.d.ts b/resources/scripts/redux/types.d.ts
deleted file mode 100644
index 707c6da8..00000000
--- a/resources/scripts/redux/types.d.ts
+++ /dev/null
@@ -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[];
-}
diff --git a/resources/scripts/routers/AccountRouter.tsx b/resources/scripts/routers/AccountRouter.tsx
index 46119c1e..d70a6b34 100644
--- a/resources/scripts/routers/AccountRouter.tsx
+++ b/resources/scripts/routers/AccountRouter.tsx
@@ -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/>
diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx
index 673278c7..8d5ed492 100644
--- a/resources/scripts/routers/AuthenticationRouter.tsx
+++ b/resources/scripts/routers/AuthenticationRouter.tsx
@@ -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}/>
diff --git a/resources/scripts/state/index.ts b/resources/scripts/state/index.ts
new file mode 100644
index 00000000..e1b4470f
--- /dev/null
+++ b/resources/scripts/state/index.ts
@@ -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);
diff --git a/resources/scripts/state/types.d.ts b/resources/scripts/state/types.d.ts
new file mode 100644
index 00000000..742b6832
--- /dev/null
+++ b/resources/scripts/state/types.d.ts
@@ -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;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 454ab76f..27f5324d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,7 @@
     "strict": true,
     "noImplicitReturns": true,
     "moduleResolution": "node",
+    "allowSyntheticDefaultImports": true,
     "sourceMap": true,
     "baseUrl": ".",
     "lib": ["es2015", "dom"],
diff --git a/webpack.config.js b/webpack.config.js
index b78f7612..95138b14 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -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,
diff --git a/yarn.lock b/yarn.lock
index 56197c65..4d7afe1a 100644
--- a/yarn.lock
+++ b/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"