forked from M-Labs/web2019
Restored all the functionality, but needs UI fixes
Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
59f726e805
commit
ff7eac97dc
|
@ -23,4 +23,4 @@ export function CrateMode({crate_index}) {
|
|||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,71 @@
|
|||
import {useShopStore} from "./shop_store";
|
||||
import {useClickAway} from "./options/useClickAway";
|
||||
import {Modal} from "react-bootstrap";
|
||||
import React from "react";
|
||||
import {Validation} from "./validate";
|
||||
|
||||
const JSONExample = JSON.stringify({
|
||||
"items": [{"pn": "1124"}, {"pn": "2118"}, {"pn": "2118"}, {"pn": "2128"}],
|
||||
"type": "desktop"
|
||||
});
|
||||
|
||||
export function ImportJSON() {
|
||||
return (
|
||||
<div> Import JSON PLACEHOLDER</div>
|
||||
)
|
||||
const {shouldShow, data, loadDescription, updateImportDescription, closeImport, showImport} = useShopStore(state => ({
|
||||
shouldShow: state.importShouldOpen,
|
||||
data: state.importValue,
|
||||
loadDescription: state.loadDescription,
|
||||
updateImportDescription: state.updateImportDescription,
|
||||
closeImport: state.closeImport,
|
||||
showImport: state.openImport,
|
||||
}));
|
||||
|
||||
const ref = useClickAway((e) => {
|
||||
if (e.type === "mousedown") // ignore touchstart
|
||||
closeImport()
|
||||
}
|
||||
);
|
||||
|
||||
return (<>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary m-0 mb-2"
|
||||
style={{'cursor': 'pointer'}}
|
||||
onClick={showImport}>Import JSON
|
||||
</button>
|
||||
<Modal show={shouldShow} animation={true} centered>
|
||||
<Modal.Body ref={ref}>
|
||||
<div className="form-group">
|
||||
<p className="small">
|
||||
Input the JSON description below. Should be something like:
|
||||
<br/>
|
||||
{JSONExample}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="form-group w-100">
|
||||
<textarea
|
||||
onChange={(event) => {
|
||||
console.log(event)
|
||||
updateImportDescription(event.target.value)
|
||||
}}
|
||||
value={data.value}
|
||||
className="form-control w-100"
|
||||
rows="5"
|
||||
placeholder="Input JSON description here."/>
|
||||
</div>
|
||||
{data.error !== Validation.OK ? (
|
||||
<div className="form-group">
|
||||
<p className="text-danger">{data.error === Validation.Empty ? "Empty input" : "Invalid JSON"}</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="d-flex flex-column flex-sm-row justify-content-end">
|
||||
<a type="button" onClick={closeImport}
|
||||
className="btn btn-sm btn-outline-primary m-0 mb-2 mb-sm-0 mr-sm-2">Close</a>
|
||||
<a type="button" onClick={loadDescription}
|
||||
className={`btn btn-sm btn-primary m-0 ml-sm-2 ${data.error ? 'disabled' : ''}`}>Load
|
||||
configuration</a>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import {validateEmail, Validation} from "./validate.js";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ShowJSON} from "./ShowJSON";
|
||||
|
||||
/**
|
||||
* Components that renders the form to request quote.
|
||||
|
@ -10,20 +11,18 @@ export function OrderForm() {
|
|||
email,
|
||||
note,
|
||||
isProcessing,
|
||||
isProcessingComplete,
|
||||
showDescription,
|
||||
updateEmail,
|
||||
updateNote,
|
||||
submitForm,
|
||||
submitDisabled,
|
||||
} = 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,
|
||||
submitDisabled: state.submitDisabled
|
||||
}));
|
||||
|
||||
|
||||
|
@ -59,14 +58,10 @@ export function OrderForm() {
|
|||
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}/>
|
||||
<ShowJSON/>
|
||||
|
||||
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit"
|
||||
disabled={submitDisabled()}
|
||||
value={`${isProcessing ? 'Processing ...' : 'Request quote'}`}/>
|
||||
</div>
|
||||
{/*This will open an email window. Send the email to make your request.*/}
|
||||
|
|
|
@ -3,6 +3,8 @@ import {OrderSummary} from "./OrderSummary";
|
|||
import {OrderForm} from "./OrderForm";
|
||||
import {CrateList} from "./CrateList";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {ImportJSON} from "./ImportJSON";
|
||||
import {RFQFeedback} from "./RFQFeedback";
|
||||
|
||||
/**
|
||||
* Component that renders all things for order.
|
||||
|
@ -22,13 +24,11 @@ export function OrderPanel({title, description}) {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary m-0 mb-2"
|
||||
style={{'cursor': 'pointer'}}
|
||||
onClick={onClickOpenImport}>Import JSON
|
||||
</button>
|
||||
<ImportJSON/>
|
||||
</div>
|
||||
|
||||
<RFQFeedback/>
|
||||
|
||||
{isMobile ? (
|
||||
<div className="mobileBtnDisplaySideMenu">
|
||||
<button onClick={onClickToggleMobileSideMenu}>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import {useShopStore} from "./shop_store";
|
||||
import {useClickAway} from "./options/useClickAway";
|
||||
import {Modal} from "react-bootstrap";
|
||||
import {Validation} from "./validate";
|
||||
import React from "react";
|
||||
|
||||
export function RFQFeedback() {
|
||||
const {closeRFQ, shouldShow, status} = useShopStore(state => ({
|
||||
closeRFQ: state.closeRFQFeedback,
|
||||
shouldShow: state.shouldShowRFQFeedback,
|
||||
status: state.processingResult
|
||||
}))
|
||||
|
||||
const ref = useClickAway((e) => {
|
||||
if (e.type === "mousedown") // ignore touchstart
|
||||
closeRFQ()
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal show={shouldShow} animation={true} centered>
|
||||
<Modal.Body ref={ref}>
|
||||
<div className="d-flex">
|
||||
<div>
|
||||
{status.status === Validation.OK ?
|
||||
<img width="30px" src="/images/shop/icon-done.svg" alt="close"/>
|
||||
: <img width="30px" src="/images/shop/icon-warning.svg" alt="close"/>
|
||||
}
|
||||
</div>
|
||||
<div style={{'padding': '0 .5em'}}>
|
||||
{status.message}
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -8,7 +8,7 @@ import {OrderPanel} from "./OrderPanel";
|
|||
import {useShopStore} from "./shop_store";
|
||||
|
||||
/**
|
||||
* Component that render the entire shop
|
||||
* Component that renders the entire shop
|
||||
*/
|
||||
|
||||
export function Shop() {
|
||||
|
@ -18,7 +18,7 @@ export function Shop() {
|
|||
deleteCard: state.deleteCard,
|
||||
cardIndexById: state.cardIndexById
|
||||
}));
|
||||
const handleOnDragEnd = (drop_result, provided) => {
|
||||
const handleOnDragEnd = (drop_result, _provided) => {
|
||||
if (drop_result.source.droppableId === "backlog")
|
||||
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
|
||||
else if(drop_result.destination.droppableId === "backlog")
|
||||
|
@ -28,7 +28,7 @@ export function Shop() {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
addCardFromBacklog(null, [cardIndexById("kasli"), cardIndexById("eem_pwr_mod")], -1);
|
||||
addCardFromBacklog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,43 @@
|
|||
import React from "react";
|
||||
import {Modal} from "react-bootstrap";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {useClickAway} from "./options/useClickAway";
|
||||
|
||||
export function ShowJSON() {
|
||||
return (
|
||||
<div> SHOW JSON PLACEHOLDER</div>
|
||||
)
|
||||
const {shouldShow, description, showDescription, closeDescription} = useShopStore(state => ({
|
||||
shouldShow: state.shouldShowDescription,
|
||||
description: state.description,
|
||||
closeDescription: state.closeDescription,
|
||||
showDescription: state.showDescription,
|
||||
}));
|
||||
|
||||
const ref = useClickAway((e) => {
|
||||
if (e.type === "mousedown") // ignore touchstart
|
||||
closeDescription()
|
||||
}
|
||||
);
|
||||
|
||||
return (<>
|
||||
<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}/>
|
||||
<Modal show={shouldShow} animation={true} centered>
|
||||
<Modal.Body ref={ref}>
|
||||
<textarea
|
||||
value={description}
|
||||
className="form-control w-100"
|
||||
rows={10}
|
||||
readOnly={true}
|
||||
placeholder="There should be description of the crate"/>
|
||||
|
||||
<div className="d-flex flex-column flex-sm-row justify-content-end m-1">
|
||||
<a type="button" onClick={closeDescription}
|
||||
className="btn btn-sm btn-outline-primary m-0 mb-2 mb-sm-0 mr-sm-2">Close</a>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>)
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import {useShopStore} from "./shop_store";
|
||||
import {FilterOptions} from "./options/utils";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
|
||||
|
||||
export function validateJSON(description) {
|
||||
|
@ -11,11 +13,15 @@ export function validateJSON(description) {
|
|||
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;
|
||||
try {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -29,6 +35,7 @@ export function JSONToCrates(description) {
|
|||
crate_mode: crate.type,
|
||||
items: Array.from(crate.items.map((card, i) => ({
|
||||
...pn_to_card(card.pn),
|
||||
id: uuidv4(),
|
||||
options_data: card.options
|
||||
}))),
|
||||
warnings: [],
|
||||
|
|
|
@ -6,8 +6,8 @@ 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";
|
||||
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
|
||||
import {CratesToJSON, JSONToCrates} from "./json_porter";
|
||||
|
||||
|
||||
const cards_to_pn_map = (cards) => {
|
||||
|
@ -38,26 +38,46 @@ const useLayout = ((set, get) => ({
|
|||
isMobile: window.deviceIsMobile(),
|
||||
sideMenuIsOpen: false,
|
||||
newCardJustAdded: false,
|
||||
importIsOpen: false,
|
||||
|
||||
switchSideMenu: () => set(state => ({
|
||||
sideMenuIsOpen: !state.sideMenuIsOpen
|
||||
})),
|
||||
}));
|
||||
|
||||
const useImportJSON = ((set, get) => ({
|
||||
importShouldOpen: false,
|
||||
importValue: {
|
||||
value: "",
|
||||
error: Validation.OK
|
||||
},
|
||||
openImport: () => set(state => ({
|
||||
importIsOpen: true
|
||||
importShouldOpen: true
|
||||
})),
|
||||
closeImport: () => set(state => ({
|
||||
importIsOpen: false
|
||||
importShouldOpen: false
|
||||
})),
|
||||
}))
|
||||
loadDescription: () => set(state => ({
|
||||
importShouldOpen: false,
|
||||
crates: JSONToCrates(state.importValue.value)
|
||||
})),
|
||||
updateImportDescription: (new_description) => set(state => ({
|
||||
importValue: {
|
||||
value: new_description,
|
||||
error: validateJSONInput(new_description)
|
||||
}
|
||||
}))
|
||||
|
||||
}));
|
||||
|
||||
const useSubmitForm = ((set, get) => ({
|
||||
// TODO think about it
|
||||
isProcessing: false,
|
||||
shouldShowRFQFeedback: true,
|
||||
RFQBodyType: 'email',
|
||||
isProcessingComplete: true,
|
||||
shouldShowRFQFeedback: false,
|
||||
processingResult: {
|
||||
status: Validation.OK,
|
||||
message: ""
|
||||
},
|
||||
API_RFQ: shared_data.API_RFQ,
|
||||
email: {
|
||||
value: "",
|
||||
error: null
|
||||
|
@ -83,6 +103,17 @@ const useSubmitForm = ((set, get) => ({
|
|||
}
|
||||
})),
|
||||
|
||||
_revalidateForm: () => set(state => ({
|
||||
email: {
|
||||
value: state.email.value,
|
||||
error: validateEmail(state.email.value)
|
||||
},
|
||||
note: {
|
||||
value: state.note.value,
|
||||
error: validateEmail(state.note.value)
|
||||
},
|
||||
})),
|
||||
|
||||
updateDescription: () => set(state => ({
|
||||
description: CratesToJSON(state.crates)
|
||||
})),
|
||||
|
@ -93,7 +124,41 @@ const useSubmitForm = ((set, get) => ({
|
|||
closeDescription: () => set(state => ({
|
||||
shouldShowDescription: false
|
||||
})),
|
||||
submitForm: () => set(state => ({})) // TODO
|
||||
_submitForm: () => set(state => ({isProcessing: true})),
|
||||
finishSubmitForm: (result) => set(state => ({
|
||||
isProcessing: false,
|
||||
shouldShowRFQFeedback: true,
|
||||
processingResult: result})),
|
||||
|
||||
submitDisabled: () => (get().email.error !== Validation.OK),
|
||||
|
||||
submitForm: () => {
|
||||
get().updateDescription();
|
||||
get()._revalidateForm();
|
||||
get()._submitForm();
|
||||
if (get().submitDisabled()) return;
|
||||
fetch(get().API_RFQ, {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
email: get().email.value,
|
||||
note: get().note.value,
|
||||
configuration: get().description
|
||||
})
|
||||
}).then(response => {
|
||||
if (response.status !== 200) {
|
||||
throw Error("Response status is not OK: " + response.status + ".\n" + response);
|
||||
}
|
||||
get().finishSubmitForm({status: Validation.OK, message: "We've received your request and will be in contact soon."})
|
||||
}).catch(err => {
|
||||
console.error("Request failed, reason:", err)
|
||||
get().finishSubmitForm({
|
||||
status: Validation.Invalid,
|
||||
message: "We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"
|
||||
})
|
||||
})
|
||||
},
|
||||
closeRFQFeedback: () => set(state => ({shouldShowRFQFeedback: false}))
|
||||
}));
|
||||
|
||||
const useHighlighted = ((set, get) => ({
|
||||
|
@ -263,7 +328,6 @@ const useCart = ((set, get) => ({
|
|||
},
|
||||
|
||||
setCrateMode: (id, mode) => {
|
||||
console.log("setCrateMode", id, mode)
|
||||
get()._setCrateMode(id, mode)
|
||||
get().fillWarnings(id);
|
||||
},
|
||||
|
@ -292,8 +356,6 @@ const useCart = ((set, get) => ({
|
|||
get()._updateOptions(crate_id, index, new_options);
|
||||
get().fillWarnings(crate_id);
|
||||
}
|
||||
|
||||
// TODO load and save jsons?
|
||||
}))
|
||||
|
||||
|
||||
|
@ -304,4 +366,5 @@ export const useShopStore = create((...params) => ({
|
|||
...useSubmitForm(...params),
|
||||
...useLayout(...params),
|
||||
...useHighlighted(...params),
|
||||
...useImportJSON(...params),
|
||||
}))
|
|
@ -20,8 +20,8 @@ module.exports = {
|
|||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
mode: "development"
|
||||
//devtool: false,
|
||||
//mode: "production"
|
||||
//devtool: "inline-source-map",
|
||||
//mode: "development"
|
||||
devtool: false,
|
||||
mode: "production"
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue