From 328347fab7fe7bcc5f6750bf3ce1eef73dc6078b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 22 Jun 2019 16:45:51 -0700 Subject: [PATCH] Convert all of the login components into hook based ones --- package.json | 11 +- resources/scripts/components/App.tsx | 33 ++- .../scripts/components/FlashMessageRender.tsx | 53 ++--- .../auth/ForgotPasswordContainer.tsx | 159 ++++++------- .../auth/LoginCheckpointContainer.tsx | 169 ++++++-------- .../components/auth/LoginContainer.tsx | 173 +++++++------- .../components/auth/LoginFormContainer.tsx | 2 +- .../auth/ResetPasswordContainer.tsx | 214 +++++++----------- resources/scripts/redux/actions/flash.ts | 17 -- resources/scripts/redux/configure.ts | 14 -- resources/scripts/redux/reducers.ts | 7 - resources/scripts/redux/reducers/flash.ts | 21 -- resources/scripts/redux/types.d.ts | 17 -- resources/scripts/routers/AccountRouter.tsx | 2 - .../scripts/routers/AuthenticationRouter.tsx | 2 - resources/scripts/state/index.ts | 16 ++ resources/scripts/state/types.d.ts | 19 ++ tsconfig.json | 1 + webpack.config.js | 2 - yarn.lock | 101 +++++---- 20 files changed, 435 insertions(+), 598 deletions(-) delete mode 100644 resources/scripts/redux/actions/flash.ts delete mode 100644 resources/scripts/redux/configure.ts delete mode 100644 resources/scripts/redux/reducers.ts delete mode 100644 resources/scripts/redux/reducers/flash.ts delete mode 100644 resources/scripts/redux/types.d.ts create mode 100644 resources/scripts/state/index.ts create mode 100644 resources/scripts/state/types.d.ts 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 ( - - - -
- - - -
-
-
-
- ); + componentDidMount () { + } - renderLoading () { + render () { return ( -
+ + +
+ + + +
+
+
); } } 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 { - 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 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) => 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 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, 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 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 { - 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 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 -
+
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 & {}>; -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 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({ - 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 {
- 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 {
- 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; + clearFlashes: Action; +} + +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"