Add: register page, account info page

This commit is contained in:
AlexGames73 2019-01-11 23:08:13 +04:00
parent cf0e05c918
commit 2728a0ca01
12 changed files with 410 additions and 81 deletions

View File

@ -28,7 +28,14 @@ public class UserSessionLoginFailureHandler implements AuthenticationFailureHand
throws IOException, ServletException { throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
HashMap<String, Object> data = new HashMap<>();
data.put("data", null);
HashMap<String, Object> error = new HashMap<>();
error.put("code", HttpStatus.UNAUTHORIZED.value());
error.put("message", "<b>Не удается войти.</b><br>Пожалуйста, проверьте правильность написания <b>логина</b> и <b>пароля</b>.");
data.put("error", error);
response.getOutputStream() response.getOutputStream()
.write("<b>Не удается войти.</b><br>Пожалуйста, проверьте правильность написания <b>логина</b> и <b>пароля</b>.".getBytes(StandardCharsets.UTF_8)); .write(objectMapper.writeValueAsString(data).getBytes(StandardCharsets.UTF_8));
} }
} }

View File

@ -107,6 +107,13 @@ public class UserController extends OdinController<UserListDto, UserDto> {
return new Response<>(userService.getUserWithRolesById(userId)); return new Response<>(userService.getUserWithRolesById(userId));
} }
@GetMapping("/account")
@Secured({UserRoleConstants.ADMIN, UserRoleConstants.USER})
public Response<UserDto> getUserBySession(@RequestParam("session_id") String sessionId){
log.debug("REST: UserController.getUserBySession( {} )", sessionId);
return new Response<>(userSessionService.getUserDtoBySessionId(sessionId));
}
@PostMapping("") @PostMapping("")
@Secured(UserRoleConstants.ADMIN) @Secured(UserRoleConstants.ADMIN)

View File

@ -0,0 +1,28 @@
package ru.ulstu.user.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import ru.ulstu.user.service.UserService;
import ru.ulstu.user.service.UserSessionService;
@Controller
public class UserMVCController {
private final Logger log = LoggerFactory.getLogger(UserMVCController.class);
private final UserService userService;
private final UserSessionService userSessionService;
public UserMVCController(UserService userService, UserSessionService userSessionService) {
this.userService = userService;
this.userSessionService = userSessionService;
}
@GetMapping("/acc-info")
public String getAcc(){
return "acc-info";
}
}

View File

@ -10,6 +10,7 @@ import ru.ulstu.core.jpa.OffsetablePageRequest;
import ru.ulstu.core.model.response.PageableItems; import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.user.error.UserNotFoundException; import ru.ulstu.user.error.UserNotFoundException;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import ru.ulstu.user.model.UserDto;
import ru.ulstu.user.model.UserSession; import ru.ulstu.user.model.UserSession;
import ru.ulstu.user.model.UserSessionListDto; import ru.ulstu.user.model.UserSessionListDto;
import ru.ulstu.user.repository.UserSessionRepository; import ru.ulstu.user.repository.UserSessionRepository;
@ -22,10 +23,12 @@ public class UserSessionService {
private final Logger log = LoggerFactory.getLogger(UserSessionService.class); private final Logger log = LoggerFactory.getLogger(UserSessionService.class);
private final UserSessionRepository userSessionRepository; private final UserSessionRepository userSessionRepository;
private final UserService userService; private final UserService userService;
private final UserMapper userMapper;
public UserSessionService(UserSessionRepository userSessionRepository, UserService userService) { public UserSessionService(UserSessionRepository userSessionRepository, UserService userService, UserMapper userMapper) {
this.userSessionRepository = userSessionRepository; this.userSessionRepository = userSessionRepository;
this.userService = userService; this.userService = userService;
this.userMapper = userMapper;
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
@ -54,4 +57,9 @@ public class UserSessionService {
userSessionRepository.save(userSession); userSessionRepository.save(userSession);
log.debug("User session {} closed", sessionId); log.debug("User session {} closed", sessionId);
} }
public UserDto getUserDtoBySessionId(String sessionId){
final UserSession userSession = userSessionRepository.findOneBySessionId(sessionId);
return userMapper.userEntityToUserDto(userSession.getUser());
}
} }

View File

@ -56,7 +56,7 @@ function errorHandler(response, callBack, errorCallBack) {
// TODO: add l10n // TODO: add l10n
// showFeedbackMessage(response.error.code + ": " + response.error.message, MessageTypesEnum.DANGER); // showFeedbackMessage(response.error.code + ": " + response.error.message, MessageTypesEnum.DANGER);
if (!isEmpty(errorCallBack)) { if (!isEmpty(errorCallBack)) {
errorCallBack(response.data); errorCallBack(response.error);
} }
throw response.error.code + ": " + response.error.message + throw response.error.code + ": " + response.error.message +
" / Details: " + response.error.data; " / Details: " + response.error.data;
@ -121,7 +121,10 @@ function postToRest(url, postData, callBack, completeCallback, errorCallBack) {
return; return;
} }
completeCallback(); completeCallback();
} },
}).fail(function (response) {
errorHandler(response.responseJSON, callBack, errorCallBack);
}); });
} }

View File

@ -0,0 +1,55 @@
const {
LinearProgress,
CircularProgress,
Card,
CardHeader,
CardContent,
FormControl,
FormLabel,
TextField,
Button
} = window['material-ui'];
const { Component } = window['React'];
const React = window['React'];
class Account extends Component{
constructor(){
super();
const session_id = document.cookie.toLowerCase().match(/jsessionid=(.*?).node0/)[1];
$.getJSON("/api/1.0/users/account?session_id=" + session_id,
null, this.responseUserDto);
this.state = {
userDto: null
};
}
responseUserDto = response => {
this.setState({
userDto: response.data
});
};
render(){
return (
this.state.userDto != null ? (
<div style={{width: '75%', margin: 'auto'}}>
<Card>
<CardHeader title={this.state.userDto.firstName + " " + this.state.userDto.lastName}/>
<CardContent>
<p>Login: {this.state.userDto.login}</p>
<p>Email: {this.state.userDto.email}</p>
</CardContent>
</Card>
</div>
) : (
<div className="text-center">
<CircularProgress />
</div>
)
)
}
}
ReactDOM.render(<Account/>, document.getElementById('react-account'));

View File

@ -7,51 +7,37 @@ const {
TextField, TextField,
Button Button
} = window['material-ui']; } = window['material-ui'];
const React = window['React'];
const {Component} = React;
class LoginForm extends React.Component{ class LoginForm extends Component{
state = { state = {
isValidUsername: false,
isValidPassword: false,
username: "", username: "",
password: "", password: "",
errorsMessage: "" errorsMessage: ""
}; };
handleChangeUsername = event => { handleChange = field => event => {
this.setState({ this.setState({
username: event.target.value, [field]: event.target.value,
isValidUsername: /^[_'.@A-Za-z0-9-]*$/.test(event.target.value) ["isValid_"+field]: true
});
};
handleChangePassword = event => {
this.setState({
password: event.target.value,
isValidPassword: (event.target.value.length > 4)
}); });
}; };
handleSubmit = loginForm => () => { handleSubmit = loginForm => () => {
if (loginForm.state.isValidUsername && loginForm.state.isValidPassword){ $.post("/login", $('form.login-form').serialize(),
$.post( this.handleResponseAjaxPost(loginForm)("success"))
"/login", .fail(this.handleResponseAjaxPost(loginForm)("error"));
loginForm.state,
this.handleResponseAjaxPost(loginForm)("success")
).fail(this.handleResponseAjaxPost(loginForm)("error"));
}
}; };
handleResponseAjaxPost = loginForm => status => response => { handleResponseAjaxPost = loginForm => status => response => {
if (status === "success"){ if (status === "success"){
window.location.href = "/index"; window.location.href = "/index";
loginForm.setState({
errorsMessage: ""
});
} else if (status === "error"){ } else if (status === "error"){
let responseJSON = JSON.parse(response.responseText);
loginForm.setState({ loginForm.setState({
errorsMessage: response.responseText, errorsMessage: responseJSON.error.message,
password: "", password: "",
isValidPassword: false
}) })
} }
}; };
@ -61,7 +47,7 @@ class LoginForm extends React.Component{
<Card onKeyPress={e => (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0"> <Card onKeyPress={e => (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0">
<CardHeader title="Вход"/> <CardHeader title="Вход"/>
<CardContent> <CardContent>
<form> <form className="login-form">
<FormControl component="fieldset" style={{width: '100%'}}> <FormControl component="fieldset" style={{width: '100%'}}>
<FormLabel error={true}> <FormLabel error={true}>
<div <div
@ -72,24 +58,23 @@ class LoginForm extends React.Component{
<TextField <TextField
label="Логин" label="Логин"
value={this.state.username} value={this.state.username}
onChange={this.handleChangeUsername} onChange={this.handleChange("username")}
margin="normal" margin="normal"
error={!this.state.isValidUsername} type="text"
helperText={!this.state.isValidUsername ? "Логин не соответствует требованиям ^[_'.@A-Za-z0-9-]*$" : ""} InputProps={{name: "username"}}
/> />
<TextField <TextField
label="Пароль" label="Пароль"
value={this.state.password} value={this.state.password}
onChange={this.handleChangePassword} onChange={this.handleChange("password")}
margin="normal" margin="normal"
error={!this.state.isValidPassword} type="password"
helperText={!this.state.isValidPassword ? "Пароль должен содержать 5 символов" : ""} InputProps={{name: "password"}}
/> />
<Button <Button
variant="contained" variant="contained"
onClick={this.handleSubmit(this)} onClick={this.handleSubmit(this)}
style={{margin: 'auto'}} style={{margin: 'auto'}}
disabled={!this.state.isValidUsername || !this.state.isValidPassword}
>Войти</Button> >Войти</Button>
</FormControl> </FormControl>
</form> </form>

View File

@ -5,58 +5,290 @@ const {
FormControl, FormControl,
FormLabel, FormLabel,
TextField, TextField,
Button Button,
InputAdornment,
IconButton,
Icon,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions
} = window['material-ui']; } = window['material-ui'];
const { Component } = window['React'];
const React = window['React'];
class RegisterForm extends Component{
validate(field, state) {
switch (field) {
case "login":
return (
(state[field].length >= 4) &&
/^[_'.@A-Za-z0-9-]*$/.test(state[field])
);
case "password":
return (
(state[field].length >= 6)
);
case "passwordConfirm":
return (
(state[field].length >= 6) &&
(state[field] === state["password"])
);
case "email":
return (
(/.+@.+\..+/i.test(state[field]))
);
case "firstName":
return (
(state[field].length >= 2)
);
case "lastName":
return (
(state[field].length >= 2)
);
}
}
fields = ["login", "password", "passwordConfirm", "email", "firstName", "lastName"];
labels = {
login: "Логин",
password: "Пароль",
passwordConfirm: "Подтверждение пароля",
email: "Email",
firstName: "Имя",
lastName: "Фамилия",
};
typeInputs = {
login: "text",
password: "password",
passwordConfirm: "password",
email: "email",
firstName: "text",
lastName: "text",
};
dialogContents = {
login: (
<DialogContentText>
Длина логина должна быть <b>не менее 4 символов</b> и <b>не превышать 50 символов</b>.<br/>
Логин может состоять из:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;1. Строчных или заглавных букв латинского алфавита<br/>
&nbsp;&nbsp;&nbsp;&nbsp;2. Цифр<br/>
&nbsp;&nbsp;&nbsp;&nbsp;3. знаков препинания <b>_'.@-</b><br/>
</DialogContentText>
),
password: (
<DialogContentText>
Длина пароля должна быть <b>не менее 6 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
passwordConfirm: (
<DialogContentText>
Пароли должны совпадать и соответствовать требованиям пароля
</DialogContentText>
),
email: (
<DialogContentText>
Почта должна соответвовать примеру: <b>example@domain.com</b>
</DialogContentText>
),
firstName: (
<DialogContentText>
Длина имени должна быть <b>не менее 2 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
lastName: (
<DialogContentText>
Длина Фамилии должна быть <b>не менее 2 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
};
class RegisterForm extends React.Component{
state = { state = {
isValidUsername: false, isValid_login: true,
isValidPassword: false, isValid_password: true,
username: "", isValid_passwordConfirm: true,
isValid_email: true,
isValid_firstName: true,
isValid_lastName: true,
login: "",
password: "", password: "",
errorsMessage: "" passwordConfirm: "",
email: "",
firstName: "",
lastName: "",
error_login: "",
error_password: "",
error_passwordConfirm: "",
error_email: "",
error_firstName: "",
error_lastName: "",
isComplete: false,
errorsMessage: "",
infoAbout: "",
infoIsOpen: false,
}; };
handleChangeUsername = event => { handleChange = field => event => {
this.setState({ this.setState({
username: event.target.value, [field]: event.target.value,
isValidUsername: (event.target.value.length > 4) ["isValid_"+field]: true
}); });
}; };
handleChangePassword = event => { isValid = state => {
this.setState({ let result = true;
password: event.target.value, this.fields.forEach(field => {
isValidPassword: (event.target.value.length > 4) result = result && state["isValid_"+field];
}); });
return result;
}; };
handleSubmit = loginForm => () => { registerFormToJson = function () {
if (loginForm.state.isValidUsername && loginForm.state.isValidPassword){ let formData = {};
$.post( $(".register-form").find(":input").not(":button").each(function () {
"/api/1.0/users/register", if ($(this).val() == null || $(this).val() === "") {
loginForm.state, return true;
this.handleResponseAjaxPost(loginForm)("success") }
).fail(this.handleResponseAjaxPost(loginForm)("error")); formData[$(this).attr("name")] = $(this).val();
});
return JSON.stringify(formData);
};
handleSubmit = registerForm => () => {
let valids = {};
this.fields.forEach(field => {
valids["isValid_"+field]=this.validate(field, registerForm.state)
});
this.setState(valids);
if (this.isValid(valids)){
postToRest("/api/1.0/users/register", this.registerFormToJson(), null,
this.handleResponseAjaxPost(registerForm)("success"),
this.handleResponseAjaxPost(registerForm)("error"));
} }
}; };
handleResponseAjaxPost = loginForm => status => response => { handleResponseAjaxPost = registerForm => status => response => {
if (status === "success"){ if (status === "success"){
window.location.href = "/index"; registerForm.setState({
loginForm.setState({ isComplete: true
errorsMessage: ""
}); });
} else if (status === "error"){ } else if (status === "error"){
loginForm.setState({ switch (response.code) {
errorsMessage: response.responseText, case 103:
password: "", registerForm.setState({
isValidPassword: false errorsMessage: response.message,
}) error_login: "Пользователь с таким логином уже существует",
isValid_login: false
});
break;
case 102:
registerForm.setState({
errorsMessage: response.message,
error_email: "Пользователь с таким адресом уже существует",
isValid_email: false
});
break;
default:
registerForm.setState({
errorsMessage: response.message
});
break;
}
} }
}; };
handleClickInfo = field => () => {
this.setState({
infoAbout: field,
infoIsOpen: true,
});
};
handleCloseInfo = () => {
this.setState({
infoIsOpen: false
})
};
getForm = () => {
return (
<form className="register-form">
<FormControl component="fieldset" style={{width: '100%'}}>
<FormLabel error={true}>
<div
className="Container"
dangerouslySetInnerHTML={{__html: this.state.errorsMessage}}>
</div>
</FormLabel>
{this.fields.map(field => (
<TextField
label={this.labels[field]}
value={this.state[field]}
onChange={this.handleChange(field)}
margin="normal"
error={!this.state["isValid_"+field]}
helperText={!this.state["isValid_"+field] ? this.state["error_"+field] : ""}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Правила"
onClick={this.handleClickInfo(field)}
>
<Icon>info</Icon>
</IconButton>
</InputAdornment>
),
name: field
}}
type={this.typeInputs[field]}
/>
))}
<Dialog
open={this.state.infoIsOpen}
onClose={this.handleCloseInfo}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">{this.labels[this.state.infoAbout]}</DialogTitle>
<DialogContent>
{this.dialogContents[this.state.infoAbout]}
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseInfo} color="primary">
Закрыть
</Button>
</DialogActions>
</Dialog>
<Button
variant="contained"
onClick={this.handleSubmit(this)}
style={{margin: 'auto'}}
disabled={!this.isValid(this.state)}
>Зарегистрироваться</Button>
</FormControl>
</form>
);
};
getCompleteContent = () => {
return (
<div className="text-center">
<h5>Вам на почту <b>{this.state.email}</b> придет письмо с активацией аккаунта в течении нескольких минут.</h5>
</div>
);
};
render(){ render(){
return <div> </div>; return (
<Card onKeyPress={e => (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0">
<CardHeader title={this.state.isComplete ? "Вы успешно зарегистрировались" : "Регистрация"}/>
<CardContent>
{this.state.isComplete ?
this.getCompleteContent() :
this.getForm()}
</CardContent>
</Card>
);
} }
} }

View File

@ -1,6 +1,9 @@
let {Button, Icon} = window['material-ui']; const {Button, Icon} = window['material-ui'];
const {render} = window['ReactDOM'];
const React = window['React'];
const {Component} = React;
class LoginPage extends React.Component{ class LoginPage extends Component{
state = { state = {
isLogin: true, isLogin: true,
isRegister: false isRegister: false
@ -18,7 +21,7 @@ class LoginPage extends React.Component{
isLogin: false, isLogin: false,
isRegister: true isRegister: true
}); });
} };
render() { render() {
this.form = null; this.form = null;
@ -46,4 +49,4 @@ class LoginPage extends React.Component{
} }
} }
ReactDOM.render(<LoginPage/>, document.getElementById("loginForm")); render(<LoginPage/>, document.getElementById("loginForm"));

View File

@ -5,10 +5,14 @@
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
layout:decorator="default"> layout:decorator="default">
<div class="container" layout:fragment="content" style="margin-top: 70px"> <div class="container" layout:fragment="content">
Login: <p th:text="${userDto.getLogin()}"></p> <section id="react-account">
Имя: <p th:text="${userDto.getFirstName()}"></p>
Фамилия: <p th:text="${userDto.getLastName()}"></p> </section>
</div>
<div layout:fragment="react-scripts">
<script type="text/babel" src="/js/react/accountPage/index.js"></script>
</div> </div>
</html> </html>

View File

@ -12,11 +12,6 @@
class="fa fa-plane fa-4" aria-hidden="true"></i> Balance</span></a> class="fa fa-plane fa-4" aria-hidden="true"></i> Balance</span></a>
</div> </div>
</nav> </nav>
<div class="container" layout:fragment="content">
<div class="margined-top-10">
<a href="/login" class="btn btn-success btn-block" role="button">Вернуться к странице входа</a>
</div>
</div>
<th:block layout:fragment="data-scripts"> <th:block layout:fragment="data-scripts">
<script type="text/javascript"> <script type="text/javascript">
/*<![CDATA[*/ /*<![CDATA[*/
@ -26,8 +21,9 @@
showFeedbackMessage("Ключ активации не задан", MessageTypesEnum.DANGER); showFeedbackMessage("Ключ активации не задан", MessageTypesEnum.DANGER);
return; return;
} }
postToRest(urlUsersActivate + "?key=" + key, null, postToRest("/api/1.0/users/activate?key=" + key, null,
function () { function () {
window.location.href = "/";
showFeedbackMessage("Пользователь успешно активирован"); showFeedbackMessage("Пользователь успешно активирован");
} }
); );

View File

@ -96,6 +96,7 @@
</footer> </footer>
<script src="/js/core.js"></script> <script src="/js/core.js"></script>
<div layout:fragment="data-scripts"></div>
<!-- React --> <!-- React -->
<script src="/webjars/react/16.7.0/umd/react.production.min.js"></script> <script src="/webjars/react/16.7.0/umd/react.production.min.js"></script>