Skip to content

Commit 328347f

Browse files
committed
Convert all of the login components into hook based ones
1 parent aabf9b8 commit 328347f

20 files changed

+435
-598
lines changed

package.json

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,22 @@
22
"name": "pterodactyl-panel",
33
"dependencies": {
44
"@hot-loader/react-dom": "^16.8.6",
5-
"@types/react-redux": "^7.0.9",
65
"axios": "^0.18.0",
76
"brace": "^0.11.1",
87
"classnames": "^2.2.6",
98
"date-fns": "^1.29.0",
9+
"easy-peasy": "^2.5.0",
1010
"feather-icons": "^4.10.0",
1111
"jquery": "^3.3.1",
1212
"lodash": "^4.17.11",
1313
"query-string": "^6.7.0",
1414
"react": "^16.8.6",
1515
"react-dom": "^16.8.6",
1616
"react-hot-loader": "^4.9.0",
17-
"react-redux": "^7.1.0",
1817
"react-router-dom": "^5.0.1",
1918
"react-transition-group": "^4.1.0",
20-
"redux": "^4.0.1",
21-
"redux-persist": "^5.10.0",
2219
"socket.io-client": "^2.2.0",
20+
"use-react-router": "^1.0.7",
2321
"ws-wrapper": "^2.0.0",
2422
"xterm": "^3.5.1"
2523
},
@@ -36,12 +34,10 @@
3634
"@types/react-dom": "^16.8.4",
3735
"@types/react-router-dom": "^4.3.3",
3836
"@types/react-transition-group": "^2.9.2",
39-
"@types/redux-persist": "^4.3.1",
4037
"@types/webpack-env": "^1.13.6",
4138
"@typescript-eslint/eslint-plugin": "^1.10.1",
4239
"@typescript-eslint/parser": "^1.10.1",
4340
"babel-loader": "^8.0.5",
44-
"clean-webpack-plugin": "^0.1.19",
4541
"css-loader": "^2.1.0",
4642
"cssnano": "^4.0.3",
4743
"eslint": "^5.16.0",
@@ -74,10 +70,11 @@
7470
"webpack-manifest-plugin": "^2.0.3"
7571
},
7672
"scripts": {
73+
"clean": "rm -rf public/assets/*.js && rm -rf public/assets/*.css",
7774
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
7875
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
7976
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
80-
"serve": "NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot",
77+
"serve": "yarn run clean && NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot",
8178
"v:serve": "PUBLIC_PATH=http://pterodactyl.test:8080 yarn run serve"
8279
}
8380
}

resources/scripts/components/App.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,27 @@ import * as React from 'react';
22
import { hot } from 'react-hot-loader/root';
33
import { BrowserRouter as Router, Route } from 'react-router-dom';
44
import AuthenticationRouter from '@/routers/AuthenticationRouter';
5-
import { Provider } from 'react-redux';
6-
import { persistor, store } from '@/redux/configure';
7-
import { PersistGate } from 'redux-persist/integration/react';
85
import AccountRouter from '@/routers/AccountRouter';
96
import ServerOverviewContainer from '@/components/ServerOverviewContainer';
7+
import { StoreProvider } from 'easy-peasy';
8+
import { store } from '@/state';
109

1110
class App extends React.PureComponent {
12-
render () {
13-
return (
14-
<Provider store={store}>
15-
<PersistGate persistor={persistor} loading={this.renderLoading()}>
16-
<Router basename={'/'}>
17-
<div className={'mx-auto px-10 w-auto'} style={{ maxWidth: '1000px' }}>
18-
<Route exact path="/" component={ServerOverviewContainer}/>
19-
<Route path="/auth" component={AuthenticationRouter}/>
20-
<Route path="/account" component={AccountRouter}/>
21-
</div>
22-
</Router>
23-
</PersistGate>
24-
</Provider>
25-
);
11+
componentDidMount () {
12+
2613
}
2714

28-
renderLoading () {
15+
render () {
2916
return (
30-
<div className={'spinner spinner-lg'}></div>
17+
<StoreProvider store={store}>
18+
<Router basename={'/'}>
19+
<div className={'mx-auto px-10 w-auto'} style={{ maxWidth: '1000px' }}>
20+
<Route exact path="/" component={ServerOverviewContainer}/>
21+
<Route path="/auth" component={AuthenticationRouter}/>
22+
<Route path="/account" component={AccountRouter}/>
23+
</div>
24+
</Router>
25+
</StoreProvider>
3126
);
3227
}
3328
}
Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
1-
import * as React from 'react';
2-
import { FlashMessage, ReduxState } from '@/redux/types';
3-
import { connect } from 'react-redux';
1+
import React from 'react';
42
import MessageBox from '@/components/MessageBox';
3+
import { State, useStoreState } from 'easy-peasy';
4+
import { ApplicationState } from '@/state/types';
55

66
type Props = Readonly<{
77
spacerClass?: string;
8-
flashes: FlashMessage[];
8+
withBottomSpace?: boolean;
99
}>;
1010

11-
class FlashMessageRender extends React.PureComponent<Props> {
12-
render () {
13-
if (this.props.flashes.length === 0) {
14-
return null;
15-
}
11+
export default ({ withBottomSpace, spacerClass }: Props) => {
12+
const flashes = useStoreState((state: State<ApplicationState>) => state.flashes.items);
1613

17-
return (
18-
<React.Fragment>
19-
{
20-
this.props.flashes.map((flash, index) => (
21-
<React.Fragment key={flash.id || flash.type + flash.message}>
22-
{index > 0 && <div className={this.props.spacerClass || 'mt-2'}></div>}
23-
<MessageBox type={flash.type} title={flash.title}>
24-
{flash.message}
25-
</MessageBox>
26-
</React.Fragment>
27-
))
28-
}
29-
</React.Fragment>
30-
);
14+
if (flashes.length === 0) {
15+
return null;
3116
}
32-
}
3317

34-
const mapStateToProps = (state: ReduxState) => ({
35-
flashes: state.flashes,
36-
});
37-
38-
export default connect(mapStateToProps)(FlashMessageRender);
18+
// noinspection PointlessBooleanExpressionJS
19+
return (
20+
<div className={withBottomSpace === false ? undefined : 'mb-2'}>
21+
{
22+
flashes.map((flash, index) => (
23+
<React.Fragment key={flash.id || flash.type + flash.message}>
24+
{index > 0 && <div className={spacerClass || 'mt-2'}></div>}
25+
<MessageBox type={flash.type} title={flash.title}>
26+
{flash.message}
27+
</MessageBox>
28+
</React.Fragment>
29+
))
30+
}
31+
</div>
32+
);
33+
};
Lines changed: 64 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,78 @@
11
import * as React from 'react';
22
import { Link } from 'react-router-dom';
33
import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail';
4-
import { connect } from 'react-redux';
5-
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
64
import { httpErrorToHuman } from '@/api/http';
75
import LoginFormContainer from '@/components/auth/LoginFormContainer';
6+
import { Actions, useStoreActions } from 'easy-peasy';
7+
import { ApplicationState } from '@/state/types';
8+
import FlashMessageRender from '@/components/FlashMessageRender';
89

9-
type Props = Readonly<{
10-
pushFlashMessage: typeof pushFlashMessage;
11-
clearAllFlashMessages: typeof clearAllFlashMessages;
12-
}>;
10+
export default () => {
11+
const [ isSubmitting, setSubmitting ] = React.useState(false);
12+
const [ email, setEmail ] = React.useState('');
1313

14-
type State = Readonly<{
15-
email: string;
16-
isSubmitting: boolean;
17-
}>;
14+
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationState>) => actions.flashes);
1815

19-
class ForgotPasswordContainer extends React.PureComponent<Props, State> {
20-
emailField = React.createRef<HTMLInputElement>();
16+
const handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value);
2117

22-
state: State = {
23-
email: '',
24-
isSubmitting: false,
25-
};
26-
27-
handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
28-
email: e.target.value,
29-
});
30-
31-
handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
18+
const handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
3219
e.preventDefault();
3320

34-
this.setState({ isSubmitting: true }, () => {
35-
this.props.clearAllFlashMessages();
36-
requestPasswordResetEmail(this.state.email)
37-
.then(response => {
38-
if (this.emailField.current) {
39-
this.emailField.current.value = '';
40-
}
41-
42-
this.props.pushFlashMessage({
43-
type: 'success', title: 'Success', message: response,
44-
});
45-
})
46-
.catch(error => {
47-
console.error(error);
48-
this.props.pushFlashMessage({
49-
type: 'error',
50-
title: 'Error',
51-
message: httpErrorToHuman(error),
52-
});
53-
})
54-
.then(() => this.setState({ isSubmitting: false }));
55-
});
21+
setSubmitting(true);
22+
clearFlashes();
23+
requestPasswordResetEmail(email)
24+
.then(response => {
25+
setEmail('');
26+
addFlash({ type: 'success', title: 'Success', message: response });
27+
})
28+
.catch(error => {
29+
console.error(error);
30+
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
31+
})
32+
.then(() => setSubmitting(false));
5633
};
5734

58-
render () {
59-
return (
60-
<div>
61-
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
62-
Request Password Reset
63-
</h2>
64-
<LoginFormContainer onSubmit={this.handleSubmission}>
65-
<label htmlFor={'email'}>Email</label>
66-
<input
67-
ref={this.emailField}
68-
id={'email'}
69-
type={'email'}
70-
required={true}
71-
className={'input'}
72-
onChange={this.handleFieldUpdate}
73-
autoFocus={true}
74-
/>
75-
<p className={'input-help'}>
76-
Enter your account email address to receive instructions on resetting your password.
77-
</p>
78-
<div className={'mt-6'}>
79-
<button
80-
className={'btn btn-primary btn-jumbo flex justify-center'}
81-
disabled={this.state.isSubmitting || this.state.email.length < 5}
82-
>
83-
{this.state.isSubmitting ?
84-
<div className={'spinner-circle spinner-sm spinner-white'}></div>
85-
:
86-
'Send Email'
87-
}
88-
</button>
89-
</div>
90-
<div className={'mt-6 text-center'}>
91-
<Link
92-
to={'/login'}
93-
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
94-
>
95-
Return to Login
96-
</Link>
97-
</div>
98-
</LoginFormContainer>
99-
</div>
100-
);
101-
}
102-
}
103-
104-
const mapDispatchToProps = {
105-
pushFlashMessage,
106-
clearAllFlashMessages,
35+
return (
36+
<div>
37+
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
38+
Request Password Reset
39+
</h2>
40+
<FlashMessageRender/>
41+
<LoginFormContainer onSubmit={handleSubmission}>
42+
<label htmlFor={'email'}>Email</label>
43+
<input
44+
id={'email'}
45+
type={'email'}
46+
required={true}
47+
className={'input'}
48+
value={email}
49+
onChange={handleFieldUpdate}
50+
autoFocus={true}
51+
/>
52+
<p className={'input-help'}>
53+
Enter your account email address to receive instructions on resetting your password.
54+
</p>
55+
<div className={'mt-6'}>
56+
<button
57+
className={'btn btn-primary btn-jumbo flex justify-center'}
58+
disabled={isSubmitting || email.length < 5}
59+
>
60+
{isSubmitting ?
61+
<div className={'spinner-circle spinner-sm spinner-white'}></div>
62+
:
63+
'Send Email'
64+
}
65+
</button>
66+
</div>
67+
<div className={'mt-6 text-center'}>
68+
<Link
69+
to={'/login'}
70+
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
71+
>
72+
Return to Login
73+
</Link>
74+
</div>
75+
</LoginFormContainer>
76+
</div>
77+
);
10778
};
108-
109-
export default connect(null, mapDispatchToProps)(ForgotPasswordContainer);

0 commit comments

Comments
 (0)