diff --git a/static/js/shop/CrateMode.jsx b/static/js/shop/CrateMode.jsx
index ac207eeb..8478a822 100644
--- a/static/js/shop/CrateMode.jsx
+++ b/static/js/shop/CrateMode.jsx
@@ -23,4 +23,4 @@ export function CrateMode({crate_index}) {
))}
);
-}
+}
\ No newline at end of file
diff --git a/static/js/shop/ImportJSON.jsx b/static/js/shop/ImportJSON.jsx
index 19a01c77..2dc0d9c4 100644
--- a/static/js/shop/ImportJSON.jsx
+++ b/static/js/shop/ImportJSON.jsx
@@ -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 (
-
Import JSON PLACEHOLDER
- )
+ 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 (<>
+ Import JSON
+
+
+
+
+
+ Input the JSON description below. Should be something like:
+
+ {JSONExample}
+
+
+
+
+
+ {data.error !== Validation.OK ? (
+
+
{data.error === Validation.Empty ? "Empty input" : "Invalid JSON"}
+
+ ) : null}
+
+
+
+
+ >)
}
\ No newline at end of file
diff --git a/static/js/shop/OrderForm.jsx b/static/js/shop/OrderForm.jsx
index b1010265..038cdb43 100644
--- a/static/js/shop/OrderForm.jsx
+++ b/static/js/shop/OrderForm.jsx
@@ -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"/>
-
+
{/*This will open an email window. Send the email to make your request.*/}
diff --git a/static/js/shop/OrderPanel.jsx b/static/js/shop/OrderPanel.jsx
index bb770505..65bc747c 100644
--- a/static/js/shop/OrderPanel.jsx
+++ b/static/js/shop/OrderPanel.jsx
@@ -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}) {
- Import JSON
-
+
+
+
{isMobile ? (
diff --git a/static/js/shop/RFQFeedback.jsx b/static/js/shop/RFQFeedback.jsx
new file mode 100644
index 00000000..477c8cbb
--- /dev/null
+++ b/static/js/shop/RFQFeedback.jsx
@@ -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 (
+
+
+
+
+ {status.status === Validation.OK ?
+
+ :
+ }
+
+
+ {status.message}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/static/js/shop/Shop.jsx b/static/js/shop/Shop.jsx
index abe69154..2f25c4ae 100644
--- a/static/js/shop/Shop.jsx
+++ b/static/js/shop/Shop.jsx
@@ -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 (
diff --git a/static/js/shop/ShowJSON.jsx b/static/js/shop/ShowJSON.jsx
index c8f3e936..a793e15b 100644
--- a/static/js/shop/ShowJSON.jsx
+++ b/static/js/shop/ShowJSON.jsx
@@ -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 (
- SHOW JSON PLACEHOLDER
- )
+ 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 (<>
+
+
+
+
+
+
+
+
+ >)
}
\ No newline at end of file
diff --git a/static/js/shop/json_porter.js b/static/js/shop/json_porter.js
index 809e4906..d5afd60f 100644
--- a/static/js/shop/json_porter.js
+++ b/static/js/shop/json_porter.js
@@ -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: [],
diff --git a/static/js/shop/shop_store.js b/static/js/shop/shop_store.js
index 57bd0f17..a45ece80 100644
--- a/static/js/shop/shop_store.js
+++ b/static/js/shop/shop_store.js
@@ -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),
}))
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 7c8fae74..bc1ffb35 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -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"
};