import * as _ from "lodash";
import FilterBuilder from "./filter-builder";
import OrderBuilder from "./order-builder";
-import { AxiosInstance } from "axios";
+import { AxiosInstance, AxiosPromise } from "axios";
import { Resource } from "../../models/resource";
export interface ListArguments {
itemsAvailable: number;
}
+export interface Errors {
+ errors: string[];
+ errorToken: string;
+}
+
export default class CommonResourceService<T extends Resource> {
static mapResponseKeys = (response: any): Promise<any> =>
}
}
+ static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
+ return promise
+ .then(CommonResourceService.mapResponseKeys)
+ .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
+ }
+
protected serverApi: AxiosInstance;
protected resourceType: string;
}
create(data: Partial<T>) {
- return this.serverApi
- .post<T>(this.resourceType, CommonResourceService.mapKeys(_.snakeCase)(data))
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .post<T>(this.resourceType, CommonResourceService.mapKeys(_.snakeCase)(data)));
}
delete(uuid: string): Promise<T> {
- return this.serverApi
- .delete(this.resourceType + uuid)
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .delete(this.resourceType + uuid));
}
get(uuid: string) {
- return this.serverApi
- .get<T>(this.resourceType + uuid)
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .get<T>(this.resourceType + uuid));
}
list(args: ListArguments = {}): Promise<ListResults<T>> {
filters: filters ? filters.serialize() : undefined,
order: order ? order.getOrder() : undefined
};
- return this.serverApi
- .get(this.resourceType, {
- params: CommonResourceService.mapKeys(_.snakeCase)(params)
- })
- .then(CommonResourceService.mapResponseKeys);
+ return CommonResourceService.defaultResponse(
+ this.serverApi
+ .get(this.resourceType, {
+ params: CommonResourceService.mapKeys(_.snakeCase)(params)
+ }));
}
update(uuid: string) {
return projectService
.create(projectData)
.then(project => dispatch(actions.CREATE_PROJECT_SUCCESS(project)))
- .catch((response) => dispatch(actions.CREATE_PROJECT_ERROR(response.response.data.errors)));
+ .catch(errors => dispatch(actions.CREATE_PROJECT_ERROR(errors.errors.join(''))));
};
export type ProjectAction = UnionOf<typeof actions>;
opened: boolean;
pending: boolean;
ownerUuid: string;
+ error?: string;
}
export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
return actions.match(action, {
- OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }),
+ OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }),
CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
- CREATE_PROJECT: () => updateCreator(state, { pending: true }),
+ CREATE_PROJECT: () => updateCreator(state, { pending: true, error: undefined }),
CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "", pending: false }),
- CREATE_PROJECT_ERROR: () => updateCreator(state, { ownerUuid: "", pending: false }),
+ CREATE_PROJECT_ERROR: error => updateCreator(state, { pending: false, error }),
REMOVE_PROJECT: () => state,
PROJECTS_REQUEST: itemId => {
const items = _.cloneDeep(state.items);
onChange: (isValid: boolean | string) => void;
render: (hasError: boolean) => React.ReactElement<any>;
isRequired: boolean;
+ duplicatedName?: string;
};
interface ValidatorState {
- isPatternValid: boolean;
isLengthValid: boolean;
}
-const nameRegEx = /^[a-zA-Z0-9-_ ]+$/;
-const maxInputLength = 60;
-
class Validator extends React.Component<ValidatorProps & WithStyles<CssRules>> {
state: ValidatorState = {
- isPatternValid: true,
isLengthValid: true
};
if (this.props.value !== value) {
this.setState({
- isPatternValid: value.match(nameRegEx),
- isLengthValid: value.length < maxInputLength
+ isLengthValid: value.length < MAX_INPUT_LENGTH
}, () => this.onChange());
}
}
onChange() {
const { value, onChange, isRequired } = this.props;
- const { isPatternValid, isLengthValid } = this.state;
- const isValid = value && isPatternValid && isLengthValid && (isRequired || (!isRequired && value.length > 0));
+ const { isLengthValid } = this.state;
+ const isValid = value && isLengthValid && (isRequired || (!isRequired && value.length > 0));
onChange(isValid);
}
render() {
- const { classes, isRequired, value } = this.props;
- const { isPatternValid, isLengthValid } = this.state;
+ const { classes, isRequired, value, duplicatedName } = this.props;
+ const { isLengthValid } = this.state;
return (
<span>
- {this.props.render(!(isPatternValid && isLengthValid) && (isRequired || (!isRequired && value.length > 0)))}
- {!isPatternValid && (isRequired || (!isRequired && value.length > 0)) ? <span className={classes.formInputError}>This field allow only alphanumeric characters, dashes, spaces and underscores.<br /></span> : null}
- {!isLengthValid ? <span className={classes.formInputError}>This field should have max 60 characters.</span> : null}
+ {this.props.render(!isLengthValid && (isRequired || (!isRequired && value.length > 0)))}
+ {!isLengthValid ? <span className={classes.formInputError}>This field should have max 255 characters.</span> : null}
+ {duplicatedName ? <span className={classes.formInputError}>Project with this name already exists</span> : null}
</span>
);
}
}
+const MAX_INPUT_LENGTH = 255;
+
type CssRules = "formInputError";
const styles: StyleRulesCallback<CssRules> = theme => ({
const mapStateToProps = (state: RootState) => ({
open: state.projects.creator.opened,
pending: state.projects.creator.pending,
+ error: state.projects.creator.error
});
const submit = (data: { name: string, description: string }) =>
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
-import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
+import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress, Typography } from '@material-ui/core';
import Validator from '../../utils/dialog-validator';
interface ProjectCreateProps {
open: boolean;
pending: boolean;
+ error: string;
handleClose: () => void;
onSubmit: (data: { name: string, description: string }) => void;
}
description: string;
isNameValid: boolean;
isDescriptionValid: boolean;
+ duplicatedName: string;
}
class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyles<CssRules>> {
name: '',
description: '',
isNameValid: false,
- isDescriptionValid: true
+ isDescriptionValid: true,
+ duplicatedName: ''
};
+ componentWillReceiveProps(nextProps: ProjectCreateProps) {
+ const { error } = nextProps;
+
+ if (this.props.error !== error) {
+ this.setState({
+ duplicatedName: error
+ });
+ }
+ }
+
render() {
- const { name, description, isNameValid, isDescriptionValid } = this.state;
+ const { name, description, isNameValid, isDescriptionValid, duplicatedName } = this.state;
const { classes, open, handleClose, pending } = this.props;
return (
value={name}
onChange={e => this.isNameValid(e)}
isRequired={true}
+ duplicatedName={duplicatedName}
render={hasError =>
<TextField
margin="dense"
id="name"
onChange={e => this.handleProjectName(e)}
label="Project name"
- error={hasError}
+ error={hasError || !!duplicatedName}
fullWidth />} />
<Validator
value={description}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} className={classes.button} color="primary" disabled={pending}>CANCEL</Button>
- <Button onClick={this.handleSubmit}
- className={classes.lastButton}
- color="primary"
- disabled={!isNameValid || (!isDescriptionValid && description.length > 0) || pending}
+ <Button onClick={this.handleSubmit}
+ className={classes.lastButton}
+ color="primary"
+ disabled={!isNameValid || (!isDescriptionValid && description.length > 0) || pending}
variant="contained">
CREATE A PROJECT
- </Button>
- {pending && <CircularProgress size={20} className={classes.createProgress} />}
+ </Button>
+ {pending && <CircularProgress size={20} className={classes.createProgress} />}
</DialogActions>
</div>
</Dialog>
handleProjectName(e: any) {
this.setState({
name: e.target.value,
+ duplicatedName: ''
});
}