diff --git a/package.json b/package.json
index 987d66ef3..bb3dd4aa0 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 c5b4d9f84..db8d6c3ef 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 (
-
-
-
-
-
-
-
-
-
-
-
- );
+ componentDidMount () {
+
}
- renderLoading () {
+ render () {
return (
-
+
+
+
+
+
+
+
+
+
);
}
}
diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx
index 3d2a5ce4d..ae9648784 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 {
- render () {
- if (this.props.flashes.length === 0) {
- return null;
- }
+export default ({ withBottomSpace, spacerClass }: Props) => {
+ const flashes = useStoreState((state: State) => state.flashes.items);
- return (
-
- {
- this.props.flashes.map((flash, index) => (
-
- {index > 0 && }
-
- {flash.message}
-
-
- ))
- }
-
- );
+ if (flashes.length === 0) {
+ return null;
}
-}
-const mapStateToProps = (state: ReduxState) => ({
- flashes: state.flashes,
-});
-
-export default connect(mapStateToProps)(FlashMessageRender);
+ // noinspection PointlessBooleanExpressionJS
+ return (
+
+ {
+ flashes.map((flash, index) => (
+
+ {index > 0 && }
+
+ {flash.message}
+
+
+ ))
+ }
+
+ );
+};
diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx
index 38741f7fc..5a8e1676f 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) => actions.flashes);
-class ForgotPasswordContainer extends React.PureComponent {
- emailField = React.createRef();
+ const handleFieldUpdate = (e: React.ChangeEvent) => setEmail(e.target.value);
- state: State = {
- email: '',
- isSubmitting: false,
- };
-
- handleFieldUpdate = (e: React.ChangeEvent) => this.setState({
- email: e.target.value,
- });
-
- handleSubmission = (e: React.FormEvent) => {
+ const handleSubmission = (e: React.FormEvent) => {
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 (
-
-
- Request Password Reset
-
-
-
-
-
- Enter your account email address to receive instructions on resetting your password.
-
-
-
-
-
-
- Return to Login
-
-
-
-
- );
- }
-}
-
-const mapDispatchToProps = {
- pushFlashMessage,
- clearAllFlashMessages,
+ return (
+
+
+ Request Password Reset
+
+
+
+
+
+
+ Enter your account email address to receive instructions on resetting your password.
+
+
+
+
+
+
+ Return to Login
+
+
+
+
+ );
};
-
-export default connect(null, mapDispatchToProps)(ForgotPasswordContainer);
diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx
index b4c3efb05..e3a0857fb 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, State> {
- state: State = {
- code: '',
- isLoading: false,
- };
+ const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => 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) => {
- if (e.target.value.length > 6) {
- e.target.value = e.target.value.substring(0, 6);
- return e.preventDefault();
+ const onChangeHandler = (e: React.ChangeEvent) => {
+ if (e.target.value.length <= 6) {
+ setCode(e.target.value);
}
-
- this.setState({ code: e.target.value });
};
- submit = (e: React.FormEvent) => {
+ const submit = (e: React.FormEvent) => {
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 (
-
-
- Device Checkpoint
-
-
-
-
- This account is protected with two-factor authentication. A valid authentication token must
- be provided in order to continue.
-
-
-
-
-
-
-
-
-
-
- Return to Login
-
-
-
-
- );
- }
-}
-
-const mapDispatchToProps = {
- pushFlashMessage,
- clearAllFlashMessages,
+ return (
+
+
+ Device Checkpoint
+
+
+
+
+
+
+
+
+
+
+
+
+ Return to Login
+
+
+
+
+ );
};
-
-export default connect(null, mapDispatchToProps)(LoginCheckpointContainer);
diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx
index e111a0305..b5d61d344 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 {
- state: State = {
- isLoading: false,
- };
+ const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes);
- submit = (e: React.FormEvent) => {
+ const submit = (e: React.FormEvent) => {
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) => this.setState({
- [e.target.id]: e.target.value,
- });
-
- render () {
- return (
-
-
- Login to Continue
-
-
-
-
+ return (
+
+
+ Login to Continue
+
+
+
+
+ setUsername(e.target.value)}
+ disabled={isLoading}
+ />
+
+
setPassword(e.target.value)}
+ disabled={isLoading}
/>
-
-
-
-
-
-
-
-
-
- Forgot password?
-
-
-
-
- );
- }
-}
+
+
+
+
+
+
+ Forgot password?
+
+
+
+
+ );
+};
diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx
index f6f0223c4..8f364e335 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
-
+
diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx
index dd4664b35..22734bb72 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
& {}>;
-type Props = Readonly & {
- 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 {
- state: State = {
- isLoading: false,
- };
+ const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => 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) => this.setState({
- password: e.target.value,
- });
-
- onPasswordConfirmChange = (e: React.ChangeEvent) => this.setState({
- passwordConfirm: e.target.value,
- });
-
- onSubmit = (e: React.FormEvent) => {
+ const submit = (e: React.FormEvent) => {
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 (
-
-
- Reset Password
-
-
-
-
-
-
-
-
-
- Passwords must be at least 8 characters in length.
-
-
-
-
-
-
-
-
-
-
-
- Return to Login
-
-
-
-
- );
- }
-}
-
-const mapDispatchToProps = {
- pushFlashMessage,
- clearAllFlashMessages,
+ return (
+
+
+ Reset Password
+
+
+
+
+
+
+
+
setPassword(e.target.value)}
+ />
+
+ Passwords must be at least 8 characters in length.
+
+
+
+
+ setPasswordConfirm(e.target.value)}
+ />
+
+
+
+
+
+
+ Return to Login
+
+
+
+
+ );
};
-
-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 56f860a12..000000000
--- 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 70f88a83e..000000000
--- 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 6ae666b01..000000000
--- 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({
- flashes: flashReducer,
-});
diff --git a/resources/scripts/redux/reducers/flash.ts b/resources/scripts/redux/reducers/flash.ts
deleted file mode 100644
index 3dc9a7d83..000000000
--- 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 707c6da8c..000000000
--- 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 46119c1ee..d70a6b343 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 {
-
diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx
index 673278c7e..8d5ed492f 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 {
-
diff --git a/resources/scripts/state/index.ts b/resources/scripts/state/index.ts
new file mode 100644
index 000000000..e1b4470f5
--- /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 000000000..742b68320
--- /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;
+ clearFlashes: Action;
+}
+
+export interface FlashMessage {
+ id?: string;
+ type: FlashMessageType;
+ title?: string;
+ message: string;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 454ab76ff..27f5324d6 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 b78f76126..95138b14d 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 56197c658..4d7afe1ad 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"