Skip to content

Instantly share code, notes, and snippets.

@lyquangthai1993
Created December 25, 2018 16:11
Show Gist options
  • Save lyquangthai1993/2b85d916a89b1f56e49ac06297708d8c to your computer and use it in GitHub Desktop.
Save lyquangthai1993/2b85d916a89b1f56e49ac06297708d8c to your computer and use it in GitHub Desktop.
Demo friends in service
/**
*
* ServiceEditPage
*
*/
//Lib
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { compose } from "redux";
import { Formik, Form, Field, FieldArray, getIn, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { WithContext as ReactTags } from 'react-tag-input';
import _ from "lodash";
//Saga, action, reducer, selector
import injectSaga from "utils/injectSaga";
import injectReducer from "utils/injectReducer";
import makeSelectServiceEditPage from "./selectors";
import reducer from "./reducer";
import saga from "./saga";
import {
getServiceDetails, updateService,
addCoupon, updateCoupon,
addCert, updateCert,
addArea, updateArea,
addTag, deleteTag,
changeImage,
showModal
} from './actions';
import { loadRepos } from '../App/actions';
//css
import './style.scss';
//Components
import SubmitButton from 'components/SubmitButton';
import InputForm from "components/InputForm";
import Dropdown from 'components/Dropdown';
import GhostButton from 'components/GhostButton';
import PurpleRoundButton from 'components/PurpleRoundButton';
import moment from 'moment';
import { Modal, ModalBody, UncontrolledTooltip } from 'reactstrap';
import TagInput from "components/TagInput";
import CouponItem from "components/CouponItem";
import CropImage from 'components/CropImage';
import Datepicker from 'components/Datepicker';
const validateForm = Yup.object().shape({
serviceName: Yup.string()
.required('Please enter service name'),
hourlyRate: Yup.string()
.required('Please enter hourly rate'),
minimumHours: Yup.string()
.required('Please enter minimum hours'),
commission: Yup.string()
.required('Please enter commission'),
couponList: Yup.array()
.of(
Yup.object().shape({
couponName: Yup.string()
.required('Required'), // these constraints take precedence
couponCode: Yup.string()
.required('Required'), // these constraints take precedence
disCount: Yup.number()
.required('Required'), // these constraints take precedence
expiredAt: Yup.string()
.required('Required'), // these constraints take precedence
})
)
.min(1, 'Minimum of 1 coupon'),
// 'couponCode': Yup.string()
// .required('')
// .min(8, 'Coupon Code is at least 8 characters'),
// 'certificationName': Yup.string()
// .required('')
// .min(8, 'Certification Name is at least 8 characters')
// .max(30, 'Certification Name is maximum at 30 characters')
// .matches(/(^[a-zA-Z0-9]{4,10}$)/, 'Invalid certification name (No special characters)'),
// 'cityAndCountry': Yup.string()
// .required('')
});
/* eslint-disable react/prefer-stateless-function */
export class ServiceEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cropSrc: '',
showBackgroundModal: false
}
}
_disableButton(value, errors) {
//console.log(value);
console.log(errors);
//Loop through validation fields
const keys = [
'serviceName',
'hourlyRate',
'minimumHours',
'commission',
'couponList'
];
// for (let key of keys) {
// if (value[key] == null || errors[key] || !value[key].toString()) {
// //If this field has error or
// //console.log(key);
// return true;
// }
// }
return false;
}
componentWillMount() {
let url = window.location.href;
let temp = url.split('?id=');
let serviceID = temp[1];
this.props.getService(serviceID);
this.props.startLoad();
}
openFileBrowser = (type) => {
if (type == 'icon') {
this.refs.iconUploader.click();
}
else {
this.refs.backgroundUploader.click();
}
}
uploadImage = (e, type) => {
let file = e.target && e.target.files ? e.target.files.item(0) : null;
if (file) {
let reader = new FileReader();
reader.onloadend = (e) => {
if (type == 'icon') {
this.props.changeImage('icon', e.target.result);
this.setState({
iconFile: file,
});
}
else {
this.setState({
cropSrc: e.target.result
}, () => {
this.setState({ showBackgroundModal: true });
})
}
}
reader.readAsDataURL(file);
}
}
changeImage = (src, file) => {
this.props.changeImage('background', src);
this.setState({
cropSrc: '',
backgroundFile: file,
showBackgroundModal: false
});
}
closeModal = () => {
this.setState({
cropSrc: '',
showBackgroundModal: false,
});
}
handleTagsChange = (tagsList) => {
this.setState({ keywords: tagsList });
}
submitForm = (e) => {
const { couponCodes, certifications, areas, keywords, deletedKeywords } = this.props.serviceeditpage;
let formData = new FormData();
if (this.state.iconFile) {
formData.append('icon', this.state.iconFile);
}
if (this.state.backgroundFile) {
formData.append('background', this.state.backgroundFile);
}
formData.append('name', e.serviceName);
let rateHours = {
unit: "$",
value: e.hourlyRate
};
formData.append('rateHours', JSON.stringify(rateHours));
let minCharge = {
unit: "$",
value: e.hourlyRate * e.minimumHours
};
formData.append('minCharge', JSON.stringify(minCharge));
let minHours = {
unit: "",
value: e.minimumHours,
};
formData.append('minHours', JSON.stringify(minHours));
let commission = {
unit: "%",
value: e.commission
};
formData.append('commission', JSON.stringify(commission));
formData.append('couponCodes', JSON.stringify(couponCodes));
let array1 = keywords;
let newKeywords = keywords.concat(deletedKeywords);
let uploadKeywords = newKeywords.map((keyword, index) => {
return {
id: keyword.id,
keyName: keyword.text,
action: keyword.action
}
})
formData.append('keywords', JSON.stringify(uploadKeywords));
formData.append('certifications', JSON.stringify(certifications));
let newAreas = areas.map((area, index) => {
let suburbs = area.suburbs.split(',');
return {
...area,
suburbs: suburbs,
}
});
formData.append('areas', JSON.stringify(newAreas));
this.setState({ updateData: formData }, () => {
this.props.showModal('confirm', true);
})
}
onChangeCouponCode = (coupon, type, value) => {
let newCoupon = coupon;
newCoupon[type] = value;
if (coupon.id === '') {
newCoupon['action'] = 'add';
}
else {
newCoupon['action'] = 'edit';
}
this.props.updateCoupon(coupon.id, newCoupon);
}
render() {
const {
serviceDetails, couponCodes, errors, keywords, certifications, areas,
backgroundSrc, iconSrc, showConfirmModal
} = this.props.serviceeditpage;
let apiErrors = errors;
return (
<div className="edit-service-profile">
<div className="header-edit-add-page edit-service-header">
<div className="action">
<div className="return" id='return'>
<span className="icon-arrow-left" onClick={e => { this.props.history.goBack(); }}></span>
</div>
<UncontrolledTooltip className="fixle-tooltip" placement="bottom" target="return">
Back</UncontrolledTooltip>
</div>
<div className="title">
<span>Edit Service</span>
</div>
</div>
<input type="file" id="file" ref="iconUploader" style={{ display: "none" }} onChange={evt => {
this.uploadImage(evt, 'icon');
evt.target.value = null;
}} />
<input type="file" id="file" ref="backgroundUploader" style={{ display: "none" }} onChange={evt => {
this.uploadImage(evt, 'background');
evt.target.value = null;
}} />
<Formik ref={ref => { this.formik = ref }}
initialValues={{
serviceName: serviceDetails ? serviceDetails.name : '',
hourlyRate: serviceDetails ? serviceDetails.rateHours.value : '',
minimumHours: serviceDetails ? serviceDetails.minHours.value : '',
minimumCharge: serviceDetails ? serviceDetails.minCharge.value : '',
commission: serviceDetails ? serviceDetails.commission.value : '',
couponList: serviceDetails ? couponCodes : [],
}}
enableReinitialize={true}
validationSchema={validateForm}
onSubmit={e => {
this.submitForm(e);
}}>
{({
values,
dirty,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form onSubmit={handleSubmit}>
<div className="content-add-edit edit-service-content">
<div className="information service-info">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Service Information</span>
</div>
</div>
<div className="col-md-5 service-details">
<div className="details-wrapper">
<div className="service-icon">
<div className="icon-wrapper">
<img className="icon-uploaded" src={iconSrc} alt="icon" />
</div>
<span className="change-avatar add-icon" onClick={e => { this.openFileBrowser('icon') }}>Add Service Icon</span>
</div>
<div className="details">
{/* SERVICE NAME */}
<InputForm
label="service name"
name='serviceName'
value={values.serviceName}
error={errors.serviceName}
touched={touched.serviceName}
onChange={evt => {
handleChange(evt);
}}
onBlur={handleBlur}
placeholder={'Service name'} />
{/* HOURLY RATE */}
<InputForm
label="hourly rate"
name={'hourlyRate'}
value={values.hourlyRate}
error={errors.hourlyRate}
touched={touched.hourlyRate}
onChange={evt => {
handleChange(evt);
}}
onBlur={handleBlur}
placeholder={'Hourly rate'}
type={'number'} />
{/* MINIMUM HOURS */}
<InputForm
label="minimum hours"
name={'minimumHours'}
value={values.minimumHours}
error={errors.minimumHours}
touched={touched.minimumHours}
onChange={evt => {
handleChange(evt);
}}
onBlur={handleBlur}
placeholder={'Minimum hours'}
type={'number'} />
{/* MINIMUM CHARGE */}
<InputForm
label="minimum charge"
name={'minimumCharge'}
value={(values.minimumHours === '' || values.hourlyRate === '') ? '' : values.minimumHours * values.hourlyRate}
error={errors.minimumCharge}
touched={touched.minimumCharge}
onChange={evt => {
handleChange(evt);
}}
onBlur={handleBlur}
placeholder={'Minimum hours x hourly rate'} />
{(apiErrors && apiErrors.length > 0) ? apiErrors.map((error, index) => {
return (
<div key={error.errorCode} className="errors">
<span className="icon-error"></span>
<div className="error-item">
<span>{error.errorMessage}</span>
</div>
</div>
);
}) : []}
</div>
</div>
</div>
<div className="col-md-3 service-background">
<div className="background-wrapper">
<div className="title">
<span>Background Image</span>
</div>
<div className="background-container">
<img className="background-uploaded" src={backgroundSrc} alt="icon" />
</div>
<div className="upload-background">
<span onClick={e => { this.openFileBrowser('background'); }}>Change Bg Image</span>
</div>
</div>
</div>
</div>
</div>
<div className="information fixle-charge">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Fixle Charge</span>
</div>
</div>
<div className="col-md-8 right">
<div className="details">
{/* COMMISSION */}
<InputForm
label="commission per booking"
name={'commission'}
type='number'
value={values.commission}
error={errors.commission}
touched={touched.commission}
onChange={evt => {
handleChange(evt);
}}
onBlur={handleBlur}
placeholder={'Commission per booking'}
type={'number'} />
</div>
</div>
</div>
</div>
<div className="information coupon-code">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Coupon Code</span>
</div>
</div>
<div className="col-md-8 right">
{values.couponList.map((coupon, index) => {
return (
// <Field
// key={index}
// name={`couponList.${index}`}
// component={CouponItem}
// onChange={evt => {
// handleChange(evt);
// }}
// {
// ...{
// index: index,
// nameInput: `couponList[${index}]`,
// couponName: coupon.name,
// couponCode: coupon.code,
// disCountType: coupon.disCountType,
// disCount: coupon.disCount,
// expiredAt: coupon.expiredAt,
// hidden: coupon.action === 'delete',
// onHandleChange: (id, type, value) => {
// let newCoupon = coupon;
// newCoupon[type] = value;
// if (coupon.id === '') {
// newCoupon['action'] = 'add';
// }
// else {
// newCoupon['action'] = 'edit';
// }
// this.props.updateCoupon(id, newCoupon);
// },
// delete: (id) => {
// let newCoupon = coupon;
// coupon['action'] = 'delete';
// this.props.updateCoupon(id, newCoupon);
// }
// }}
// />
<CouponItem
name={`couponList[${index}]`}
key={index}
index={index}
couponName={coupon.name}
couponCode={coupon.code}
disCount={coupon.disCount}
disCountType={coupon.disCountType}
expiryDate={coupon.expiredAt}
onChange={evt => {
handleChange(evt);
}}
onHandleChange={(id, type, value) => {
let newCoupon = coupon;
newCoupon[type] = value;
if (coupon.id === '') {
newCoupon['action'] = 'add';
}
else {
newCoupon['action'] = 'edit';
}
this.props.updateCoupon(id, newCoupon);
}}
delete={id => {
let newCoupon = coupon;
coupon['action'] = 'delete';
this.props.updateCoupon(id, newCoupon);
}}
hidden={coupon.action === 'delete'}
value={values.commission}
/>
)
})}
<div className="row addition-action">
<PurpleRoundButton
title={'Add Coupon'}
className="btn-purple-round add-coupon"
type="button"
onClick={e => { this.props.addCoupon(); }}
/>
</div>
</div>
</div>
</div>
{/* KEYWORDS */}
<div className="information keywords">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Keywords</span>
</div>
</div>
<div className="col-md-8 right">
<div className="details">
<div className="info-item">
<div className="title">
<span>Keywords</span>
</div>
<div className="data list-tags">
<ReactTags
autofocus={false}
tags={keywords}
handleDelete={index => {
this.props.deleteTag(index);
}}
handleAddition={tag => {
tag.id = '';
tag['action'] = 'add';
this.props.addTag(tag);
}}
placeholder={'Keywords'}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="information certification">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Certification Required</span>
</div>
</div>
<div className="col-md-8 right">
{certifications.map((cert, index) => (
<div key={cert.id} className="certification-item row" hidden={cert.action === 'delete'}>
<div className="col-md-10">
<InputForm
label="certification name"
value={cert.content}
onChange={evt => {
let newItem = cert;
newItem.content = evt.target.value;
if (cert.id === '') {
newItem.action = 'add';
}
else {
newItem.action = 'edit';
}
this.props.updateCert(index, newItem);
}}
placeholder={'Certification name'} />
</div>
<div className="col-md-2">
<div className="delete-tab">
<span className="icon-bin" onClick={e => {
let newItem = cert;
newItem.action = 'delete';
this.props.updateCert(index, newItem);
}}></span>
</div>
</div>
</div>
))}
<div className="row addition-action">
<PurpleRoundButton title={'Add Certification'} className="btn-purple-round add-certification" onClick={e => { this.props.addCert(); }} type="button" />
</div>
</div>
</div>
</div>
<div className="information areas-supplied">
<div className="row">
<div className="col-md-4 left">
<div className="title">
<span>Areas supplied</span>
</div>
</div>
<div className="col-md-8 right">
{areas.map((area, index) => {
return (
<div key={index} className="area-item row">
<div className="col-md-10">
<InputForm
label="city and country"
value={area.country}
onChange={evt => {
let newItem = area;
newItem.city = evt.target.value;
newItem.country = evt.target.value;
if (area.id === '') {
newItem.action = 'add';
}
else {
newItem.action = 'edit';
}
this.props.updateArea(index, newItem);
}}
placeholder={'City and country'} />
<InputForm
label="suburbs"
value={area.suburbs}
onChange={evt => {
let newItem = area;
newItem.suburbs = evt.target.value;
if (area.id === '') {
newItem.action = 'add';
}
else {
newItem.action = 'edit';
}
this.props.updateArea(index, newItem);
}}
placeholder={'Suburbs'} />
</div>
<div className="col-md-2">
<div className="delete-tab">
<span className="icon-bin" onClick={e => {
let newItem = area;
newItem.action = 'delete';
this.props.updateArea(index, newItem);
}}></span>
</div>
</div>
</div>
)
})}
<div className="row addition-action">
<PurpleRoundButton title={'Add Area'} className="btn-purple-round add-area" onClick={e => { this.props.addArea(); }} type="button" />
</div>
</div>
</div>
</div>
</div>
<div className="footer edit-service-footer">
<GhostButton className="btn-ghost cancel" title={'Cancel'} onClick={e => { this.props.history.goBack(); }} type="button" />
<SubmitButton type={'submit'} disabled={this._disableButton(values, errors)} content={'Save'} />
</div>
</Form>
)}
</Formik>
{(this.state.cropSrc && this.state.cropSrc != '') &&
<CropImage
show={this.state.showBackgroundModal}
cropSrc={this.state.cropSrc}
closeModal={this.closeModal}
changeImage={this.changeImage}
/>
}
<Modal isOpen={showConfirmModal} className="logout-modal">
<ModalBody>
<div className="add-success">
<div className="upper">
<div className="title">
<span>Save This Change</span>
</div>
<div className="description">
<span>Are you sure want to save this change. This action could influence on all data involved.</span>
</div>
</div>
<div className="lower">
<GhostButton className="btn-ghost cancel" title={'Cancel'} onClick={e => { this.props.showModal('confirm', false); }} />
<PurpleRoundButton className="btn-purple-round save" title={'Save'} onClick={e => {
if (this.state.updateData) {
this.props.showModal('confirm', false);
this.props.update(serviceDetails._id, this.state.updateData);
}
}} />
</div>
</div>
</ModalBody>
</Modal>
</div>
);
}
}
ServiceEditPage.propTypes = {
dispatch: PropTypes.func,
getService: PropTypes.func,
update: PropTypes.func,
addCoupon: PropTypes.func,
updateCoupon: PropTypes.func,
addCert: PropTypes.func,
updateCert: PropTypes.func,
addArea: PropTypes.func,
updateArea: PropTypes.func,
changeImage: PropTypes.func,
startLoad: PropTypes.func,
addTag: PropTypes.func,
deleteTag: PropTypes.func,
showModal: PropTypes.func
};
const mapStateToProps = createStructuredSelector({
serviceeditpage: makeSelectServiceEditPage()
});
function mapDispatchToProps(dispatch) {
return {
getService: (id) => {
dispatch(getServiceDetails(id));
},
update: (id, updateData) => {
dispatch(updateService(id, updateData));
},
addCoupon: () => {
dispatch(addCoupon());
},
updateCoupon: (id, newItem) => {
dispatch(updateCoupon(id, newItem));
},
addCert: () => {
dispatch(addCert());
},
updateCert: (id, newItem) => {
dispatch(updateCert(id, newItem));
},
addArea: () => {
dispatch(addArea());
},
updateArea: (id, newItem) => {
dispatch(updateArea(id, newItem));
},
changeImage: (objectType, src) => {
dispatch(changeImage(objectType, src));
},
startLoad: () => {
dispatch(loadRepos());
},
addTag: (newTag) => {
dispatch(addTag(newTag));
},
deleteTag: (id) => {
dispatch(deleteTag(id));
},
showModal: (modal, value) => {
dispatch(showModal(modal, value));
}
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps
);
const withReducer = injectReducer({ key: "serviceEditPage", reducer });
const withSaga = injectSaga({ key: "serviceEditPage", saga });
export default compose(
withReducer,
withSaga,
withConnect
)(ServiceEditPage);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment