Start refactor of order form
This commit is contained in:
parent
6b92bf9145
commit
59f726e805
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {Droppable} from "@hello-pangea/dnd";
|
||||
import {ProductItem} from "./ProductItem.jsx";
|
||||
import {ProductItem} from "./ProductItem";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import {Droppable} from "@hello-pangea/dnd";
|
||||
import {cartStyle} from "./utils";
|
||||
import {ProductCartItem} from "./ProductCartItem.jsx";
|
||||
import {FakePlaceholder} from "./FakePlaceholder.jsx";
|
||||
import {ProductCartItem} from "./ProductCartItem";
|
||||
import {FakePlaceholder} from "./FakePlaceholder";
|
||||
import {FillExtData} from "./options/utils";
|
||||
import {hp_to_slots, resource_counters} from "./count_resources";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {Cart} from "./Cart.jsx";
|
||||
import {CrateMode} from "./CrateMode.jsx";
|
||||
import {CrateWarnings} from "./CrateWarnings.jsx";
|
||||
import {Cart} from "./Cart";
|
||||
import {CrateMode} from "./CrateMode";
|
||||
import {CrateWarnings} from "./CrateWarnings";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {TriggerCrateWarnings} from "./warnings";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import {Accordion} from "react-bootstrap";
|
||||
import {Crate} from "./Crate.jsx";
|
||||
import {Crate} from "./Crate";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
export function CrateList() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function ImportJSON() {
|
||||
return (
|
||||
<div> Import JSON BAOBAO</div>
|
||||
<div> Import JSON PLACEHOLDER</div>
|
||||
)
|
||||
}
|
@ -1,220 +1,78 @@
|
||||
import React, {PureComponent} from 'react'
|
||||
import PropTypes from "prop-types";
|
||||
import React from 'react'
|
||||
import {validateEmail, Validation} from "./validate.js";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
* Components that renders the form to request quote.
|
||||
*/
|
||||
export class OrderForm extends PureComponent {
|
||||
export function OrderForm() {
|
||||
const {
|
||||
email,
|
||||
note,
|
||||
isProcessing,
|
||||
isProcessingComplete,
|
||||
showDescription,
|
||||
updateEmail,
|
||||
updateNote,
|
||||
submitForm,
|
||||
} = useShopStore(state => ({
|
||||
email: state.email,
|
||||
note: state.note,
|
||||
isProcessing: state.isProcessing,
|
||||
isProcessingComplete: state.isProcessingComplete,
|
||||
showDescription: state.showDescription,
|
||||
updateEmail: state.updateEmail,
|
||||
updateNote: state.updateNote,
|
||||
submitForm: state.submitForm,
|
||||
}));
|
||||
|
||||
static get propTypes() {
|
||||
return {
|
||||
isProcessing: PropTypes.bool,
|
||||
isProcessingComplete: PropTypes.bool,
|
||||
onClickSubmit: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
note: '',
|
||||
email: '',
|
||||
error: {
|
||||
note: null,
|
||||
email: null,
|
||||
},
|
||||
empty: {
|
||||
note: null,
|
||||
email: null,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div className="summary-form">
|
||||
|
||||
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);
|
||||
}
|
||||
<form onSubmit={submitForm} noValidate>
|
||||
|
||||
checkValidation() {
|
||||
let isValid = true;
|
||||
let validationFields = {...this.state};
|
||||
<input
|
||||
className={`${email.error > 0 ? 'errorField' : ''}`}
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={(event) => updateEmail(event.target.value)}
|
||||
onBlur={(event) => updateEmail(event.target.value)}
|
||||
value={email.value}/>
|
||||
|
||||
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({
|
||||
...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) {
|
||||
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 {
|
||||
onClickShow,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
email,
|
||||
note,
|
||||
error,
|
||||
empty
|
||||
} = this.state;
|
||||
|
||||
const { isProcessing, isProcessingComplete } = this.props;
|
||||
|
||||
return (
|
||||
<div className="summary-form">
|
||||
|
||||
<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
|
||||
onChange={handleNote}
|
||||
defaultValue={note}
|
||||
rows="5"
|
||||
placeholder="Additional notes" />
|
||||
|
||||
<div className="d-flex flex-column flex-sm-row justify-content-between">
|
||||
<input
|
||||
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
|
||||
style={{'cursor': 'pointer', 'fontWeight': '700'}}
|
||||
defaultValue="Show JSON"
|
||||
onClick={onClickShow}
|
||||
readOnly={true} />
|
||||
|
||||
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
|
||||
{email.error === Validation.Empty ? (
|
||||
<div className="error">
|
||||
<small>Required</small>
|
||||
</div>
|
||||
{/*This will open an email window. Send the email to make your request.*/}
|
||||
</form>
|
||||
) : null}
|
||||
|
||||
{email.error === Validation.Invalid ? (
|
||||
<div className="error">
|
||||
<small>Your email is incomplete</small>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<textarea
|
||||
onChange={(event) => updateNote(event.target.value)}
|
||||
defaultValue={note.value}
|
||||
rows="5"
|
||||
placeholder="Additional notes"/>
|
||||
|
||||
<div className="d-flex flex-column flex-sm-row justify-content-between">
|
||||
<input
|
||||
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
|
||||
style={{'cursor': 'pointer', 'fontWeight': '700'}}
|
||||
defaultValue="Show JSON"
|
||||
onClick={showDescription}
|
||||
readOnly={true}/>
|
||||
|
||||
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit"
|
||||
value={`${isProcessing ? 'Processing ...' : 'Request quote'}`}/>
|
||||
</div>
|
||||
{/*This will open an email window. Send the email to make your request.*/}
|
||||
</form>
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {OrderSummary} from "./OrderSummary.jsx";
|
||||
import {OrderForm} from "./OrderForm.jsx";
|
||||
import {CrateList} from "./CrateList.jsx";
|
||||
import {OrderSummary} from "./OrderSummary";
|
||||
import {OrderForm} from "./OrderForm";
|
||||
import {CrateList} from "./CrateList";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {SummaryPopup} from "./options/SummaryPopup.jsx";
|
||||
import {SummaryPopup} from "./options/SummaryPopup";
|
||||
import {formatMoney} from "./utils";
|
||||
import {WarningIndicator} from "./CardWarnings.jsx";
|
||||
import {WarningIndicator} from "./CardWarnings";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import {Draggable} from "@hello-pangea/dnd";
|
||||
import {DialogPopup} from "./options/DialogPopup.jsx";
|
||||
import {DialogPopup} from "./options/DialogPopup";
|
||||
import {productStyle} from "./utils";
|
||||
import {Resources} from "./Resources.jsx";
|
||||
import {CardWarnings} from "./CardWarnings.jsx";
|
||||
import {Resources} from "./Resources";
|
||||
import {CardWarnings} from "./CardWarnings";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,9 @@ import React, {useEffect} from 'react';
|
||||
import {DragDropContext} from "@hello-pangea/dnd";
|
||||
|
||||
|
||||
import {Layout} from "./Layout.jsx";
|
||||
import {Backlog} from "./Backlog.jsx";
|
||||
import {OrderPanel} from "./OrderPanel.jsx";
|
||||
import {Layout} from "./Layout";
|
||||
import {Backlog} from "./Backlog";
|
||||
import {OrderPanel} from "./OrderPanel";
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
|
5
static/js/shop/ShowJSON.jsx
Normal file
5
static/js/shop/ShowJSON.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
export function ShowJSON() {
|
||||
return (
|
||||
<div> SHOW JSON PLACEHOLDER</div>
|
||||
)
|
||||
}
|
47
static/js/shop/json_porter.js
Normal file
47
static/js/shop/json_porter.js
Normal file
@ -0,0 +1,47 @@
|
||||
import {useShopStore} from "./shop_store";
|
||||
|
||||
|
||||
export function validateJSON(description) {
|
||||
let crates_raw;
|
||||
try {
|
||||
crates_raw = JSON.parse(description);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
const crate_modes = useShopStore.getState().crate_modes;
|
||||
const pn_to_card = useShopStore.getState().pn_to_cards;
|
||||
|
||||
for (const crate of crates_raw) {
|
||||
if (!crate.type || !crate.items || !(crate.type in crate_modes)) return false;
|
||||
for (const card of crate.items) {
|
||||
if (!(card.pn in pn_to_card)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function JSONToCrates(description) {
|
||||
const crates_raw = JSON.parse(description);
|
||||
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
||||
return Array.from(crates_raw.map((crate, c_i) => ({
|
||||
id: "crate" + c_i,
|
||||
crate_mode: crate.type,
|
||||
items: Array.from(crate.items.map((card, i) => ({
|
||||
...pn_to_card(card.pn),
|
||||
options_data: card.options
|
||||
}))),
|
||||
warnings: [],
|
||||
occupiedHP: 0,
|
||||
})));
|
||||
}
|
||||
|
||||
export function CratesToJSON(crates) {
|
||||
return JSON.stringify(Array.from(crates.map((crate, _i) => ({
|
||||
items: Array.from(crate.items.map((card, _) => ({
|
||||
pn: card.name_number,
|
||||
options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
|
||||
}))),
|
||||
type: crate.crate_mode
|
||||
}))), null, 2)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, {useState} from "react";
|
||||
import {useClickAway} from "./useClickAway";
|
||||
import {ProcessOptions} from "./Options.jsx";
|
||||
import {ProcessOptions} from "./Options";
|
||||
|
||||
export function DialogPopup({options, data, target, id, big, first, last, options_class}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip.jsx";
|
||||
import {Tip} from "./Tip";
|
||||
|
||||
class Line extends Component {
|
||||
constructor(props) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip.jsx";
|
||||
import {Tip} from "./Tip";
|
||||
|
||||
class Radio extends Component {
|
||||
constructor(props) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip.jsx";
|
||||
import {Tip} from "./Tip";
|
||||
|
||||
class Switch extends Component {
|
||||
constructor(props) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {Component} from "react";
|
||||
import {Tip} from "./Tip.jsx";
|
||||
import {Tip} from "./Tip";
|
||||
|
||||
class SwitchLine extends Component {
|
||||
constructor(props) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
import {LineWrapper} from "./Line.jsx";
|
||||
import {RadioWrapper} from "./Radio.jsx";
|
||||
import {SwitchWrapper} from "./Switch.jsx";
|
||||
import {SwitchLineWrapper} from "./SwitchLine.jsx";
|
||||
import {UnimplementedComponent} from "./UnimplementedComponent.jsx";
|
||||
import {LineWrapper} from "./Line";
|
||||
import {RadioWrapper} from "./Radio";
|
||||
import {SwitchWrapper} from "./Switch";
|
||||
import {SwitchLineWrapper} from "./SwitchLine";
|
||||
import {UnimplementedComponent} from "./UnimplementedComponent";
|
||||
|
||||
export const componentsList = {
|
||||
"Radio": RadioWrapper,
|
||||
|
@ -6,14 +6,24 @@ import {true_type_of} from "./options/utils";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {FillResources} from "./count_resources";
|
||||
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
||||
import {Validation, validateEmail, validateNote} from "./validate";
|
||||
import {CratesToJSON} from "./json_porter";
|
||||
|
||||
|
||||
const cards_to_pn_map = (cards) => {
|
||||
let result = {};
|
||||
Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key})
|
||||
return result;
|
||||
};
|
||||
|
||||
const useBacklog = ((set, get) => ({
|
||||
cards: shared_data.items,
|
||||
groups: shared_data.columns.backlog,
|
||||
cards_list: itemsUnfoldedList,
|
||||
currency: shared_data.currency,
|
||||
pn_to_cards: cards_to_pn_map(shared_data.items),
|
||||
getCardDescription: index => get().cards[get().cards_list[index]],
|
||||
getCardDescriptionByPn: pn => get().cards[get().pn_to_cards[pn]],
|
||||
cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
|
||||
}));
|
||||
|
||||
@ -48,6 +58,42 @@ const useSubmitForm = ((set, get) => ({
|
||||
shouldShowRFQFeedback: true,
|
||||
RFQBodyType: 'email',
|
||||
isProcessingComplete: true,
|
||||
email: {
|
||||
value: "",
|
||||
error: null
|
||||
},
|
||||
note: {
|
||||
value: "",
|
||||
error: Validation.OK
|
||||
},
|
||||
|
||||
description: "",
|
||||
shouldShowDescription: false,
|
||||
|
||||
updateEmail: (new_email) => set(state => ({
|
||||
email: {
|
||||
value: new_email,
|
||||
error: validateEmail(new_email)
|
||||
}
|
||||
})),
|
||||
updateNote: (new_notes) => set(state => ({
|
||||
note: {
|
||||
value: new_notes,
|
||||
error: validateNote(new_notes)
|
||||
}
|
||||
})),
|
||||
|
||||
updateDescription: () => set(state => ({
|
||||
description: CratesToJSON(state.crates)
|
||||
})),
|
||||
showDescription: () => set(state => ({
|
||||
description: CratesToJSON(state.crates),
|
||||
shouldShowDescription: true
|
||||
})),
|
||||
closeDescription: () => set(state => ({
|
||||
shouldShowDescription: false
|
||||
})),
|
||||
submitForm: () => set(state => ({})) // TODO
|
||||
}));
|
||||
|
||||
const useHighlighted = ((set, get) => ({
|
||||
|
34
static/js/shop/validate.js
Normal file
34
static/js/shop/validate.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
import {validateJSON} from "./json_porter";
|
||||
|
||||
export const Validation = {
|
||||
OK: 0,
|
||||
Empty: 1,
|
||||
Invalid: 2,
|
||||
};
|
||||
|
||||
export function validateEmail(value) {
|
||||
if (!value || value.trim() === '') {
|
||||
return Validation.Empty
|
||||
} else if (value && !value.match(/^\w+([\+\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) {
|
||||
return Validation.Invalid;
|
||||
}
|
||||
return Validation.OK;
|
||||
}
|
||||
|
||||
|
||||
export function validateNote(value) {
|
||||
if (!value || value.trim() === '') {
|
||||
return Validation.Empty
|
||||
}
|
||||
return Validation.OK;
|
||||
}
|
||||
|
||||
export function validateJSONInput(value) {
|
||||
if (!value || value.trim() === '') {
|
||||
return Validation.Empty
|
||||
} else if (value && !(validateJSON(value))) {
|
||||
return Validation.Invalid;
|
||||
}
|
||||
return Validation.OK;
|
||||
}
|
@ -17,8 +17,11 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
//devtool: "inline-source-map",
|
||||
//mode: "development"
|
||||
devtool: false,
|
||||
mode: "production"
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
mode: "development"
|
||||
//devtool: false,
|
||||
//mode: "production"
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user