ESlint: application générale des règles

This commit is contained in:
wpetit 2020-03-09 14:49:56 +01:00
parent 21fe79684d
commit e5c5d9c8d2
40 changed files with 396 additions and 398 deletions

View File

@ -1,20 +0,0 @@
<?php
namespace App\Controller;
use App\Http\DataResponse;
use Symfony\Component\Routing\Annotation\Route;
class RequestController
{
/**
* @Route("/api/v1/request", name="api_v1_list_requests")
*/
public function listRequests()
{
return new DataResponse([
]);
}
}

View File

@ -1,31 +1,47 @@
export const LOGIN_REQUEST = 'LOGIN_REQUEST' export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILURE = 'LOGIN_FAILURE'; export const LOGIN_FAILURE = 'LOGIN_FAILURE';
export function login(username, password) { export function login(username, password) {
return { type: LOGIN_REQUEST, username, password } return { type: LOGIN_REQUEST, username, password };
} }
export function loginFailure(username, error) { export function loginFailure(username, error) {
return { type: LOGIN_FAILURE, username, error } return { type: LOGIN_FAILURE, username, error };
} }
export function loginSuccess(username) { export function loginSuccess(username) {
return { type: LOGIN_SUCCESS, username } return { type: LOGIN_SUCCESS, username };
} }
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST' export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
export function logout() { export function logout() {
return { type: LOGOUT_REQUEST } return { type: LOGOUT_REQUEST };
} }
export function logoutFailure(error) { export function logoutFailure(error) {
return { type: LOGOUT_FAILURE, error } return { type: LOGOUT_FAILURE, error };
} }
export function logoutSuccess() { export function logoutSuccess() {
return { type: LOGOUT_SUCCESS } return { type: LOGOUT_SUCCESS };
}
export const REFRESH_USER_SESSION_REQUEST = 'REFRESH_USER_SESSION_REQUEST';
export const REFRESH_USER_SESSION_SUCCESS = 'REFRESH_USER_SESSION_SUCCESS';
export const REFRESH_USER_SESSION_FAILURE = 'REFRESH_USER_SESSION_FAILURE';
export function refreshUserSession() {
return { type: REFRESH_USER_SESSION_REQUEST };
}
export function refreshUserSessionSuccess(user) {
return { type: REFRESH_USER_SESSION_SUCCESS, user };
}
export function refreshUserSessionFailure(error) {
return { type: REFRESH_USER_SESSION_FAILURE, error };
} }

View File

@ -1,11 +1,11 @@
export const ADD_MESSAGE = 'ADD_MESSAGE' export const ADD_MESSAGE = 'ADD_MESSAGE';
export function addMessage(type, text) { export function addMessage(type, text) {
return { type: ADD_MESSAGE, text, messageType: type }; return { type: ADD_MESSAGE, text, messageType: type };
} }
export const REMOVE_OLDEST_MESSAGE = 'REMOVE_OLDEST_MESSAGE' export const REMOVE_OLDEST_MESSAGE = 'REMOVE_OLDEST_MESSAGE';
export function removeOldestMessage() { export function removeOldestMessage() {
return { type: REMOVE_OLDEST_MESSAGE } return { type: REMOVE_OLDEST_MESSAGE };
} }

View File

@ -3,13 +3,13 @@ export const PROJECT_USER_LIST_SUCCESS = 'PROJECT_USER_LIST_SUCCESS';
export const PROJECT_USER_LIST_FAILURE = 'PROJECT_USER_LIST_FAILURE'; export const PROJECT_USER_LIST_FAILURE = 'PROJECT_USER_LIST_FAILURE';
export function projectUserListRequest() { export function projectUserListRequest() {
return { type: PROJECT_USER_LIST} return { type: PROJECT_USER_LIST};
} }
export function projectUserListSuccess(projects) { export function projectUserListSuccess(projects) {
return { type: PROJECT_USER_LIST_SUCCESS, projects } return { type: PROJECT_USER_LIST_SUCCESS, projects };
} }
export function projectUserListFailure(error) { export function projectUserListFailure(error) {
return { type: PROJECT_USER_LIST_FAILURE, error } return { type: PROJECT_USER_LIST_FAILURE, error };
} }
export const PROJECT_LIST = 'PROJECT_LIST'; export const PROJECT_LIST = 'PROJECT_LIST';
@ -17,11 +17,11 @@ export const PROJECT_LIST_SUCCESS = 'PROJECT_LIST_SUCCESS';
export const PROJECT_LIST_FAILURE = 'PROJECT_LIST_FAILURE'; export const PROJECT_LIST_FAILURE = 'PROJECT_LIST_FAILURE';
export function projectList() { export function projectList() {
return { type: PROJECT_LIST} return { type: PROJECT_LIST};
} }
export function projectListSuccess(projects) { export function projectListSuccess(projects) {
return { type: PROJECT_LIST_SUCCESS, projects } return { type: PROJECT_LIST_SUCCESS, projects };
} }
export function projectListFailure(error) { export function projectListFailure(error) {
return { type: PROJECT_LIST_FAILURE, error } return { type: PROJECT_LIST_FAILURE, error };
} }

View File

@ -3,13 +3,13 @@ export const CREATE_USER_SUCCESS = 'CREATE_USER_SUCCESS';
export const CREATE_USER_FAILURE = 'CREATE_USER_FAILURE'; export const CREATE_USER_FAILURE = 'CREATE_USER_FAILURE';
export function createUser(username, password) { export function createUser(username, password) {
return { type: CREATE_USER_REQUEST, username, password} return { type: CREATE_USER_REQUEST, username, password};
} }
export function createUserSuccess(user) { export function createUserSuccess(user) {
return { type: CREATE_USER_SUCCESS, user } return { type: CREATE_USER_SUCCESS, user };
} }
export function createUserFailure(error) { export function createUserFailure(error) {
return { type: CREATE_USER_FAILURE, error } return { type: CREATE_USER_FAILURE, error };
} }

View File

@ -1,8 +1,8 @@
import { Component, Fragment } from 'react' import { Component, Fragment } from 'react';
import { hot } from 'react-hot-loader' import { hot } from 'react-hot-loader';
import { HashRouter } from 'react-router-dom' // ou BrowserRouter import { HashRouter } from 'react-router-dom'; // ou BrowserRouter
import { Route, Switch, Redirect } from 'react-router' import { Route, Switch, Redirect } from 'react-router';
import HomePage from './pages/home'; import { ConnectedHomePage as HomePage } from './pages/home';
import { ConnectedLoginPage as LoginPage } from './pages/login'; import { ConnectedLoginPage as LoginPage } from './pages/login';
import DashBoardClient from './pages/DashBoardClient'; import DashBoardClient from './pages/DashBoardClient';
import DashBoardDev from './pages/DashBoardDev'; import DashBoardDev from './pages/DashBoardDev';
@ -11,24 +11,23 @@ import { connect } from 'react-redux';
import { history } from './util/history'; import { history } from './util/history';
export class App extends Component { export class App extends Component {
render () { render () {
return ( return (
<Fragment> <Fragment>
<HashRouter history={history}> <HashRouter history={history}>
<Switch> <Switch>
<Route path='/login' exact component={LoginPage} /> <Route path='/login' exact component={LoginPage} />
<Route path='/logout' exact component={LogoutPage} /> <Route path='/logout' exact component={LogoutPage} />
<Route path='/home' exact component={HomePage} /> <Route path='/home' exact component={HomePage} />
<Route path='/dashboard-client' exact component={DashBoardClient} /> <Route path='/dashboard-client' exact component={DashBoardClient} />
<Route path='/dashboard-dev' exact component={DashBoardDev} /> <Route path='/dashboard-dev' exact component={DashBoardDev} />
<Route component={() => <Redirect to="/home" />} /> <Route component={() => <Redirect to="/home" />} />
</Switch> </Switch>
</HashRouter> </HashRouter>
</Fragment> </Fragment>
) );
} }
} }
export const ConnectedApp = connect()(App); export const ConnectedApp = connect()(App);
export const HotApp = hot(module)(ConnectedApp); export const HotApp = hot(module)(ConnectedApp);

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react';
import ProjectTile from './ProjectTile' import ProjectTile from './ProjectTile';
export default ({ projects, withRequest }) => ( export default ({ projects, withRequest }) => (
<div className="tile is-parent is-vertical"> <div className="tile is-parent is-vertical">
@ -9,4 +9,4 @@ export default ({ projects, withRequest }) => (
)) ))
} }
</div> </div>
) );

View File

@ -1,11 +1,11 @@
import React from 'react' import React from 'react';
import RequestList from './RequestList' import RequestList from './RequestList';
export default ({ project, withRequest }) => ( export default ({ project, withRequest }) => (
<article className="tile is-child notification is-primary"> <article className="tile is-child notification is-primary">
<p className="title">{project.name}</p> <p className="title">{project.name}</p>
{ {
withRequest && <RequestList requests={project.requests || []} /> withRequest && <RequestList requests={project.requests || []} />
} }
</article> </article>
) );

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react';
import RequestTile from './RequestTile' import RequestTile from './RequestTile';
export default ({ requests }) => ( export default ({ requests }) => (
<div className="tile is-parent is-vertical"> <div className="tile is-parent is-vertical">
@ -9,4 +9,4 @@ export default ({ requests }) => (
)) ))
} }
</div> </div>
) );

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react';
import styles from './RequestTile.css' import styles from './RequestTile.css';
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom';
export default ({ request }) => ( export default ({ request }) => (
<Link to={`dashboard-client/detail-demande/${request.id}`}> <Link to={`dashboard-client/detail-demande/${request.id}`}>
@ -11,4 +11,4 @@ export default ({ request }) => (
</div> </div>
</article> </article>
</Link> </Link>
) );

View File

@ -3,74 +3,74 @@ import { WithForm } from './WithForm';
export function UserForm({ form, onSubmit }) { export function UserForm({ form, onSubmit }) {
useEffect(() => { useEffect(() => {
if (form.valid && form.submitted) onSubmit(form.data); if (form.valid && form.submitted) onSubmit(form.data);
}, [form.valid, form.submitted]); }, [form.valid, form.submitted]);
return ( return (
<form className="form" onSubmit={form.onSubmit}> <form className="form" onSubmit={form.onSubmit}>
<div className="field"> <div className="field">
<label className="label" htmlFor="username">Nom d'utilisateur</label> <label className="label" htmlFor="username">Nom d'utilisateur</label>
<div className="control"> <div className="control">
<input className={`input ${form.hasError('username') ? 'is-danger' : ''}`} type="text" <input className={`input ${form.hasError('username') ? 'is-danger' : ''}`} type="text"
name="username" value={form.field('username', '')} name="username" value={form.field('username', '')}
onChange={form.onChange('username')} /> onChange={form.onChange('username')} />
</div> </div>
{ {
form.hasError('username') ? form.hasError('username') ?
<p className="help is-danger">{form.error('username')}</p> <p className="help is-danger">{form.error('username')}</p>
: null : null
} }
</div> </div>
<div className="field"> <div className="field">
<label className="label" htmlFor="password">Mot de passe</label> <label className="label" htmlFor="password">Mot de passe</label>
<div className="control"> <div className="control">
<input className={`input ${form.hasError('password') ? 'is-danger' : ''}`} <input className={`input ${form.hasError('password') ? 'is-danger' : ''}`}
type="password" name="password" type="password" name="password"
autoComplete='new-password' autoComplete='new-password'
value={form.field('password', '')} value={form.field('password', '')}
onChange={form.onChange('password')} /> onChange={form.onChange('password')} />
</div> </div>
{ {
form.hasError('password') ? form.hasError('password') ?
<p className="help is-danger">{form.error('password')}</p> <p className="help is-danger">{form.error('password')}</p>
: null : null
} }
</div> </div>
<div className="field"> <div className="field">
<label className="label" htmlFor="passwordVerification">Mot de passe (vérification)</label> <label className="label" htmlFor="passwordVerification">Mot de passe (vérification)</label>
<div className="control"> <div className="control">
<input className={`input ${form.hasError('passwordVerification') ? 'is-danger' : ''}`} <input className={`input ${form.hasError('passwordVerification') ? 'is-danger' : ''}`}
type="password" name="passwordVerification" type="password" name="passwordVerification"
autoComplete='new-password' autoComplete='new-password'
value={form.field('passwordVerification', '')} value={form.field('passwordVerification', '')}
onChange={form.onChange('passwordVerification')} /> onChange={form.onChange('passwordVerification')} />
</div> </div>
{ {
form.hasError('passwordVerification') ? form.hasError('passwordVerification') ?
<p className="help is-danger">{form.error('passwordVerification')}</p> <p className="help is-danger">{form.error('passwordVerification')}</p>
: null : null
} }
</div> </div>
<button type="submit" disabled={!form.valid} className="button">Envoyer</button> <button type="submit" disabled={!form.valid} className="button">Envoyer</button>
</form> </form>
); );
}; }
const validators = { const validators = {
username: (value, formData) => { username: (value, formData) => {
if (!value) return { hasError: true, message: 'Le nom d\'utilisateur ne peut être vide !' } if (!value) return { hasError: true, message: 'Le nom d\'utilisateur ne peut être vide !' };
return { hasError: false, message: '' }; return { hasError: false, message: '' };
}, },
password: (value, formData) => { password: (value, formData) => {
if (!value) return { hasError: true, message: 'Le mot de passe ne peut pas être vide !' } if (!value) return { hasError: true, message: 'Le mot de passe ne peut pas être vide !' };
if (value !== formData.passwordVerification) return { hasError: true, message: 'Vos deux mots de passe sont différents !' } if (value !== formData.passwordVerification) return { hasError: true, message: 'Vos deux mots de passe sont différents !' };
return { hasError: false, message: '' }; return { hasError: false, message: '' };
}, },
passwordVerification: (value, formData) => { passwordVerification: (value, formData) => {
if (value !== formData.password) return { hasError: true, message: 'Vos deux mots de passe sont différents !' } if (value !== formData.password) return { hasError: true, message: 'Vos deux mots de passe sont différents !' };
return { hasError: false, message: '' }; return { hasError: false, message: '' };
} }
}; };
export const ExtendedUserForm = WithForm(validators)(UserForm); export const ExtendedUserForm = WithForm(validators)(UserForm);

View File

@ -23,7 +23,7 @@ describe('<UserForm />', () => {
password: 'test', password: 'test',
passwordVerification: 'test' passwordVerification: 'test'
}); });
}) });
}); });
describe('username validation', () => { describe('username validation', () => {

View File

@ -16,15 +16,15 @@ export function WithForm(validators) {
setFormErrors(formErrors => { setFormErrors(formErrors => {
return { ...formErrors, [name]: { hasError: false, message: '' } }; return { ...formErrors, [name]: { hasError: false, message: '' } };
}); });
return return;
}; }
const value = formData[name]; const value = formData[name];
const result = validators[name](value, formData); const result = validators[name](value, formData);
setFormErrors(formErrors => { setFormErrors(formErrors => {
return { ...formErrors, [name]: result }; return { ...formErrors, [name]: result };
}); });
} };
useEffect(() => { useEffect(() => {
Object.keys(formData).forEach(validateField); Object.keys(formData).forEach(validateField);
@ -78,7 +78,7 @@ export function WithForm(validators) {
return <Component form={form} {...props} />; return <Component form={form} {...props} />;
}; };
} };
} }
// connect(mapStateToProps)(MyComponent) // connect(mapStateToProps)(MyComponent)

View File

@ -2,20 +2,20 @@ export function MessageList({ messages }) {
if (!Array.isArray(messages)) return null; if (!Array.isArray(messages)) return null;
return ( return (
<div className="columns" style={{marginTop:"1em"}}> <div className="columns" style={{marginTop:'1em'}}>
<div className="column is-4 is-offset-4"> <div className="column is-4 is-offset-4">
<div className="messages"> <div className="messages">
{ {
messages.map((m, i) => { messages.map((m, i) => {
return ( return (
<div key={`message-${i}`} className={`message is-${m.type}`}> <div key={`message-${i}`} className={`message is-${m.type}`}>
<div className="message-body"> <div className="message-body">
{m.text} {m.text}
</div> </div>
</div> </div>
) );
}) })
} }
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,10 @@
export class APIError extends Error { export class APIError extends Error {
constructor(endpoint, code, message, data) { constructor(endpoint, code, message, data) {
super(`APIError: ${message}`); super(`APIError: ${message}`);
this.endpoint = endpoint; this.endpoint = endpoint;
this.code = code; this.code = code;
this.data = data; this.data = data;
Error.captureStackTrace(this, APIError); Error.captureStackTrace(this, APIError);
} }
getEndpoint() { getEndpoint() {

View File

@ -1,6 +1,6 @@
export class UnauthorizedError extends Error { export class UnauthorizedError extends Error {
constructor() { constructor() {
super("Unauthorized"); super('Unauthorized');
Error.captureStackTrace(this, UnauthorizedError); Error.captureStackTrace(this, UnauthorizedError);
} }
} }

View File

@ -1,14 +1,14 @@
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import { HotApp as App } from './app' import { HotApp as App } from './app';
import { configureStore } from './store/store' import { configureStore } from './store/store';
import { Provider } from 'react-redux' import { Provider } from 'react-redux';
import 'bulma/css/bulma.min.css'; import 'bulma/css/bulma.min.css';
const store = configureStore() const store = configureStore();
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider>, </Provider>,
document.getElementById('app') document.getElementById('app')
) );

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { connect } from 'react-redux' import { connect } from 'react-redux';
import Page from './page'; import Page from './page';
import ProjectList from '../components/ProjectList'; import ProjectList from '../components/ProjectList';
import { projectUserListRequest } from '../actions/project'; import { projectUserListRequest } from '../actions/project';
@ -7,8 +7,8 @@ import { projectUserListRequest } from '../actions/project';
const DashBoardClient = ({ projects = [], ...props }) => { const DashBoardClient = ({ projects = [], ...props }) => {
useEffect(() => { useEffect(() => {
props.dispatch(projectUserListRequest()) props.dispatch(projectUserListRequest());
}, []) }, []);
projects = [ projects = [
{ {
id: 1, id: 1,
@ -78,7 +78,7 @@ const DashBoardClient = ({ projects = [], ...props }) => {
] ]
}, },
] ];
return ( return (
<Page title="dashBoard client"> <Page title="dashBoard client">
<div className="notification"> <div className="notification">
@ -87,11 +87,11 @@ const DashBoardClient = ({ projects = [], ...props }) => {
<ProjectList projects={projects} withRequest /> <ProjectList projects={projects} withRequest />
</Page> </Page>
); );
} };
const mapStateToProps = ({ project }) => ({ const mapStateToProps = ({ project }) => ({
projects: project.items, projects: project.items,
}) });
export default connect(mapStateToProps)(DashBoardClient) export default connect(mapStateToProps)(DashBoardClient);

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { connect } from 'react-redux' import { connect } from 'react-redux';
import Page from './page'; import Page from './page';
import ProjectList from '../components/ProjectList'; import ProjectList from '../components/ProjectList';
import { projectList } from '../actions/project'; import { projectList } from '../actions/project';
@ -7,8 +7,8 @@ import { projectList } from '../actions/project';
const DashBoardDev = ({ projects = [], ...props }) => { const DashBoardDev = ({ projects = [], ...props }) => {
useEffect(() => { useEffect(() => {
props.dispatch(projectList()) props.dispatch(projectList());
}, []) }, []);
projects = [ projects = [
{ {
id: 1, id: 1,
@ -78,7 +78,7 @@ const DashBoardDev = ({ projects = [], ...props }) => {
] ]
}, },
] ];
return ( return (
<Page title="dashBoard dev"> <Page title="dashBoard dev">
<div className="notification"> <div className="notification">
@ -87,11 +87,11 @@ const DashBoardDev = ({ projects = [], ...props }) => {
<ProjectList projects={projects} /> <ProjectList projects={projects} />
</Page> </Page>
); );
} };
const mapStateToProps = ({ project }) => ({ const mapStateToProps = ({ project }) => ({
projects: project.items, projects: project.items,
}) });
export default connect(mapStateToProps)(DashBoardDev) export default connect(mapStateToProps)(DashBoardDev);

View File

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
import Page from './page'; import Page from './page';
import { ExtendedUserForm as UserForm } from '../components/UserForm'; import { ExtendedUserForm as UserForm } from '../components/UserForm';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -6,45 +6,45 @@ import { createUser } from '../actions/user.actions';
export class HomePage extends React.PureComponent { export class HomePage extends React.PureComponent {
render() { render() {
const { createUserFormError } = this.props; const { createUserFormError } = this.props;
const onUserFormSubmit = ({ username, password }) => { const onUserFormSubmit = ({ username, password }) => {
this.props.dispatch(createUser(username, password)); this.props.dispatch(createUser(username, password));
}; };
return ( return (
<Page title="home"> <Page title="home">
<div className="section"> <div className="section">
<h1 className="title">Bienvenue sur PleaseWait !</h1> <h1 className="title">Bienvenue sur PleaseWait !</h1>
<h2 className="subtitle">Le gestionnaire de ticket simplifié.</h2> <h2 className="subtitle">Le gestionnaire de ticket simplifié.</h2>
<a href="#/logout">Se déconnecter</a> <a href="#/logout">Se déconnecter</a>
<UserForm onSubmit={onUserFormSubmit} errors={createUserFormError} /> <UserForm onSubmit={onUserFormSubmit} errors={createUserFormError} />
</div> </div>
</Page> </Page>
); );
} }
} }
function transformErrorCode(code) { function transformErrorCode(code) {
switch (code) { switch (code) {
case 0: case 0:
return { return {
username: { username: {
hasError: true, hasError: true,
message: "Le nom d'utilisateur ne peut pas être vide. (API)" message: 'Le nom d\'utilisateur ne peut pas être vide. (API)'
} }
} };
default: default:
return null; return null;
} }
} }
function mapStateToProps({ users }) { function mapStateToProps({ users }) {
return { return {
createUserFormError: transformErrorCode(users.createUserForm.errorCode), createUserFormError: transformErrorCode(users.createUserForm.errorCode),
} };
} }
export const ConnectedHomePage = connect(mapStateToProps)(HomePage) export const ConnectedHomePage = connect(mapStateToProps)(HomePage);

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react';
import { ConnectedPage as Page } from './page'; import { ConnectedPage as Page } from './page';
import { login } from '../actions/auth.actions'; import { login } from '../actions/auth.actions';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -13,10 +13,10 @@ export function LoginPage({ dispatch, isLoggedIn, history }) {
evt.preventDefault(); evt.preventDefault();
const { username, password } = formData; const { username, password } = formData;
dispatch(login(username, password)); dispatch(login(username, password));
} };
useEffect(() => { useEffect(() => {
if (isLoggedIn) history.push('/home'); if (isLoggedIn) history.push('/home');
}, [isLoggedIn]); }, [isLoggedIn]);
return ( return (

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { logout } from '../actions/auth.actions'; import { logout } from '../actions/auth.actions';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ConnectedPage as Page } from './page'; import { ConnectedPage as Page } from './page';
@ -16,7 +16,7 @@ export function LogoutPage({ dispatch }) {
</div> </div>
</div> </div>
</Page> </Page>
) );
} }
export const ConnectedLogoutPage = connect()(LogoutPage); export const ConnectedLogoutPage = connect()(LogoutPage);

View File

@ -1,33 +1,33 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { MessageList } from '../components/message-list'; import { MessageList } from '../components/message-list';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { removeOldestMessage } from '../actions/message.actions'; import { removeOldestMessage } from '../actions/message.actions';
export default function Page({ title, messages, dispatch, children }) { export default function Page({ title, messages, dispatch, children }) {
useEffect(() => { useEffect(() => {
document.title = title ? `${title } - PleaseWait` : 'PleaseWait'; document.title = title ? `${title } - PleaseWait` : 'PleaseWait';
}); });
useEffect(() => { useEffect(() => {
if (!Array.isArray(messages) || messages.length === 0) return; if (!Array.isArray(messages) || messages.length === 0) return;
let timeoutId = setTimeout(() => { let timeoutId = setTimeout(() => {
dispatch(removeOldestMessage()); dispatch(removeOldestMessage());
}, 5000); }, 5000);
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
}, [messages]) }, [messages]);
return ( return (
<div className="container"> <div className="container">
<MessageList messages={messages} /> <MessageList messages={messages} />
{ children } { children }
</div> </div>
); );
} }
function mapStateToProps(state) { function mapStateToProps(state) {
return { messages: state.messages.sortedByTimestamp }; return { messages: state.messages.sortedByTimestamp };
} }
export const ConnectedPage = connect(mapStateToProps)(Page); export const ConnectedPage = connect(mapStateToProps)(Page);

View File

@ -1,4 +1,4 @@
import { ADD_MESSAGE, REMOVE_OLDEST_MESSAGE } from "../actions/message.actions"; import { ADD_MESSAGE, REMOVE_OLDEST_MESSAGE } from '../actions/message.actions';
const initialState = { const initialState = {
sortedByTimestamp: [] sortedByTimestamp: []
@ -6,11 +6,11 @@ const initialState = {
export function messagesReducer(state = initialState, action) { export function messagesReducer(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ADD_MESSAGE: case ADD_MESSAGE:
return handleAddMessage(state, action); return handleAddMessage(state, action);
case REMOVE_OLDEST_MESSAGE: case REMOVE_OLDEST_MESSAGE:
return handleRemoveOldestMessage(state, action); return handleRemoveOldestMessage(state, action);
}; }
return state; return state;
} }
@ -21,12 +21,12 @@ function handleAddMessage(state, action) {
{ ts: Date.now(), text: action.text, type: action.messageType }, { ts: Date.now(), text: action.text, type: action.messageType },
...state.sortedByTimestamp ...state.sortedByTimestamp
] ]
} };
}; }
function handleRemoveOldestMessage(state, action) { function handleRemoveOldestMessage(state, action) {
return { return {
...state, ...state,
sortedByTimestamp: state.sortedByTimestamp.slice(0, -1) sortedByTimestamp: state.sortedByTimestamp.slice(0, -1)
} };
}; }

View File

@ -1,15 +1,15 @@
import { import {
PROJECT_USER_LIST_SUCCESS, PROJECT_USER_LIST_SUCCESS,
PROJECT_USER_LIST_FAILURE PROJECT_USER_LIST_FAILURE
} from '../actions/project' } from '../actions/project';
const initialState = { const initialState = {
items: [] items: []
} };
export default (state = initialState, action) => { export default (state = initialState, action) => {
console.log(`Action: ${JSON.stringify(action)}`) console.log(`Action: ${JSON.stringify(action)}`);
switch (action.type) { switch (action.type) {
@ -17,15 +17,15 @@ export default (state = initialState, action) => {
return { return {
...state, ...state,
items: action.projects items: action.projects
} };
case PROJECT_USER_LIST_FAILURE: case PROJECT_USER_LIST_FAILURE:
return { return {
...state, ...state,
items: [], items: [],
error: action.error error: action.error
} };
} }
return state return state;
} };

View File

@ -1,4 +1,4 @@
import { LOGIN_SUCCESS, LOGOUT_SUCCESS } from "../actions/auth.actions"; import { LOGIN_SUCCESS, LOGOUT_SUCCESS } from '../actions/auth.actions';
const initialState = { const initialState = {
isLoggedIn: false, isLoggedIn: false,
@ -7,11 +7,11 @@ const initialState = {
export function sessionReducer(state = initialState, action) { export function sessionReducer(state = initialState, action) {
switch(action.type) { switch(action.type) {
case LOGIN_SUCCESS: case LOGIN_SUCCESS:
return handleLoginSuccess(state, action); return handleLoginSuccess(state, action);
case LOGOUT_SUCCESS: case LOGOUT_SUCCESS:
return handleLogoutSuccess(state, action); return handleLogoutSuccess(state, action);
}; }
return state; return state;
} }
@ -22,13 +22,13 @@ function handleLoginSuccess(state, action) {
user: { user: {
username: action.username, username: action.username,
}, },
} };
}; }
function handleLogoutSuccess(state, action) { function handleLogoutSuccess(state, action) {
return { return {
...state, ...state,
isLoggedIn: false, isLoggedIn: false,
user: null, user: null,
} };
}; }

View File

@ -1,4 +1,4 @@
import { CREATE_USER_FAILURE } from "../actions/user.actions"; import { CREATE_USER_FAILURE } from '../actions/user.actions';
const initialState = { const initialState = {
byId: {}, byId: {},
@ -7,9 +7,9 @@ const initialState = {
export function usersReducer(state = initialState, action) { export function usersReducer(state = initialState, action) {
switch(action.type) { switch(action.type) {
case CREATE_USER_FAILURE: case CREATE_USER_FAILURE:
return handleCreateUserFailure(state, action); return handleCreateUserFailure(state, action);
}; }
return state; return state;
} }

View File

@ -11,20 +11,20 @@ export function* loginSaga(action) {
result = yield call(client.login, action.username, action.password); result = yield call(client.login, action.username, action.password);
} catch(err) { } catch(err) {
if (err instanceof UnauthorizedError) { if (err instanceof UnauthorizedError) {
yield put(addMessage('warning', "Identifiants invalides.")); yield put(addMessage('warning', 'Identifiants invalides.'));
return; return;
} }
yield put(loginFailure(action.username, err)); yield put(loginFailure(action.username, err));
yield put(addMessage('danger', "Une erreur inconnue bloque le fonctionnement normal de l'application. Veuillez réessayer plus tard.")); yield put(addMessage('danger', 'Une erreur inconnue bloque le fonctionnement normal de l\'application. Veuillez réessayer plus tard.'));
return return;
} }
if ('error' in result) { if ('error' in result) {
yield put(loginFailure(action.username, result.error)); yield put(loginFailure(action.username, result.error));
const message = result.error.message ? result.error.message : result.error.toString(); const message = result.error.message ? result.error.message : result.error.toString();
yield put(addMessage('danger', message)); yield put(addMessage('danger', message));
return return;
} }
yield put(loginSuccess(action.username)); yield put(loginSuccess(action.username));

View File

@ -1,8 +1,8 @@
import { history } from "../../util/history"; import { history } from '../../util/history';
import { addMessage } from '../../actions/message.actions'; import { addMessage } from '../../actions/message.actions';
import { put } from 'redux-saga/effects'; import { put } from 'redux-saga/effects';
export function* logoutRedirectSaga() { export function* logoutRedirectSaga() {
yield put(addMessage("success", "Vous êtes déconnecté.")); yield put(addMessage('success', 'Vous êtes déconnecté.'));
history.push('/login'); history.push('/login');
} }

View File

@ -1,6 +1,6 @@
import { APIClient } from "../../services/api-client.service"; import { APIClient } from '../../services/api-client.service';
import { logoutFailure, logoutSuccess } from "../../actions/auth.actions"; import { logoutFailure, logoutSuccess } from '../../actions/auth.actions';
import { addMessage } from "../../actions/message.actions"; import { addMessage } from '../../actions/message.actions';
import { call, put } from 'redux-saga/effects'; import { call, put } from 'redux-saga/effects';
export function* logoutSaga() { export function* logoutSaga() {
@ -10,7 +10,7 @@ export function* logoutSaga() {
result = yield call(client.logout); result = yield call(client.logout);
} catch(err) { } catch(err) {
yield put(logoutFailure(err)); yield put(logoutFailure(err));
yield put(addMessage('danger', "Une erreur inconnue bloque le fonctionnement normal de l'application. Veuillez réessayer plus tard.")); yield put(addMessage('danger', 'Une erreur inconnue bloque le fonctionnement normal de l\'application. Veuillez réessayer plus tard.'));
return; return;
} }
@ -18,7 +18,7 @@ export function* logoutSaga() {
yield put(logoutFailure(result.error)); yield put(logoutFailure(result.error));
const message = result.error.message ? result.error.message : result.error.toString(); const message = result.error.message ? result.error.message : result.error.toString();
yield put(addMessage('danger', message)); yield put(addMessage('danger', message));
return return;
} }
yield put(logoutSuccess()); yield put(logoutSuccess());

View File

@ -1,14 +1,14 @@
import { LOGIN_REQUEST, LOGOUT_REQUEST, LOGOUT_SUCCESS } from "../../actions/auth.actions"; import { LOGIN_REQUEST, LOGOUT_REQUEST, LOGOUT_SUCCESS } from '../../actions/auth.actions';
import { loginSaga } from "./login.saga"; import { loginSaga } from './login.saga';
import { logoutSaga } from "./logout.saga"; import { logoutSaga } from './logout.saga';
import { logoutRedirectSaga } from "./logout-redirect.saga"; import { logoutRedirectSaga } from './logout-redirect.saga';
import { all, takeLatest } from 'redux-saga/effects'; import { all, takeLatest } from 'redux-saga/effects';
export function * rootSaga() { export function * rootSaga() {
yield all([ yield all([
takeLatest(LOGIN_REQUEST, loginSaga), takeLatest(LOGIN_REQUEST, loginSaga),
takeLatest(LOGOUT_REQUEST, logoutSaga), takeLatest(LOGOUT_REQUEST, logoutSaga),
// Redirect after logout success // Redirect after logout success
takeLatest(LOGOUT_SUCCESS, logoutRedirectSaga), takeLatest(LOGOUT_SUCCESS, logoutRedirectSaga),
]); ]);
} }

View File

@ -1,8 +1,8 @@
import { failureActionSaga } from "./failure.saga"; import { failureActionSaga } from './failure.saga';
import { takeLatest, all } from "redux-saga/effects"; import { takeLatest, all } from 'redux-saga/effects';
export function * rootSaga(action) { export function * rootSaga(action) {
yield all([ yield all([
takeLatest(action => /_FAILURE$/g.test(action.type), failureActionSaga), takeLatest(action => /_FAILURE$/g.test(action.type), failureActionSaga),
]); ]);
} }

View File

@ -1,8 +1,8 @@
import { all } from 'redux-saga/effects'; import { all } from 'redux-saga/effects';
import { checkSessionSaga } from './session.saga'; import { refreshSessionSaga } from './session.saga';
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
checkSessionSaga(), refreshSessionSaga(),
]); ]);
} }

View File

@ -1,11 +1,14 @@
import { APIClient } from '../../services/api-client.service'; import { APIClient } from '../../services/api-client.service';
import { call } from 'redux-saga/effects'; import { call, put } from 'redux-saga/effects';
import { refreshUserSessionFailure, refreshUserSessionSuccess } from '../../actions/auth.actions';
export function* checkSessionSaga() { export function* refreshSessionSaga() {
const client = new APIClient(); const client = new APIClient();
try { let user;
yield call(client.retrieveSessionUser); try {
} catch(err) { user = yield call(client.retrieveSessionUser);
console.error(err); } catch(err) {
} yield put(refreshUserSessionFailure(err));
}
yield put(refreshUserSessionSuccess(user));
} }

View File

@ -2,17 +2,17 @@ import { call, put } from 'redux-saga/effects';
import { projectUserListFailure, projectUserListSuccess, projectListFailure, projectListSuccess } from '../actions/project'; import { projectUserListFailure, projectUserListSuccess, projectListFailure, projectListSuccess } from '../actions/project';
export function* projectUserListSaga() { export function* projectUserListSaga() {
let result let result;
try { try {
result = yield call(projectUserList); result = yield call(projectUserList);
} catch(err) { } catch(err) {
yield put(projectUserListFailure(err)); yield put(projectUserListFailure(err));
return return;
} }
if ('error' in result) { if ('error' in result) {
yield put(projectUserListFailure(result.error)); yield put(projectUserListFailure(result.error));
return return;
} }
yield put(projectUserListSuccess(result.data)); yield put(projectUserListSuccess(result.data));
@ -23,22 +23,22 @@ const projectUserList = () => {
method: 'GET', method: 'GET',
mode: 'cors', mode: 'cors',
credentials: 'include' credentials: 'include'
}).then(res => res.json()) }).then(res => res.json());
} };
export function* projectListSaga() { export function* projectListSaga() {
let result let result;
try { try {
result = yield call(projectList); result = yield call(projectList);
} catch(err) { } catch(err) {
yield put(projectListFailure(err)); yield put(projectListFailure(err));
return return;
} }
if ('error' in result) { if ('error' in result) {
yield put(projectListFailure(result.error)); yield put(projectListFailure(result.error));
return return;
} }
yield put(projectListSuccess(result.data)); yield put(projectListSuccess(result.data));
@ -49,5 +49,5 @@ const projectList = () => {
method: 'GET', method: 'GET',
mode: 'cors', mode: 'cors',
credentials: 'include' credentials: 'include'
}).then(res => res.json()) }).then(res => res.json());
} };

View File

@ -5,10 +5,10 @@ import { rootSaga as initRootSaga } from './init/root.saga';
import { rootSaga as userRootSaga } from './user/root.saga'; import { rootSaga as userRootSaga } from './user/root.saga';
export default function* rootSaga() { export default function* rootSaga() {
yield all([ yield all([
initRootSaga(), initRootSaga(),
authRootSaga(), authRootSaga(),
failureRootSaga(), failureRootSaga(),
userRootSaga() userRootSaga()
]); ]);
} }

View File

@ -3,7 +3,7 @@ import { CREATE_USER_REQUEST } from '../../actions/user.actions';
import { createUserSaga } from './user.saga'; import { createUserSaga } from './user.saga';
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
takeLatest(CREATE_USER_REQUEST, createUserSaga), takeLatest(CREATE_USER_REQUEST, createUserSaga),
]); ]);
} }

View File

@ -10,7 +10,7 @@ export function* createUserSaga({username, password}) {
} catch(err) { } catch(err) {
console.error(err); console.error(err);
yield put(createUserFailure(err)); yield put(createUserFailure(err));
return return;
} }
yield put(createUserSuccess(user)); yield put(createUserSuccess(user));

View File

@ -5,82 +5,82 @@ export const UnauthorizedStatusCode = 401;
export class APIClient { export class APIClient {
constructor(baseURL = 'http://localhost:8001/api/v1') { constructor(baseURL = 'http://localhost:8001/api/v1') {
this.baseURL = baseURL; this.baseURL = baseURL;
this.login = this.login.bind(this); this.login = this.login.bind(this);
this.logout = this.logout.bind(this); this.logout = this.logout.bind(this);
this.retrieveSessionUser = this.retrieveSessionUser.bind(this); this.retrieveSessionUser = this.retrieveSessionUser.bind(this);
this.listUsers = this.listUsers.bind(this); this.listUsers = this.listUsers.bind(this);
this.listRequests = this.listRequests.bind(this); this.listRequests = this.listRequests.bind(this);
this.createUser = this.createUser.bind(this); this.createUser = this.createUser.bind(this);
} }
login(username, password) { login(username, password) {
return this._callAPI('/login', { username, password }, 'POST') return this._callAPI('/login', { username, password }, 'POST')
.then(result => result.data); .then(result => result.data);
} }
logout() { logout() {
return this._callAPI('/logout') return this._callAPI('/logout');
} }
retrieveSessionUser() { retrieveSessionUser() {
return this._callAPI('/me') return this._callAPI('/me')
.then(result => result.data); .then(result => result.data);
} }
listUsers() { listUsers() {
return this._callAPI('/users') return this._callAPI('/users');
} }
listRequests() { listRequests() {
} }
createRequest(request) { createRequest(request) {
} }
createUser(username, password) { createUser(username, password) {
return this._callAPI('/users', { username, password }, 'POST') return this._callAPI('/users', { username, password }, 'POST')
.then(this._withAPIErrorMiddleware('create_user')) .then(this._withAPIErrorMiddleware('create_user'))
.then(result => result.data); .then(result => result.data);
} }
updateRequestStatus(reqID, newStatus) { updateRequestStatus(reqID, newStatus) {
} }
_callAPI(path, body, method='GET') { _callAPI(path, body, method='GET') {
return fetch(this.baseURL + path, { return fetch(this.baseURL + path, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
mode: 'cors', mode: 'cors',
credentials: 'include', credentials: 'include',
body: JSON.stringify(body), body: JSON.stringify(body),
}) })
.then(this._withUnauthorizedErrorMiddleware()) .then(this._withUnauthorizedErrorMiddleware())
.then(res => res.json()) .then(res => res.json());
} }
_withUnauthorizedErrorMiddleware() { _withUnauthorizedErrorMiddleware() {
return res => { return res => {
if (res.status === UnauthorizedStatusCode) { if (res.status === UnauthorizedStatusCode) {
throw new UnauthorizedError(); throw new UnauthorizedError();
} }
return res; return res;
} };
} }
_withAPIErrorMiddleware(endpoint) { _withAPIErrorMiddleware(endpoint) {
return result => { return result => {
if (result.error) { if (result.error) {
const { code, message, data } = result.error; const { code, message, data } = result.error;
throw new APIError(endpoint, code, message, data); throw new APIError(endpoint, code, message, data);
} }
return result; return result;
} };
} }
} }

View File

@ -1,18 +1,18 @@
import { createStore, applyMiddleware, combineReducers, compose } from 'redux' import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import createSagaMiddleware from 'redux-saga' import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/root' import rootSaga from '../sagas/root';
import { sessionReducer } from '../reducers/session.reducers'; import { sessionReducer } from '../reducers/session.reducers';
import { messagesReducer } from '../reducers/messages.reducers'; import { messagesReducer } from '../reducers/messages.reducers';
import project from '../reducers/project' import project from '../reducers/project';
import { usersReducer } from '../reducers/users.reducers'; import { usersReducer } from '../reducers/users.reducers';
const sagaMiddleware = createSagaMiddleware() const sagaMiddleware = createSagaMiddleware();
const rootReducer = combineReducers({ const rootReducer = combineReducers({
session: sessionReducer, session: sessionReducer,
messages: messagesReducer, messages: messagesReducer,
project, project,
users: usersReducer, users: usersReducer,
}); });
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@ -24,7 +24,7 @@ export function configureStore(initialState = {}) {
composeEnhancers( composeEnhancers(
applyMiddleware(sagaMiddleware) applyMiddleware(sagaMiddleware)
) )
) );
sagaMiddleware.run(rootSaga); sagaMiddleware.run(rootSaga);
return store; return store;
} }