Commit 73f18366 authored by imtiny's avatar imtiny

add login page with login action

parent dec8a78a
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@reduxjs/toolkit": "^1.2.5", "@reduxjs/toolkit": "^1.2.5",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
...@@ -13,12 +15,19 @@ ...@@ -13,12 +15,19 @@
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7", "@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.5", "@types/react-router-dom": "^5.1.5",
"@types/redux-logger": "^3.0.7",
"@types/yup": "^0.28.1",
"add": "^2.0.6",
"formik": "^2.1.4",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"typescript": "~3.8.2" "redux-logger": "^3.0.6",
"typescript": "~3.8.2",
"yarn": "^1.22.4",
"yup": "^0.28.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
......
...@@ -3,6 +3,7 @@ import './App.css'; ...@@ -3,6 +3,7 @@ import './App.css';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from './pages/Home'; import Home from './pages/Home';
import Login from './pages/Login';
const NotFound = () => { const NotFound = () => {
return ( return (
...@@ -18,6 +19,7 @@ function App() { ...@@ -18,6 +19,7 @@ function App() {
<div> <div>
<Switch> <Switch>
<Route exact path='/' component={Home} /> <Route exact path='/' component={Home} />
<Route path='/login' component={Login} />
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
</div> </div>
......
import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppThunk } from '../../app/store'; import { AppThunk, RootState } from '../store';
interface UserState { interface UserState {
name?: string; name?: string;
...@@ -26,29 +26,29 @@ const initialState: UserState = { ...@@ -26,29 +26,29 @@ const initialState: UserState = {
isLoggedOut: true isLoggedOut: true
} }
const loginRequest = createAction('user/login'); // const loginRequest = createAction('user/login');
const loginSuccess = createAction('user/login/success', function prepare(info: UserInfo) { // const loginSuccess = createAction('user/login/success', function prepare(info: UserInfo) {
return { // return {
payload: info // payload: info
} // }
}); // });
const loginFailure = createAction('user/login/failure', function prepare(error: UserError) { // const loginFailure = createAction('user/login/failure', function prepare(error: UserError) {
return { // return {
payload: error // payload: error
} // }
}); // });
const logout = createAction('user/logout'); // const logout = createAction('user/logout');
export const userSlice = createSlice({ export const userSlice = createSlice({
name: 'user', name: 'user',
initialState, initialState,
reducers: { reducers: {
[loginRequest]: (state, action) => { loginRequest: (state, action) => {
return { return {
isLoggingIn: true isLoggingIn: true
} }
}, },
[loginSuccess]: (state, action: PayloadAction<UserInfo>) => { loginSuccess: (state, action: PayloadAction<UserInfo>) => {
return { return {
isLoggingIn: false, isLoggingIn: false,
isLoggedOut: false, isLoggedOut: false,
...@@ -56,26 +56,27 @@ export const userSlice = createSlice({ ...@@ -56,26 +56,27 @@ export const userSlice = createSlice({
email: action.payload.email email: action.payload.email
} }
}, },
[loginFailure]: (state, action: PayloadAction<UserError>) => { loginFailure: (state, action: PayloadAction<UserError>) => {
return { return {
loggingError: action.payload.error loggingError: action.payload.error
} }
} },
[logout]: (state, action) => { logout: (state, action) => {
return { return {
isLoggedOut: true isLoggedOut: true
} }
} }}
},
}) })
export const { loginRequest, loginSuccess, loginFailure, logout } = userSlice.actions;
interface LoginRequestPayload { interface LoginRequestPayload {
username: string; username: string;
password: string; password: string;
} }
export const userLogin = (payload: LoginRequestPayload): AppThunk => dispatch => { export const userLogin = (payload: LoginRequestPayload): AppThunk => dispatch => {
dispatch(loginRequest) dispatch(loginRequest({}))
setTimeout(() => { setTimeout(() => {
dispatch(loginSuccess({name: payload.username, email: '[email protected]'})); dispatch(loginSuccess({name: payload.username, email: '[email protected]'}));
}, 2000); }, 2000);
...@@ -86,3 +87,7 @@ export const userLogout = (): AppThunk => dispatch => { ...@@ -86,3 +87,7 @@ export const userLogout = (): AppThunk => dispatch => {
dispatch(logout) dispatch(logout)
}, 1000) }, 1000)
} }
export const selectUser = (state: RootState) => state.user;
export default userSlice.reducer;
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; import { combineReducers, configureStore, ThunkAction, Action, getDefaultMiddleware } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice'; import counterReducer from '../features/counter/counterSlice';
import userReducer from './slices/userSlice';
import logger from 'redux-logger';
export const store = configureStore({ const middleware = [...getDefaultMiddleware(), logger]
reducer: {
const rootReducer = combineReducers({
counter: counterReducer, counter: counterReducer,
}, user: userReducer
})
export const store = configureStore({
reducer: rootReducer,
middleware
}); });
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof rootReducer>;
export type AppThunk<ReturnType = void> = ThunkAction< export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType, ReturnType,
RootState, RootState,
......
.home-container {
display: flex;
flex-direction: column;
align-items: center;
}
import React from 'react'; import React from 'react';
import './index.css'; import './index.css';
import Button from '@material-ui/core/Button';
import { Link } from "react-router-dom";
export default () => { export default () => {
return ( return (
<div> <div className='home-container'>
<h1>Home page111</h1> <h1>Home page111</h1>
<Button variant="contained" color="primary">
Hello World
</Button>
<Link to='/login'>login</Link>
</div> </div>
) )
} }
.login-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 100px 30px;
}
.login-container form {
padding: 30px 15px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.login-container form .input-label {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 0;
}
import React, { useEffect } from 'react';
import './index.css';
import { useField, Form, FormikProps, Formik } from "formik";
import * as yup from "yup";
import { useDispatch, useSelector } from 'react-redux';
import { userLogin, selectUser } from '../../app/slices/userSlice';
import Button from '@material-ui/core/Button';
import { Input } from '@material-ui/core';
import { InputLabel } from '@material-ui/core';
const MyTextField = ({ label, ...props }: any) => {
const [field, meta, helpers] = useField(props);
return (
<div>
<InputLabel className='input-label'>
{label}
<Input {...field} {...props} />
</InputLabel>
{meta.touched && meta.error ? (
<div className='error'>{meta.error}</div>
) : null}
</div>
);
};
interface Values {
username: string;
password: string;
}
export default () => {
const user = useSelector(selectUser);
console.log(user)
const dispatch = useDispatch();
console.log(user)
useEffect(() => {
console.log(user)
})
const handleLoginSubmit = async (values: Values) => {
console.log('on submit')
dispatch(userLogin(values))
}
return (
<div className='login-container'>
<Formik
initialValues={{
username: '',
password: ''
}}
onSubmit={handleLoginSubmit}
validationSchema={yup.object().shape({
username: yup.string()
.required("Required"),
password: yup.string()
.required("No password provided.")
})}
>
{(props: FormikProps<Values>) => {
console.log(props)
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset
} = props;
return (
<form onSubmit={handleSubmit}>
<MyTextField name="username" type="text" label="User Name" onChange={handleChange} value={values.username} />
<MyTextField name="password" type="password" label="Password" onChange={handleChange} value={values.password} />
<Button variant="contained" color="primary" type='submit'>Submit</Button>
</form>
)
}}
</Formik>
</div>
)
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment