feat(issue22/UI): Updates shop (email + req to API for RFQ + feeback)

This commit is contained in:
sovanna 2020-04-14 14:46:51 +09:00
parent 64730e8557
commit 0c4d2cfdac
7 changed files with 283 additions and 17 deletions

View File

@ -12,6 +12,36 @@ button {
.layout {
.rfqFeedback {
display: flex;
align-items: center;
padding: 2rem 3rem;
text-align: center;
position: absolute;
width: 350px;
background: white;
left: calc(100%/2 - 350px/2);
-webkit-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
box-shadow: 0px 0px 33px -7px rgba(0,0,0,0.75);
top: calc(50% - 50px);
border: 1px solid $brand-color;
font-size: .9rem;
button {
background-color: inherit;
align-self: center;
border: 0;
position: absolute;
right: 10px;
top: 10px;
img {
width: 15px;
}
}
}
display: flex;
> aside.aside {
@ -253,8 +283,9 @@ button {
textarea {
border: 1px solid $color-secondary;
border-radius: 3px;
margin: 0 0 1rem;
margin: 0 0 .5rem;
padding: .4rem;
outline: none;
}
input[type="submit"] {
@ -264,6 +295,16 @@ button {
padding: .7rem;
border: 0;
border-radius: 3px;
outline: none;
}
.error {
color: #e53e3e;
padding-bottom: 1em;
}
.errorField {
border: 1px solid #e53e3e !important;
}
}
}

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<g id="Group_441" data-name="Group 441" transform="translate(-1251 -346)">
<rect id="Rectangle_1020" data-name="Rectangle 1020" width="22" height="22" transform="translate(1251 346)" fill="none"/>
<rect id="Rectangle_1021" data-name="Rectangle 1021" width="2.4" height="24" transform="translate(1269.778 347.808) rotate(45)" fill="#715ec7"/>
<rect id="Rectangle_1022" data-name="Rectangle 1022" width="2.4" height="24" transform="translate(1271.192 364.778) rotate(135)" fill="#715ec7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 600 B

View File

@ -0,0 +1,4 @@
<svg id="done" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
<path id="Path_795" data-name="Path 795" d="M0,0H50V50H0Z" fill="none"/>
<path id="Path_796" data-name="Path 796" d="M22.833,2A20.833,20.833,0,1,0,43.667,22.833,20.841,20.841,0,0,0,22.833,2ZM18.667,33.25,8.25,22.833,11.187,19.9l7.479,7.458L34.479,11.542,37.417,14.5Z" transform="translate(2.167 2.167)" fill="#715ec7"/>
</svg>

After

Width:  |  Height:  |  Size: 425 B

3
static/js/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@ const {
const data = window.shop_data;
const axios = window.axios;
const productStyle = (style, snapshot, removeAnim, hovered, selected) => {
const custom = {
@ -140,6 +141,7 @@ class Layout extends React.PureComponent {
isMobile: PropTypes.bool,
newCardJustAdded: PropTypes.bool,
onClickToggleMobileSideMenu: PropTypes.func,
onClickCloseRFQFeedback: PropTypes.func,
};
}
@ -156,7 +158,9 @@ class Layout extends React.PureComponent {
mobileSideMenuShouldOpen,
isMobile,
newCardJustAdded,
onClickToggleMobileSideMenu
onClickToggleMobileSideMenu,
onClickCloseRFQFeedback,
showRFQFeedback,
} = this.props;
return (
@ -176,6 +180,23 @@ class Layout extends React.PureComponent {
</div>
) : null}
<div className="rfqFeedback" style={{'display': `${ showRFQFeedback ? 'flex' : 'none'}`}}>
<div>
<img width="30px" src="/images/shop/icon-done.svg" alt="close" />
</div>
<div style={{'padding': '0 .5em'}}>
We've received your request and will be in contact soon.
</div>
<div>
<button onClick={onClickCloseRFQFeedback}>
<img src="/images/shop/icon-close.svg" alt="close" />
</button>
</div>
</div>
</div>
);
}
@ -812,43 +833,197 @@ class OrderForm extends React.PureComponent {
static get propTypes() {
return {
isProcessing: PropTypes.bool,
isProcessingComplete: PropTypes.bool,
onClickSubmit: PropTypes.func,
};
}
constructor(props) {
super(props);
this.state = {note: ''};
this.state = {
note: '',
email: '',
error: {
note: null,
email: null,
},
empty: {
note: null,
email: null,
},
};
this.handleNoteChange = this.handleNoteChange.bind(this);
this.handleEmail = this.handleEmail.bind(this);
this.handleNote = this.handleNote.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.resetEmptyError = this.resetEmptyError.bind(this);
this.checkValidation = this.checkValidation.bind(this);
}
handleNoteChange(event) {
checkValidation() {
let isValid = true;
let validationFields = {...this.state};
const {
isEmpty: isEmailEmpty,
isError: isEmailError
} = this.validateEmail(this.state.email);
validationFields = {
...validationFields,
error: {
...this.state.error,
email: isEmailError,
},
empty: {
...this.state.empty,
email: isEmailEmpty,
}
}
this.setState(validationFields);
isValid =
!isEmailEmpty &&
!isEmailError
return isValid;
}
validateEmail(value) {
let isEmpty = null;
let isError = null;
const { t } = this.props;
if (!value || value.trim() === '') {
isEmpty = true;
} else if (value && !value.match(/^\w+([\+\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) {
isError = {
message: 'Your email is incomplete',
};
}
return { isEmpty, isError };
}
validateNote(value) {
let isEmpty = null;
if (!value || value.trim() === '') {
isEmpty = true;
}
return { isEmpty };
}
resetEmptyError(key) {
this.setState({
note: event.target.value,
...this.state,
error: {
...this.state.error,
[key]: null,
},
empty: {
...this.state.empty,
[key]: null,
},
});
}
handleEmail(e) {
const value = e.target.value;
const { isEmpty, isError } = this.validateEmail(value);
this.setState({
...this.state,
email: value,
error: {
...this.state.error,
email: isError,
},
empty: {
...this.state.empty,
email: isEmpty,
}
});
}
handleNote(e) {
const value = e.target.value;
this.setState({
...this.state,
note: value,
});
}
handleSubmit(event) {
if (this.props.onClickSubmit) {
this.props.onClickSubmit(this.state.note);
}
event.preventDefault();
if (this.props.onClickSubmit) {
// check validation input fields
const isValidated = this.checkValidation();
if (!isValidated) {
return false;
}
this.props.onClickSubmit(this.state.note, this.state.email);
}
}
render() {
const {
handleEmail,
handleNote,
resetEmptyError,
handleSubmit,
} = this;
const {
email,
note,
error,
empty
} = this.state;
const { isProcessing, isProcessingComplete } = this.props;
return (
<div className="summary-form">
<form onSubmit={this.handleSubmit}>
<form onSubmit={handleSubmit} noValidate>
<input
className={`${error && error.email ? 'errorField':''}`}
type="email"
placeholder="Email"
onFocus={() => resetEmptyError('email')}
onChange={handleEmail}
onBlur={handleEmail}
value={email} />
{ empty && empty.email ? (
<div className="error">
<small>Required</small>
</div>
) : null}
{ error && error.email ? (
<div className="error">
<small>Your email is incomplete</small>
</div>
) : null}
<textarea
value={this.state.note}
onChange={this.handleNoteChange}
onChange={handleNote}
value={note}
rows="5"
placeholder="Additional notes" />
<input type="submit" value="Request quote" />
This will open an email window. Send the email to make your request.
<input style={{'backgroundColor': `${isProcessingComplete ? 'gray':'#715ec7'}`}} disabled={isProcessingComplete} type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
{/*This will open an email window. Send the email to make your request.*/}
</form>
</div>
@ -1174,6 +1349,7 @@ class Shop extends React.PureComponent {
this.handleClickSubmit = this.handleClickSubmit.bind(this);
this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
this.handleClickToggleMobileSideMenu = this.handleClickToggleMobileSideMenu.bind(this);
this.handleClickCloseRFQFeedback = this.handleClickCloseRFQFeedback.bind(this);
this.timer = null;
}
@ -1375,7 +1551,7 @@ class Shop extends React.PureComponent {
});
}
handleClickSubmit(note) {
handleClickSubmit(note, email) {
const crate = {
items: [],
type: this.state.currentMode,
@ -1388,15 +1564,33 @@ class Shop extends React.PureComponent {
});
}
const {data} = this.props;
const a = document.createElement('a');
const num = (new Date()).getTime();
const subject = `[Order hardware] - Request Quote`;
let body = `Hello!\n\nI would like to request a quotation for my below configuration:\n\n${JSON.stringify(crate)}\n\n(Please do not edit the machine-readable representation above)\n\n`;
let body = `Hello!<br><br>I would like to request a quotation for my below configuration:<br><br>${JSON.stringify(crate)}<br><br>(Please do not edit the machine-readable representation above)<br><br>`;
if (note) {
body = `${body}\n\nAdditional note:\n\n${note ? note.trim() : ''}`;
body = `${body}<br><br>Additional note:<br><br>${note ? note.trim() : ''}`;
}
this.setState({isProcessing: true});
axios.post(data.API_RFQ, {
email,
body,
headers: {'X-MLABS-OH': 'rlebcleu'}
}).then(response => {
this.setState({isProcessing: false, shouldShowRFQFeedback: true, isProcessingComplete: true});
}).catch(err => {
this.setState({isProcessing: false}, () => {
alert("We cannot receive your request. Try using the export by coping the configuration and send it to us at sales[at]m-labs.hk");
});
})
return;
document.body.appendChild(a);
a.style = 'display: none';
@ -1484,6 +1678,12 @@ class Shop extends React.PureComponent {
});
}
handleClickCloseRFQFeedback() {
this.setState({
shouldShowRFQFeedback: false,
});
}
checkAlerts(prevItems, newItems) {
console.log('--- START CHECKING CRATE WARNING ---');
@ -1802,6 +2002,9 @@ class Shop extends React.PureComponent {
rules,
mobileSideMenuShouldOpen,
newCardJustAdded,
isProcessing,
shouldShowRFQFeedback,
isProcessingComplete,
} = this.state;
const isMobile = window.deviceIsMobile();
@ -1810,11 +2013,13 @@ class Shop extends React.PureComponent {
<DragDropContext onDragEnd={this.handleOnDragEnd}>
<Layout
showRFQFeedback={shouldShowRFQFeedback}
className="shop"
mobileSideMenuShouldOpen={mobileSideMenuShouldOpen}
isMobile={isMobile}
newCardJustAdded={newCardJustAdded}
onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
onClickCloseRFQFeedback={this.handleClickCloseRFQFeedback}
aside={
<Backlog
currency={currency}
@ -1871,6 +2076,9 @@ class Shop extends React.PureComponent {
}
form={
<OrderForm
isProcessingComplete={isProcessingComplete}
processingComplete={this.handleProcessingComplete}
isProcessing={isProcessing}
onClickSubmit={this.handleClickSubmit}>
</OrderForm>
}>

View File

@ -1,5 +1,7 @@
const shop_data = {
API_RFQ: 'http://127.0.0.1:5000/api/rfq',
mobileSideMenuShouldOpen: false,
currentItemHovered: null,
currentMode: 'rack',

View File

@ -67,6 +67,7 @@
<!-- v11.0.5 -->
<script src="{{ get_url(path='js/react-beautiful-dnd.min.js', cachebust=true) }}"></script>
<script src="{{ get_url(path='js/uuid_v4@latest.js', cachebust=true) }}"></script>
<script src="{{ get_url(path='js/axios.min.js', cachebust=true) }}"></script>
<!-- Load Data -->
<script src="{{ get_url(path='js/shop_data.js', cachebust=true) }}"></script>