Start refactor of order form

This commit is contained in:
火焚 富良 2023-12-12 18:21:09 +08:00 committed by Egor Savkin
parent 6b92bf9145
commit 59f726e805
21 changed files with 234 additions and 241 deletions

View File

@ -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";
/**

View File

@ -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";

View File

@ -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";

View File

@ -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() {

View File

@ -1,5 +1,5 @@
export function ImportJSON() {
return (
<div> Import JSON BAOBAO</div>
<div> Import JSON PLACEHOLDER</div>
)
}

View File

@ -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>
);
}
}

View File

@ -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";
/**

View File

@ -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";
/**

View File

@ -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";
/**

View File

@ -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";
/**

View File

@ -0,0 +1,5 @@
export function ShowJSON() {
return (
<div> SHOW JSON PLACEHOLDER</div>
)
}

View 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)
}

View File

@ -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);

View File

@ -1,5 +1,5 @@
import React, {Component} from "react";
import {Tip} from "./Tip.jsx";
import {Tip} from "./Tip";
class Line extends Component {
constructor(props) {

View File

@ -1,5 +1,5 @@
import React, {Component} from "react";
import {Tip} from "./Tip.jsx";
import {Tip} from "./Tip";
class Radio extends Component {
constructor(props) {

View File

@ -1,5 +1,5 @@
import React, {Component} from "react";
import {Tip} from "./Tip.jsx";
import {Tip} from "./Tip";
class Switch extends Component {
constructor(props) {

View File

@ -1,5 +1,5 @@
import React, {Component} from "react";
import {Tip} from "./Tip.jsx";
import {Tip} from "./Tip";
class SwitchLine extends Component {
constructor(props) {

View File

@ -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,

View File

@ -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) => ({

View 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;
}

View File

@ -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"
};