Commit ec05fa46 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '3-entry-page' into 'master'

Resolve web application with expressjs

Closes #3

See merge request grin-global/grin-global-ui!3
parents e20d79da 01171422
import { call, put, takeEvery } from 'redux-saga/effects';
// Constants
import {
RECEIVE_USER, RECEIVE_USERS,
SAGA_RECEIVE_USER, SAGA_RECEIVE_USERS,
} from 'user/constants';
// Model
import User from '@gringlobal/client/model/user/User';
import { IPageRequest, FilteredPage } from '@gringlobal/client/model/page';
// Service
import UserService from '@gringlobal/client/service/UserService';
export const userPublicSagas = [
takeEvery(SAGA_RECEIVE_USER, getUserSaga),
takeEvery(SAGA_RECEIVE_USERS, listUsersSaga),
];
// #getUser
export const getUserAction = (uuid) => ({
type: SAGA_RECEIVE_USER,
payload: {
uuid,
},
});
function* getUserSaga(action) {
const { uuid } = action.payload;
const user = yield call(UserService.getUser, uuid);
yield put(receiveUserSuccess(user));
}
const receiveUserSuccess = (user: User) => ({
type: RECEIVE_USER,
payload: { user },
});
// #listUsers
export const listUsersAction = (filter: string = '', pageR: IPageRequest = { page: 0 }) => ({
type: SAGA_RECEIVE_USERS,
payload: {
filter,
pageR,
},
});
function * listUsersSaga(action) {
const users = yield call(UserService.listUsers, action.payload.filter, action.payload.pageR);
yield put(receiveUsersSuccess(users));
}
const receiveUsersSuccess = (users: FilteredPage<User>) => ({
type: RECEIVE_USERS,
payload: { users },
});
export const USER_LOGIN_FORM = 'user/form/USER_LOGIN_FORM';
export const RECEIVE_USER = 'user/public/RECEIVE_USER';
export const RECEIVE_USERS = 'user/public/RECEIVE_USERS';
export const SAGA_RECEIVE_USER = 'saga/user/public/RECEIVE_USER';
export const SAGA_RECEIVE_USERS = 'saga/user/public/RECEIVE_USERS';
export const SAGA_LOGIN_USER = 'saga/user/public/LOGIN_USER';
export const SAGA_LOGOUT_USER = 'saga/user/public/LOGOUT_USER';
import { combineReducers } from 'redux';
import userPublic from 'user/reducer/public';
const rootReducer = combineReducers({
public: userPublic,
});
export default rootReducer;
import update from 'immutability-helper';
// Constants
import { RECEIVE_USER, RECEIVE_USERS } from 'user/constants';
// Model
import User from '@gringlobal/client/model/user/User';
import { FilteredPage } from '@gringlobal/client/model/page';
const initialState: {
users: FilteredPage<User>,
user: User,
} = {
users: null,
user: null,
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_USER: {
const { user } = action.payload;
const { users } = state;
const updatedIndex = users && users.content && users.content.findIndex((userDetails) => userDetails.uuid === user.uuid) || -1;
if (updatedIndex !== -1) {
return update(state, {
user: { $set: user },
users: {
[updatedIndex]: { $set: user },
},
});
} else {
return update(state, {
user: { $set: user },
});
}
}
case RECEIVE_USERS: {
const { users } = action.payload;
return update(state, {
users: { $set: users },
});
}
default:
return state;
}
};
export default userReducer;
import Loadable from '@gringlobal/client/utilities/CustomReactLoadable';
// model
import IRoute from '@gringlobal/client/model/common/IRoute';
const publicRoutes: IRoute[] = [
{
exact: true,
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "user" */'user/ui/LoginPage'),
}),
path: '/login',
},
{
exact: true,
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "user" */ 'user/ui/admin/BrowsePage'),
}),
path: '/user',
},
{
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "user" */'user/ui/admin/DetailsPage'),
}),
path: '/user/:uuid',
},
];
export { publicRoutes as userPublicRotes };
{
"public": {
"p": {
"login": {
"loggedIn": "You are logged in as {{username}}",
"username": {
"label": "Username",
"placeholder": "Your GG-CE username"
},
"password": {
"label": "Password",
"placeholder": "Your password"
}
}
}
},
"admin": {
"p": {
"browse": {
"title": "List of users"
}
}
}
}
import * as React from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import { WithTranslation, withTranslation } from 'react-i18next';
// action
import { loginUserAction, logoutUserAction } from '@gringlobal/client/action/login';
// ui
import LoginForm from 'user/ui/c/form/LoginForm';
import CenteredLayout from '@gringlobal/client/ui/common/layout/CenteredLayout'
import BackButton from '@gringlobal/client/ui/common/button/BackButton';
interface ILoginPageProps extends React.ClassAttributes<any>, WithTranslation {
login: any;
loginUserAction: (username, password, needRedirect?) => void;
logoutUserAction: () => void;
getUser: (uuid) => void;
uuid: string;
}
class UserDetailsPage extends React.Component<ILoginPageProps> {
protected handleLogin = (value) => {
const { loginUserAction } = this.props;
loginUserAction(value.username, value.password, false);
};
protected logout = () => {
const { logoutUserAction } = this.props;
logoutUserAction();
};
public render() {
const { login, t } = this.props;
return !login || !login.user_name ? (
<>
<CenteredLayout>
<LoginForm
onSubmit={ this.handleLogin }
/>
</CenteredLayout>
<BackButton/>
</>
) : (
<span>
<div>
<p>{ t('user.public.p.login.loggedIn', { username: login.user_name })}</p>
<button onClick={ this.logout }>{ t('common:action.logout') }</button>
</div>
<BackButton/>
</span>
);
}
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loginUserAction,
logoutUserAction,
}, dispatch);
export default compose<ILoginPageProps>(
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
)(UserDetailsPage) as ILoginPageProps;
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { WithTranslation, withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
// Action
import { listUsersAction } from 'user/action/public';
// Model
import User from '@gringlobal/client/model/user/User';
import { FilteredPage } from '@gringlobal/client/model/page';
// Ui
import BackButton from '@gringlobal/client/ui/common/button/BackButton';
interface IBrowsePageProps extends React.ClassAttributes<any>, WithTranslation {
listUsersAction: () => void;
users: FilteredPage<User>;
}
class UserBrowsePage extends React.Component<IBrowsePageProps> {
protected static needs = [
({}) => listUsersAction(),
];
public componentWillMount(): void {
const { users, listUsersAction } = this.props;
if (!users || !users.content || users.content.length === 0) {
listUsersAction();
}
}
public render() {
const { users, t } = this.props;
return (
<>
<h3>{ t('user.admin.p.browse.title') }</h3>
<ul>
{ users && users.content && users.content.length > 0 && users.content.map((user) => (
<li key={ user.uuid }>
<Link to={ `/user/${ user.uuid }` }>{ `${ user.email } - ${ user.uuid } - ${ user.roles }` }</Link>
</li>
)) }
</ul>
<BackButton/>
</>
);
}
}
const mapStateToProps = (state, ownProps) => ({
users: state.user.public.users,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
listUsersAction,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withTranslation(),
)(UserBrowsePage);
import * as React from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import { WithTranslation, withTranslation } from 'react-i18next';
// Action
import { getUserAction } from 'user/action/public';
// Model
import User from '@gringlobal/client/model/user/User';
interface IUserDetailsPage extends React.ClassAttributes<any>, WithTranslation {
user: User;
getUserAction: (uuid) => void;
uuid: string;
}
class UserDetailsPage extends React.Component<IUserDetailsPage> {
public componentWillMount(): void {
const { uuid, user, getUserAction } = this.props;
if (!user || user.uuid !== uuid) {
getUserAction(uuid);
}
}
public render() { // TODO add t
const { user } = this.props;
return user ? (
<div>
{ JSON.stringify(user) }
</div>
) : null;
}
}
const mapStateToProps = (state, ownProps) => ({
user: state.user.public.user,
uuid: ownProps.match.params.uuid,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
getUserAction,
}, dispatch);
export default compose<IUserDetailsPage>(
withTranslation,
connect(mapStateToProps, mapDispatchToProps),
)(UserDetailsPage) as IUserDetailsPage;
import * as React from 'react';
import { compose } from 'redux';
import { Form, Field, FormProps } from 'react-final-form';
import { withTranslation, WithTranslation } from 'react-i18next';
// utilities
import * as Validators from '@gringlobal/client/utilities/validators'
// UI
import { TextField } from '@gringlobal/client/ui/common/form/TextField';
// material ui
import { Button } from '@material-ui/core';
interface ILoginFormProps extends React.ClassAttributes<any>, WithTranslation, FormProps {
}
class LoginFormInternal extends React.Component<any> {
public render() {
const { handleSubmit, t } = this.props;
return (
<form onSubmit={ handleSubmit }>
<Field
label={ t('user.public.p.login.username.label') }
placeholder={ t('user.public.p.login.username.placeholder') }
name="username"
type="text"
validate={ Validators.required }
component={ TextField }
/>
<Field
label={ t('user.public.p.login.password.label') }
placeholder={ t('user.public.p.login.password.placeholder') }
name="password"
type="password"
validate={ Validators.required }
component={ TextField }
/>
<Button variant="contained" className="float-right" type="submit">{t('common:action.login')}</Button>
</form>
);
}
}
class LoginForm extends React.Component<ILoginFormProps, any> {
public render() {
const { t, onSubmit } = this.props;
return (
<Form
onSubmit={ onSubmit }
render={ (props) => <LoginFormInternal t={ t } {...props}/> }
/>
);
}
}
export default compose(
withTranslation(),
)(LoginForm);
This diff is collapsed.
This diff is collapsed.
// Animated Icons
// --------------------------
.@{fa-css-prefix}-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.@{fa-css-prefix}-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
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