UserForm: ajout retour de validation d'API

This commit is contained in:
wpetit 2020-03-09 11:32:21 +01:00
parent de6832e94f
commit 67732aa00a
11 changed files with 163 additions and 19 deletions

View File

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

View File

@ -4,7 +4,6 @@ import { WithForm } from './WithForm';
export function UserForm({ form, onSubmit }) {
useEffect(() => {
console.log(form.valid, form.submitted);
if (form.valid && form.submitted) onSubmit(form.data);
}, [form.valid, form.submitted]);

View File

@ -4,10 +4,10 @@ export function WithForm(validators) {
return function(Component) {
return function WithFormWrapper(props) {
return function WithFormWrapper({ errors, ...props }) {
const [ formData, setFormData ] = useState({});
const [ formErrors, setFormErrors ] = useState({});
const [ formErrors, setFormErrors ] = useState(errors || {});
const [ submitted, setSubmitted ] = useState(false);
const validateField = (name) => {
@ -27,6 +27,10 @@ export function WithForm(validators) {
setSubmitted(false);
}, [submitted]);
useEffect(() => {
setFormErrors(errors || {});
}, [errors]);
const form = {
field: (name, defaultValue) => {
return name in formData ? formData[name] : defaultValue;

View File

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

View File

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

View File

@ -0,0 +1,24 @@
import { CREATE_USER_FAILURE } from "../actions/user.actions";
const initialState = {
byId: {},
createUserForm: { errorCode: null },
};
export function usersReducer(state = initialState, action) {
switch(action.type) {
case CREATE_USER_FAILURE:
return handleCreateUserFailure(state, action);
};
return state;
}
function handleCreateUserFailure(state, { error }) {
return {
...state,
createUserForm: {
...state.createUserForm,
errorCode: error.code,
}
};
}

View File

@ -2,11 +2,13 @@ import { all } from 'redux-saga/effects';
import { rootSaga as authRootSaga } from './auth/root.saga';
import { rootSaga as failureRootSaga } from './failure/root.saga';
import { rootSaga as initRootSaga } from './init/root.saga';
import { rootSaga as userRootSaga } from './user/root.saga';
export default function* rootSaga() {
yield all([
initRootSaga(),
authRootSaga(),
failureRootSaga(),
userRootSaga()
]);
}

View File

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

View File

@ -0,0 +1,17 @@
import { call, put } from 'redux-saga/effects';
import { APIClient } from '../../services/api-client.service';
import { createUserFailure, createUserSuccess } from '../../actions/user.actions';
export function* createUserSaga({username, password}) {
const client = new APIClient();
let user;
try {
user = yield call(client.createUser, username, password);
} catch(err) {
console.error(err);
yield put(createUserFailure(err));
return
}
yield put(createUserSuccess(user));
}

View File

@ -1,4 +1,5 @@
import { UnauthorizedError } from '../errors/unauthorized.error.js';
import { APIError } from '../errors/api.error.js';
export const UnauthorizedStatusCode = 401;
@ -10,10 +11,12 @@ export class APIClient {
this.retrieveSessionUser = this.retrieveSessionUser.bind(this);
this.listUsers = this.listUsers.bind(this);
this.listRequests = this.listRequests.bind(this);
this.createUser = this.createUser.bind(this);
}
login(username, password) {
return this._callAPI('/login', { username, password }, 'POST')
return this._callAPI('/login', { username, password }, 'POST')
.then(result => result.data);
}
logout() {
@ -21,7 +24,8 @@ export class APIClient {
}
retrieveSessionUser() {
return this._callAPI('/me')
return this._callAPI('/me')
.then(result => result.data);
}
listUsers() {
@ -36,6 +40,12 @@ export class APIClient {
}
createUser(username, password) {
return this._callAPI('/users', { username, password }, 'POST')
.then(this._withAPIErrorMiddleware('create_user'))
.then(result => result.data);
}
updateRequestStatus(reqID, newStatus) {
}
@ -50,13 +60,27 @@ export class APIClient {
credentials: 'include',
body: JSON.stringify(body),
})
.then(res => {
if (res.status === UnauthorizedStatusCode) {
throw new UnauthorizedError();
}
return res;
})
.then(this._withUnauthorizedErrorMiddleware())
.then(res => res.json())
}
_withUnauthorizedErrorMiddleware() {
return res => {
if (res.status === UnauthorizedStatusCode) {
throw new UnauthorizedError();
}
return res;
}
}
_withAPIErrorMiddleware(endpoint) {
return result => {
if (result.error) {
const { code, message, data } = result.error;
throw new APIError(endpoint, code, message, data);
}
return result;
}
}
}

View File

@ -4,13 +4,15 @@ import rootSaga from '../sagas/root'
import { sessionReducer } from '../reducers/session.reducers';
import { messagesReducer } from '../reducers/messages.reducers';
import project from '../reducers/project'
import { usersReducer } from '../reducers/users.reducers';
const sagaMiddleware = createSagaMiddleware()
const rootReducer = combineReducers({
session: sessionReducer,
messages: messagesReducer,
project
project,
users: usersReducer,
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;