Implement password change for employee profile
This commit is contained in:
@@ -56,9 +56,9 @@ class EmployeesActionManager extends SubActionManager
|
|||||||
if (!empty($childCompaniesIds)) {
|
if (!empty($childCompaniesIds)) {
|
||||||
$childStructureSubordinates
|
$childStructureSubordinates
|
||||||
= $obj->Find(
|
= $obj->Find(
|
||||||
"department in (" . implode(',', $childCompaniesIds) . ") and id != ?",
|
"department in (" . implode(',', $childCompaniesIds) . ") and id != ?",
|
||||||
array($cemp)
|
array($cemp)
|
||||||
);
|
);
|
||||||
$subordinates = array_merge($subordinates, $childStructureSubordinates);
|
$subordinates = array_merge($subordinates, $childStructureSubordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +147,10 @@ class EmployeesActionManager extends SubActionManager
|
|||||||
return new IceResponse(IceResponse::ERROR, "Error occurred while changing password");
|
return new IceResponse(IceResponse::ERROR, "Error occurred while changing password");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!PasswordManager::verifyPassword($req->current, $user->password)) {
|
||||||
|
return new IceResponse(IceResponse::ERROR, "Current password is incorrect");
|
||||||
|
}
|
||||||
|
|
||||||
$passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd);
|
$passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd);
|
||||||
if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) {
|
if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) {
|
||||||
return $passwordStrengthResponse;
|
return $passwordStrengthResponse;
|
||||||
@@ -158,6 +162,6 @@ class EmployeesActionManager extends SubActionManager
|
|||||||
return new IceResponse(IceResponse::ERROR, $user->ErrorMsg());
|
return new IceResponse(IceResponse::ERROR, $user->ErrorMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IceResponse(IceResponse::SUCCESS, $user);
|
return new IceResponse(IceResponse::SUCCESS, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
web/api/CustomAction.js
Normal file
26
web/api/CustomAction.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
class CustomAction {
|
||||||
|
constructor(adapter) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(subAction, module, request, isPost) {
|
||||||
|
if (!isPost) {
|
||||||
|
return axios.get(
|
||||||
|
this.adapter.moduleRelativeURL,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.post(this.moduleRelativeURL, {
|
||||||
|
t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomAction;
|
||||||
232
web/components/UpdatePasswordModal.js
Normal file
232
web/components/UpdatePasswordModal.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Modal,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
|
import CustomAction from '../api/CustomAction';
|
||||||
|
|
||||||
|
class UpdatePasswordModal extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
passwordHasError: false,
|
||||||
|
passwordState: { hasFeedback: false, validateStatus:'', help:'Password must include at least one number, one lowercase letter, one uppercase letter and a symbol' },
|
||||||
|
confirmationHasError: false,
|
||||||
|
confirmationState: { hasFeedback: false, validateStatus:'', help:'' },
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.formRef = React.createRef();
|
||||||
|
this.customAction = new CustomAction(this.props.adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
message.config({
|
||||||
|
top: 40,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConfirmFeedback = () => {
|
||||||
|
this.setState({confirmationHasError: false});
|
||||||
|
this.setState({
|
||||||
|
confirmationState: {
|
||||||
|
hasFeedback : false,
|
||||||
|
validateStatus:'',
|
||||||
|
help:'',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePasswordState(value) {
|
||||||
|
const passwordValidationResult = this.validatePassword(value);
|
||||||
|
if (passwordValidationResult !== null) {
|
||||||
|
this.setState({passwordHasError: true});
|
||||||
|
this.setState({
|
||||||
|
passwordState: {
|
||||||
|
hasFeedback : true,
|
||||||
|
validateStatus:'error',
|
||||||
|
help:passwordValidationResult,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.setState({passwordHasError: false});
|
||||||
|
this.setState({
|
||||||
|
passwordState: {
|
||||||
|
hasFeedback : true,
|
||||||
|
validateStatus:'success',
|
||||||
|
help:'',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfirmPasswordState(values) {
|
||||||
|
if (values.confirm !== values.new) {
|
||||||
|
this.setState({confirmationHasError: true});
|
||||||
|
this.setState({
|
||||||
|
confirmationState: {
|
||||||
|
hasFeedback : true,
|
||||||
|
validateStatus:'error',
|
||||||
|
help:'Passwords don\'t match',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.setState({confirmationHasError: false});
|
||||||
|
this.setState({
|
||||||
|
confirmationState: {
|
||||||
|
hasFeedback : false,
|
||||||
|
validateStatus:'',
|
||||||
|
help:'',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOk = () => {
|
||||||
|
const from = this.formRef.current;
|
||||||
|
from
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
if (this.updatePasswordState(values.new) && this.updateConfirmPasswordState(values)) {
|
||||||
|
this.updatePassword(values.current, values.new)
|
||||||
|
.then((response) => {
|
||||||
|
const data = response.data;
|
||||||
|
console.log(data);
|
||||||
|
if (data.status === 'SUCCESS') {
|
||||||
|
this.handleCancel();
|
||||||
|
message.success(this.props.adapter.gt('Password updated'));
|
||||||
|
} else {
|
||||||
|
message.error(
|
||||||
|
`${this.props.adapter.gt('Error updating password')}: ${this.props.adapter.gt(data.data)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
message.error(
|
||||||
|
`${this.props.adapter.gt('Error updating password')}`
|
||||||
|
);
|
||||||
|
console.log(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((info) => {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
if (this.formRef.current) {
|
||||||
|
this.formRef.current.resetFields();
|
||||||
|
}
|
||||||
|
this.props.closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePassword = (oldPassword, newPassword) => {
|
||||||
|
const req = { current: oldPassword ? oldPassword : '', pwd: newPassword };
|
||||||
|
const reqJson = JSON.stringify(req);
|
||||||
|
|
||||||
|
const callBackData = [];
|
||||||
|
callBackData.callBackData = [];
|
||||||
|
callBackData.callBackSuccess = 'changePasswordSuccessCallBack';
|
||||||
|
callBackData.callBackFail = 'changePasswordFailCallBack';
|
||||||
|
|
||||||
|
return this.customAction.execute('changePassword', 'modules=employees', reqJson);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
validatePassword = (password) => {
|
||||||
|
if (password.length < 8) {
|
||||||
|
return this.props.adapter.gt('Password too short');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length > 20) {
|
||||||
|
return this.props.adapter.gt('Password too long');
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberTester = /.*[0-9]+.*$/;
|
||||||
|
if (!password.match(numberTester)) {
|
||||||
|
return this.props.adapter.gt('Password must include at least one number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerTester = /.*[a-z]+.*$/;
|
||||||
|
if (!password.match(lowerTester)) {
|
||||||
|
return this.props.adapter.gt('Password must include at least one lowercase letter');
|
||||||
|
}
|
||||||
|
|
||||||
|
const upperTester = /.*[A-Z]+.*$/;
|
||||||
|
if (!password.match(upperTester)) {
|
||||||
|
return this.props.adapter.gt('Password must include at least one uppercase letter');
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbolTester = /.*[\W]+.*$/;
|
||||||
|
if (!password.match(symbolTester)) {
|
||||||
|
return this.props.adapter.gt('Password must include at least one symbol');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const layout = {
|
||||||
|
labelCol: { span: 8 },
|
||||||
|
wrapperCol: { span: 16 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={this.props.visible}
|
||||||
|
title="Update Password"
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
footer={[
|
||||||
|
<Button key="back" onClick={this.handleCancel}>
|
||||||
|
{this.props.adapter.gt('Cancel')}
|
||||||
|
</Button>,
|
||||||
|
<Button key="submit" type="primary" loading={this.state.loading} onClick={this.handleOk}>
|
||||||
|
{this.props.adapter.gt('Update')}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form {...layout} ref={this.formRef}>
|
||||||
|
<Form.Item label="Current Password" key="current" name="current" >
|
||||||
|
<Input.Password placeholder="current password"/>
|
||||||
|
</Form.Item>
|
||||||
|
{ this.state.passwordHasError &&
|
||||||
|
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
|
||||||
|
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{ !this.state.passwordHasError &&
|
||||||
|
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
|
||||||
|
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{ this.state.confirmationHasError &&
|
||||||
|
<Form.Item label="Confirm Password" key="confirm" name="confirm" {...this.state.confirmationState}>
|
||||||
|
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{ !this.state.confirmationHasError &&
|
||||||
|
<Form.Item label="Confirm Password" key="confirm" name="confirm" >
|
||||||
|
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdatePasswordModal;
|
||||||
@@ -1,37 +1,32 @@
|
|||||||
import React, {Component} from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Col,
|
Col,
|
||||||
Card,
|
Card,
|
||||||
Badge,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
Input,
|
|
||||||
Row,
|
Row,
|
||||||
Descriptions,
|
Descriptions,
|
||||||
Typography,
|
Typography,
|
||||||
Table,
|
|
||||||
Space,
|
Space,
|
||||||
Button,
|
|
||||||
Tag,
|
Tag,
|
||||||
message,
|
|
||||||
Tabs,
|
Tabs,
|
||||||
Spin,
|
|
||||||
Skeleton
|
Skeleton
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
FilterOutlined,
|
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
PhoneTwoTone,
|
PhoneTwoTone,
|
||||||
MailTwoTone,
|
MailTwoTone,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
|
LockOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import TagList from "../../../../components/TagList";
|
import TagList from "../../../../components/TagList";
|
||||||
const { Search } = Input;
|
import UpdatePasswordModal from "../../../../components/UpdatePasswordModal";
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
class EmployeeProfile extends React.Component {
|
class EmployeeProfile extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
|
showPasswordResetModal: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -42,6 +37,10 @@ class EmployeeProfile extends React.Component {
|
|||||||
this.setState({ loading: value });
|
this.setState({ loading: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setShowPasswordUpdate(value) {
|
||||||
|
this.setState({ showPasswordResetModal: value });
|
||||||
|
}
|
||||||
|
|
||||||
updateProfileImage() {
|
updateProfileImage() {
|
||||||
showUploadDialog(
|
showUploadDialog(
|
||||||
`profile_image_${this.props.element.id}_${(new Date()).getTime()}`,
|
`profile_image_${this.props.element.id}_${(new Date()).getTime()}`,
|
||||||
@@ -71,6 +70,33 @@ class EmployeeProfile extends React.Component {
|
|||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEditButtonJsxWithPassword() {
|
||||||
|
return (<>
|
||||||
|
{this.state.loading &&
|
||||||
|
<Tag icon={<SyncOutlined spin/>} color="processing">
|
||||||
|
{this.props.adapter.gt('Edit')}
|
||||||
|
</Tag>
|
||||||
|
}
|
||||||
|
{!this.state.loading &&
|
||||||
|
<Tag icon={<EditOutlined/>} color="processing"
|
||||||
|
onClick={() => modJs.edit(this.props.element.id)}>
|
||||||
|
{this.props.adapter.gt('Edit')}
|
||||||
|
</Tag>
|
||||||
|
}
|
||||||
|
<Tag icon={<LockOutlined/>} color="volcano" onClick={() => this.setShowPasswordUpdate(true)}>
|
||||||
|
{this.props.adapter.gt('Update Password')}
|
||||||
|
</Tag>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdatePasswordButtonJsx() {
|
||||||
|
return (<>
|
||||||
|
<Tag icon={<SyncOutlined spin/>} color="processing">
|
||||||
|
{this.props.adapter.gt('Update Password')}
|
||||||
|
</Tag>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
getTabViewEmployeeFilterButtonJsx(tab) {
|
getTabViewEmployeeFilterButtonJsx(tab) {
|
||||||
return (
|
return (
|
||||||
<Tag icon={<EditOutlined/>} color="processing"
|
<Tag icon={<EditOutlined/>} color="processing"
|
||||||
@@ -86,10 +112,15 @@ class EmployeeProfile extends React.Component {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<UpdatePasswordModal
|
||||||
|
visible={this.state.showPasswordResetModal}
|
||||||
|
closeModal={() => {this.setState({ showPasswordResetModal: false })}}
|
||||||
|
adapter={this.props.adapter}
|
||||||
|
/>
|
||||||
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
|
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card title={this.props.adapter.gt('Employee Profile')}
|
<Card title={this.props.adapter.gt('Employee Profile')}
|
||||||
extra={this.getEditButtonJsx()}
|
extra={this.getEditButtonJsxWithPassword()}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<Space size={'large'}>
|
<Space size={'large'}>
|
||||||
|
|||||||
Reference in New Issue
Block a user