Skip to content

Commit 75c6ef7

Browse files
authored
Feature/03 user management (#13)
* Initial routing and page design done. * User add done * Edit user implemented * Implemented add role to user
1 parent 6915930 commit 75c6ef7

File tree

16 files changed

+821
-18
lines changed

16 files changed

+821
-18
lines changed

client/src/App.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { checkPermission } from "./utils/permissionManager.js";
1010
import { ResourceCreate, ResourceList } from "./components/Resource";
1111
import { PermissionCreate, PermissionList, PermissionEdit } from "./components/Permission";
1212
import { RoleCreate, RoleList } from './components/Role';
13+
import { UserCreate, UserEdit, UserList } from './components/User';
1314

1415
export const PrivateRoute = ({ component: Component, name: resource, ...rest }) => {
1516

@@ -86,6 +87,9 @@ const App = () => {
8687
{ name: 'link-role-list', url: '/role-list', text: 'List role', component: RoleList, isRootMenu: true },
8788
{ name: 'link-resource-create', url: '/resource-create', text: 'Create resource', component: ResourceCreate, isRootMenu: true },
8889
{ name: 'link-resource-list', url: '/resource-list', text: 'List resource', component: ResourceList, isRootMenu: true },
90+
{ name: 'link-user-list', url: '/user-list', text: 'List user', component: UserList, isRootMenu: true },
91+
{ name: 'link-user-create', url: '/user-create', text: 'Create user', component: UserCreate, isRootMenu: true },
92+
{ name: 'link-user-edit', url: '/user-edit/:id', text: 'Edit user', component: UserEdit, isRootMenu: false },
8993
];
9094

9195
// let routes = [
@@ -119,7 +123,7 @@ const App = () => {
119123
</nav>
120124

121125
<div className="container">
122-
<Switch>
126+
<Switch>
123127
{
124128
links.map(route => {
125129
return <PrivateRoute key={route.name} path={route.url} component={route.component} name={route.name}></PrivateRoute>;

client/src/components/Login.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect} from 'react';
22
import { Redirect, useHistory } from "react-router-dom";
33
import { useForm } from 'react-hook-form';
44
import { useSelector, useDispatch } from 'react-redux';
@@ -13,7 +13,6 @@ export const Login = () => {
1313
dispatch({
1414
type: Constants.LOGIN_REQUEST, payload: data
1515
})
16-
history.push('/');
1716
}
1817

1918
const onSubmit = data => {
@@ -24,6 +23,12 @@ export const Login = () => {
2423
return state.userContext;
2524
});
2625

26+
useEffect(() => {
27+
console.log('userContext', userContext);
28+
if (userContext.isSuccess)
29+
history.push('/');
30+
}, [userContext.isSuccess]);
31+
2732
return (userContext.isAuthenticated) ? <Redirect to={{ pathname: '/' }} /> :
2833
<>
2934
<div className="col-md-6 col-md-offset-3">
@@ -42,6 +47,10 @@ export const Login = () => {
4247
<div className="form-group">
4348
<input type="submit" className="btn btn-primary" />
4449
</div>
50+
<div className="form-group">
51+
<label htmlFor="error"></label>
52+
<span className="col-form-label text-danger">{userContext.error}</span>
53+
</div>
4554
</form>
4655
</div>
4756
</>;

client/src/components/Register.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const Register = () => {
4040
return (shouldRedirect) ? <Redirect to={{ pathname: '/' }} /> :
4141
<>
4242
<div className="col-md-6 col-md-offset-3">
43-
<h2>Login</h2>
43+
<h2>Register</h2>
4444
{userContext.error &&
4545
<div className="alert alert-danger alert-dismissible fade show" role="alert">
4646
{userContext.error.message}

client/src/components/User.js

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import React from 'react';
2+
import { useHistory, useParams, Link } from "react-router-dom";
3+
import { Formik, Form, Field, ErrorMessage } from 'formik';
4+
import * as Yup from 'yup';
5+
import { useSelector, useDispatch } from 'react-redux';
6+
import { useEffect } from 'react';
7+
import { Table, Row, Col } from 'react-bootstrap';
8+
9+
const CreateUserSchema = Yup.object().shape({
10+
userName: Yup.string()
11+
.min(3, 'Too Short!')
12+
.max(64, 'Too Long!')
13+
.required('Hey input the value!'),
14+
password: Yup.string()
15+
.min(3, 'Too Short!')
16+
.max(64, 'Too Long!')
17+
.required('Hey input the value!')
18+
});
19+
20+
const EditUserSchema = Yup.object().shape({
21+
userName: Yup.string()
22+
.min(3, 'Too Short!')
23+
.max(64, 'Too Long!')
24+
.required('Hey input the value!'),
25+
});
26+
27+
export const UserCreate = () => {
28+
29+
let history = useHistory();
30+
let dispatch = useDispatch();
31+
32+
const usersContext = useSelector(state => state.usersContext);
33+
34+
useEffect(() => {
35+
console.log('usersContext', usersContext);
36+
if (usersContext.isSuccess)
37+
history.push('/user-list');
38+
}, [usersContext.isSuccess]);
39+
40+
return (
41+
<div>
42+
<h1>User entry</h1>
43+
<Formik
44+
enableReinitialize={true}
45+
setFieldValue={(field, value) => console.log(field, value)}
46+
initialValues={{ firstName: '', lastName: '', userName: '', email: '', phoneNumber: '', password: '', errors: usersContext.errors }}
47+
validationSchema={CreateUserSchema}
48+
onSubmit={(values, { setSubmitting }) => {
49+
console.log(values);
50+
dispatch({
51+
type: "ADD_USER", payload: values
52+
});
53+
setSubmitting(false);
54+
}}
55+
>
56+
{({ isSubmitting, values, errors }) => {
57+
console.log('errors', errors, values.errors);
58+
let keys = Object.keys(values.errors);
59+
let validationErrors = [];
60+
keys.map((key) => {
61+
let value = values.errors[key][0];
62+
validationErrors.push({ key, value });
63+
});
64+
return (
65+
<Form>
66+
<div className="form-group row">
67+
<label htmlFor="firstName" className="col-sm-2 col-form-label">First Name</label>
68+
<Field className="col-sm-8 col-form-label" type="text" name="firstName" />
69+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
70+
</div>
71+
<div className="form-group row">
72+
<label htmlFor="lastName" className="col-sm-2 col-form-label">Last Name</label>
73+
<Field className="col-sm-8 col-form-label" type="text" name="lastName" />
74+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
75+
</div>
76+
<div className="form-group row">
77+
<label htmlFor="userName" className="col-sm-2 col-form-label">User Name</label>
78+
<Field className="col-sm-8 col-form-label" type="text" name="userName" />
79+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="userName" component="div" />
80+
</div>
81+
<div className="form-group row">
82+
<label htmlFor="email" className="col-sm-2 col-form-label">Email</label>
83+
<Field className="col-sm-8 col-form-label" type="text" name="email" />
84+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
85+
</div>
86+
<div className="form-group row">
87+
<label htmlFor="phoneNumber" className="col-sm-2 col-form-label">Phone Number</label>
88+
<Field className="col-sm-8 col-form-label" type="text" name="phoneNumber" />
89+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="phoneNumber" component="div" />
90+
</div>
91+
<div className="form-group row">
92+
<label htmlFor="password" className="col-sm-2 col-form-label">Password</label>
93+
<Field className="col-sm-8 col-form-label" type="password" name="password" />
94+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="password" component="div" />
95+
</div>
96+
<div className="form-group row">
97+
<label htmlFor="errors" className="col-sm-2 col-form-label"></label>
98+
<ul>
99+
{
100+
validationErrors.map(error =>
101+
<li className="text-danger" key={error.key}>
102+
{error.value}
103+
</li>
104+
)
105+
}
106+
</ul>
107+
</div>
108+
<div className="form-group row">
109+
<label htmlFor="name" className="col-sm-2 col-form-label"></label>
110+
<button type="submit" disabled={isSubmitting}>Submit</button>
111+
</div>
112+
</Form>
113+
);
114+
}}
115+
</Formik>
116+
</div >
117+
);
118+
};
119+
120+
121+
export const UserEdit = () => {
122+
123+
let history = useHistory();
124+
let dispatch = useDispatch();
125+
126+
let { id } = useParams();
127+
128+
const roleContext = useSelector(state => {
129+
return state.roleContext;
130+
});
131+
132+
const usersContext = useSelector(state => state.usersContext);
133+
134+
useEffect(() => {
135+
console.log('usersContext', usersContext);
136+
dispatch({ type: "FETCH_ROLE" });
137+
dispatch({ type: "FETCH_USER_DETAIL", payload: id });
138+
139+
if (usersContext.isSuccess)
140+
history.push('/user-list');
141+
}, [usersContext.isSuccess]);
142+
143+
return (
144+
<div>
145+
<h1>User entry</h1>
146+
<Formik
147+
enableReinitialize={true}
148+
setFieldValue={(field, value) => console.log(field, value)}
149+
initialValues={{
150+
...usersContext.selectedUser,
151+
errors: usersContext.errors
152+
}}
153+
validationSchema={EditUserSchema}
154+
onSubmit={(values, { setSubmitting }) => {
155+
values.id = id;
156+
dispatch({
157+
type: "EDIT_USER", payload: values
158+
});
159+
setSubmitting(false);
160+
}}
161+
>
162+
{({ isSubmitting, values, errors }) => {
163+
let keys = Object.keys(values.errors);
164+
let validationErrors = [];
165+
keys.map((key) => {
166+
let value = values.errors[key][0];
167+
validationErrors.push({ key, value });
168+
});
169+
return (
170+
<Form>
171+
<div className="form-group row">
172+
<label htmlFor="firstName" className="col-sm-2 col-form-label">First name</label>
173+
<Field className="col-sm-8 col-form-label" type="text" name="firstName" />
174+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
175+
</div>
176+
<div className="form-group row">
177+
<label htmlFor="lastName" className="col-sm-2 col-form-label">Last name</label>
178+
<Field className="col-sm-8 col-form-label" type="text" name="lastName" />
179+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
180+
</div>
181+
<div className="form-group row">
182+
<label htmlFor="userName" className="col-sm-2 col-form-label">Username</label>
183+
<Field className="col-sm-8 col-form-label" type="text" name="userName" />
184+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="userName" component="div" />
185+
</div>
186+
<div className="form-group row">
187+
<label htmlFor="email" className="col-sm-2 col-form-label">Email</label>
188+
<Field className="col-sm-8 col-form-label" type="text" name="email" />
189+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
190+
</div>
191+
<div className="form-group row">
192+
<label htmlFor="phoneNumber" className="col-sm-2 col-form-label">Phone number</label>
193+
<Field className="col-sm-8 col-form-label" type="text" name="phoneNumber" />
194+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="phoneNumber" component="div" />
195+
</div>
196+
<div className="form-group row">
197+
<label htmlFor="roleId" className="col-sm-2 col-form-label">Roles</label>
198+
<Field as="select" name="roleId" className="col-sm-8">
199+
<option value="" key=""></option>
200+
{
201+
roleContext.roleList.map(({ id, name }) => {
202+
return <option value={id} key={id}>{name}</option>;
203+
})
204+
}
205+
</Field>
206+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="name" component="div" />
207+
</div>
208+
<div className="form-group row">
209+
<label htmlFor="password" className="col-sm-2 col-form-label">Current password</label>
210+
<Field className="col-sm-8 col-form-label" type="password" name="password" placeholder="Leave empty if you don't want to change password" />
211+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="password" component="div" />
212+
</div>
213+
<div className="form-group row">
214+
<label htmlFor="newPassword" className="col-sm-2 col-form-label">New password</label>
215+
<Field className="col-sm-8 col-form-label" type="password" name="newPassword" placeholder="Leave empty if you don't want to change password" />
216+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="newPassword" component="div" />
217+
</div>
218+
<div className="form-group row">
219+
<label htmlFor="isActive" className="col-sm-2 col-form-label">Is active</label>
220+
<label><Field type="checkbox" name="isActive" /></label>
221+
<ErrorMessage className="col-sm-2 col-form-label text-danger" name="isActive" component="div" />
222+
</div>
223+
<div className="form-group row">
224+
<label htmlFor="errors" className="col-sm-2 col-form-label"></label>
225+
<ul>
226+
{
227+
validationErrors.map(error =>
228+
<li className="text-danger" key={error.key}>
229+
{error.value}
230+
</li>
231+
)
232+
}
233+
</ul>
234+
</div>
235+
<div className="form-group row">
236+
<label htmlFor="name" className="col-sm-2 col-form-label"></label>
237+
<button type="submit" disabled={isSubmitting}>Submit</button>
238+
</div>
239+
</Form>
240+
);
241+
}}
242+
</Formik>
243+
</div >
244+
);
245+
};
246+
247+
248+
export const UserList = () => {
249+
250+
let dispatch = useDispatch();
251+
252+
const usersContext = useSelector(state => {
253+
return state.usersContext;
254+
});
255+
256+
useEffect(() => {
257+
if (usersContext.userList.length === 0 || usersContext.shouldReload) {
258+
dispatch({ type: "FETCH_USER" });
259+
}
260+
}, [usersContext.shouldReload]);
261+
262+
return (
263+
<>
264+
<Row>
265+
<h2>User List</h2>
266+
</Row>
267+
<Row>
268+
<Col>
269+
<Table striped bordered hover>
270+
<thead>
271+
<tr>
272+
<th>#</th>
273+
<th>Username</th>
274+
<th>Phone Number</th>
275+
<th>Name</th>
276+
<th>Is active</th>
277+
<th></th>
278+
</tr>
279+
</thead>
280+
<tbody>
281+
{
282+
usersContext.userList.map((user, index) => {
283+
return (
284+
<tr key={user.id}>
285+
<td>{index + 1}</td>
286+
<td>{user.userName}</td>
287+
<td>{user.phoneNumber}</td>
288+
<td>{user.firstName} {user.lastName}</td>
289+
<td>{user.isActive.toString()}</td>
290+
<td><Link to={`/user-edit/${user.id}`} className="">Edit</Link></td>
291+
</tr>
292+
)
293+
})
294+
}
295+
</tbody>
296+
</Table>
297+
</Col>
298+
</Row>
299+
</>
300+
);
301+
}

client/src/reducers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import UserReducer from "./userReducer";
44
import ResourceReducer from "./resourceReducer";
55
import RoleReducer from "./roleReducer";
66
import PermissionReducer from "./permissionReducer";
7+
import UsersReducer from "./usersReducer";
78

89
const rootReducer = combineReducers({
910
posts: PostsReducer,
1011
userContext: UserReducer,
12+
usersContext: UsersReducer,
1113
resourceContext: ResourceReducer,
1214
roleContext: RoleReducer,
1315
permissionContext: PermissionReducer

0 commit comments

Comments
 (0)