From 1ddfa70e20b0b18461ec76c74d29ba1f56c298e9 Mon Sep 17 00:00:00 2001 From: Khalifa Lame Date: Tue, 1 Oct 2019 09:49:05 +0400 Subject: [PATCH 01/48] support .env config --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 55c8ce335..d2ccd8247 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ const path = require("path"); const webpack = require("webpack"); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +require('dotenv').config(); const resolve = path.resolve.bind(path, __dirname); From 5a40f619e1f35eba5403620bb7a2152da1801718 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 19 Sep 2019 12:13:04 +0200 Subject: [PATCH 02/48] Add variant creator wip --- src/attributes/fixtures.ts | 6 +- .../ProductVariantCreate.stories.tsx | 94 +++++++ .../ProductVariantCreateAttributes.tsx | 78 ++++++ .../ProductVariantCreateContent.tsx | 123 ++++++++ .../ProductVariantCreateDialog.tsx | 132 +++++++++ .../ProductVariantCreatePrices.tsx | 265 ++++++++++++++++++ .../ProductVariantCreateSummary.tsx | 165 +++++++++++ .../ProductVariantCreateTabs.tsx | 107 +++++++ .../ProductVariantCreateValues.tsx | 64 +++++ .../ProductVariantCreateDialog/form.ts | 33 +++ .../ProductVariantCreateDialog/reducer.ts | 192 +++++++++++++ .../ProductVariantCreateDialog/types.ts | 5 + 12 files changed, 1263 insertions(+), 1 deletion(-) create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx create mode 100644 src/products/components/ProductVariantCreateDialog/form.ts create mode 100644 src/products/components/ProductVariantCreateDialog/reducer.ts create mode 100644 src/products/components/ProductVariantCreateDialog/types.ts diff --git a/src/attributes/fixtures.ts b/src/attributes/fixtures.ts index 4c236da3a..92ab3ef0d 100644 --- a/src/attributes/fixtures.ts +++ b/src/attributes/fixtures.ts @@ -1,3 +1,4 @@ +import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; import { AttributeInputTypeEnum, AttributeValueType @@ -35,7 +36,10 @@ export const attribute: AttributeDetailsFragment = { visibleInStorefront: true }; -export const attributes: AttributeList_attributes_edges_node[] = [ +export const attributes: Array< + AttributeList_attributes_edges_node & + ProductDetails_product_productType_variantAttributes +> = [ { node: { __typename: "Attribute" as "Attribute", diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx new file mode 100644 index 000000000..d855f58ab --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -0,0 +1,94 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import { attributes } from "@saleor/attributes/fixtures"; +import { isSelected } from "@saleor/utils/lists"; +import Decorator from "../../../storybook/Decorator"; +import ProductVariantCreateContent, { + ProductVariantCreateContentProps +} from "./ProductVariantCreateContent"; +import ProductVariantCreateDialog from "./ProductVariantCreateDialog"; + +const selectedAttributes = [1, 2, 4].map(index => attributes[index].id); +const selectedValues = attributes + .filter(attribute => + isSelected(attribute.id, selectedAttributes, (a, b) => a === b) + ) + .map(attribute => attribute.values.map(value => value.id)) + .reduce((acc, curr) => [...acc, ...curr], []) + .filter((_, valueIndex) => valueIndex % 2); + +const props: ProductVariantCreateContentProps = { + attributes, + currencySymbol: "USD", + data: { + attributes: selectedAttributes, + price: { + all: false, + attribute: selectedAttributes[1], + value: "2.79", + values: selectedAttributes.map((_, attributeIndex) => + (attributeIndex + 4).toFixed(2) + ) + }, + stock: { + all: false, + attribute: selectedAttributes[1], + value: "8", + values: selectedAttributes.map((_, attributeIndex) => + (selectedAttributes.length * 10 - attributeIndex).toString() + ) + }, + values: selectedValues, + variants: [ + { + attributes: attributes + .filter(attribute => selectedAttributes.includes(attribute.id)) + .map(attribute => ({ + id: attribute.id, + values: [attribute.values[0].id] + })), + product: "=1uahc98nas" + } + ] + }, + dispatchFormDataAction: () => undefined, + step: "attributes" +}; + +storiesOf("Views / Products / Create multiple variants", module) + .addDecorator(storyFn => ( + + {storyFn()} + + )) + .addDecorator(Decorator) + .add("choose attributes", () => ) + .add("select values", () => ( + + )) + .add("prices and SKU", () => ( + + )) + .add("summary", () => ( + + )); + +storiesOf("Views / Products / Create multiple variants", module) + .addDecorator(Decorator) + .add("interactive", () => ( + undefined} + onSubmit={() => undefined} + /> + )); diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx new file mode 100644 index 000000000..2b176c7b1 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx @@ -0,0 +1,78 @@ +import { Theme } from "@material-ui/core/styles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import makeStyles from "@material-ui/styles/makeStyles"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import Checkbox from "@saleor/components/Checkbox"; +import { maybe, renderCollection } from "@saleor/misc"; +import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { ProductVariantCreateFormData } from "./form"; + +export interface ProductVariantCreateAttributesProps { + attributes: ProductDetails_product_productType_variantAttributes[]; + data: ProductVariantCreateFormData; + onAttributeClick: (id: string) => void; +} + +const useStyles = makeStyles((theme: Theme) => ({ + checkboxCell: { + paddingLeft: 0 + }, + wideCell: { + width: "100%" + } +})); + +const ProductVariantCreateAttributes: React.FC< + ProductVariantCreateAttributesProps +> = props => { + const { attributes, data, onAttributeClick } = props; + const classes = useStyles(props); + + return ( + + + {renderCollection( + attributes, + attribute => { + if (!attribute) { + return null; + } + const isChecked = !!data.attributes.find( + selectedAttribute => selectedAttribute === attribute.id + ); + + return ( + attribute.id)}> + + onAttributeClick(attribute.id)} + /> + + + {attribute.name} + + + ); + }, + () => ( + + + + + + ) + )} + +
+ ); +}; + +ProductVariantCreateAttributes.displayName = "ProductVariantCreateAttributes"; +export default ProductVariantCreateAttributes; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx new file mode 100644 index 000000000..87a0ccd83 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -0,0 +1,123 @@ +import React from "react"; + +import { makeStyles } from "@material-ui/styles"; +import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { isSelected } from "@saleor/utils/lists"; +import { ProductVariantCreateFormData } from "./form"; +import ProductVariantCreateAttributes from "./ProductVariantCreateAttributes"; +import ProductVariantCreatePrices from "./ProductVariantCreatePrices"; +import ProductVariantCreateSummary from "./ProductVariantCreateSummary"; +import ProductVariantCreateTabs from "./ProductVariantCreateTabs"; +import ProductVariantCreateValues from "./ProductVariantCreateValues"; +import { ProductVariantCreateReducerAction } from "./reducer"; +import { ProductVariantCreateStep } from "./types"; + +const useStyles = makeStyles({ + root: { + maxHeight: 400, + overflowY: "scroll" + } +}); + +export interface ProductVariantCreateContentProps { + attributes: ProductDetails_product_productType_variantAttributes[]; + currencySymbol: string; + data: ProductVariantCreateFormData; + dispatchFormDataAction: React.Dispatch; + step: ProductVariantCreateStep; +} + +const ProductVariantCreateContent: React.FC< + ProductVariantCreateContentProps +> = props => { + const { + attributes, + currencySymbol, + data, + dispatchFormDataAction, + step + } = props; + const classes = useStyles(props); + + const selectedAttributes = attributes.filter(attribute => + isSelected(attribute.id, data.attributes, (a, b) => a === b) + ); + + return ( +
+ +
+ {step === "attributes" && ( + + dispatchFormDataAction({ + id, + type: "selectAttribute" + }) + } + /> + )} + {step === "values" && ( + + dispatchFormDataAction({ + id, + type: "selectValue" + }) + } + /> + )} + {step === "prices" && ( + + dispatchFormDataAction({ + all, + type: type === "price" ? "applyPriceToAll" : "applyStockToAll" + }) + } + onApplyToAllChange={(value, type) => + dispatchFormDataAction({ + type: + type === "price" + ? "changeApplyPriceToAllValue" + : "changeApplyStockToAllValue", + value + }) + } + onAttributeSelect={(id, type) => + dispatchFormDataAction({ + id, + type: + type === "price" + ? "changeApplyPriceToAttributeId" + : "changeApplyStockToAttributeId" + }) + } + onValueClick={id => + dispatchFormDataAction({ + id, + type: "selectValue" + }) + } + /> + )} + {step === "summary" && ( + + )} +
+
+ ); +}; + +ProductVariantCreateContent.displayName = "ProductVariantCreateContent"; +export default ProductVariantCreateContent; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx new file mode 100644 index 000000000..4143e63a3 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -0,0 +1,132 @@ +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import { Theme } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { FormattedMessage } from "react-intl"; +import { initialForm, ProductVariantCreateFormData } from "./form"; +import ProductVariantCreateContent, { + ProductVariantCreateContentProps +} from "./ProductVariantCreateContent"; +import reduceProductVariantCreateFormData from "./reducer"; +import { ProductVariantCreateStep } from "./types"; + +const useStyles = makeStyles((theme: Theme) => ({ + button: { + marginLeft: theme.spacing.unit * 2 + }, + content: { + overflowX: "visible", + overflowY: "hidden", + width: 600 + } +})); + +export interface ProductVariantCreateDialogProps + extends Omit< + ProductVariantCreateContentProps, + "dispatchFormDataAction" | "step" + > { + open: boolean; + onClose: () => undefined; + onSubmit: (data: ProductVariantCreateFormData) => void; +} + +const ProductVariantCreateDialog: React.FC< + ProductVariantCreateDialogProps +> = props => { + const { open, onClose, ...contentProps } = props; + const classes = useStyles(props); + const [step, setStep] = React.useState( + "attributes" + ); + + function handleNextStep() { + switch (step) { + case "attributes": + setStep("values"); + break; + case "values": + setStep("prices"); + break; + case "prices": + setStep("summary"); + break; + } + } + + function handlePrevStep() { + switch (step) { + case "values": + setStep("attributes"); + break; + case "prices": + setStep("values"); + break; + case "summary": + setStep("prices"); + break; + } + } + + const [data, dispatchFormDataAction] = React.useReducer( + reduceProductVariantCreateFormData, + initialForm + ); + + return ( + + + + + + + + + + {step !== "attributes" && ( + + )} + {step !== "summary" ? ( + + ) : ( + + )} + + + ); +}; + +ProductVariantCreateDialog.displayName = "ProductVariantCreateDialog"; +export default ProductVariantCreateDialog; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx new file mode 100644 index 000000000..324feb070 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -0,0 +1,265 @@ +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Radio from "@material-ui/core/Radio"; +import RadioGroup from "@material-ui/core/RadioGroup"; +import { Theme } from "@material-ui/core/styles"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import FormSpacer from "@saleor/components/FormSpacer"; +import Grid from "@saleor/components/Grid"; +import Hr from "@saleor/components/Hr"; +import SingleSelectField from "@saleor/components/SingleSelectField"; +import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { isSelected } from "@saleor/utils/lists"; +import { ProductVariantCreateFormData } from "./form"; + +const useStyles = makeStyles((theme: Theme) => ({ + hr: { + marginBottom: theme.spacing.unit, + marginTop: theme.spacing.unit / 2 + }, + label: { + alignSelf: "center" + }, + shortInput: { + width: "50%" + } +})); + +export type PriceOrStock = "price" | "stock"; +export interface ProductVariantCreatePricesProps { + attributes: ProductDetails_product_productType_variantAttributes[]; + data: ProductVariantCreateFormData; + onValueClick: (id: string) => void; + onAttributeSelect: (id: string, type: PriceOrStock) => void; + onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void; + onApplyToAllChange: (value: string, type: PriceOrStock) => void; +} + +const ProductVariantCreatePrices: React.FC< + ProductVariantCreatePricesProps +> = props => { + const { + attributes, + data, + onApplyPriceOrStockChange, + onApplyToAllChange, + onAttributeSelect + } = props; + const classes = useStyles(props); + const intl = useIntl(); + + const selectedAttributes = attributes.filter(attribute => + isSelected(attribute.id, data.attributes, (a, b) => a === b) + ); + const attributeChoices = selectedAttributes.map(attribute => ({ + label: attribute.name, + value: attribute.id + })); + const priceAttributeValues = data.price.all + ? null + : data.price.attribute + ? selectedAttributes.find( + attribute => attribute.id === data.price.attribute + ).values + : []; + const stockAttributeValues = data.stock.all + ? null + : data.stock.attribute + ? selectedAttributes.find( + attribute => attribute.id === data.stock.attribute + ).values + : []; + + return ( + <> + + + +
+ + } + label={intl.formatMessage({ + defaultMessage: "Apply single price to all SKUs" + })} + onChange={() => onApplyPriceOrStockChange(true, "price")} + /> + + onApplyToAllChange(event.target.value, "price")} + /> + + } + label={intl.formatMessage({ + defaultMessage: "Apply unique prices by attribute to each SKU" + })} + onChange={() => onApplyPriceOrStockChange(false, "price")} + /> + {!data.price.all && ( + <> + + +
+ + + +
+
+ + onAttributeSelect(event.target.value, "price") + } + /> +
+
+ {priceAttributeValues && + priceAttributeValues.map((attribute, attributeIndex) => ( + <> + + +
+ {attribute.name} +
+
+ +
+
+ + ))} + + )} +
+ + + + +
+ + } + label={intl.formatMessage({ + defaultMessage: "Apply single stock to all SKUs" + })} + onChange={() => onApplyPriceOrStockChange(true, "stock")} + /> + + onApplyToAllChange(event.target.value, "stock")} + /> + + } + label={intl.formatMessage({ + defaultMessage: "Apply unique stock by attribute to each SKU" + })} + onChange={() => onApplyPriceOrStockChange(false, "stock")} + /> + {!data.stock.all && ( + <> + + +
+ + + +
+
+ + onAttributeSelect(event.target.value, "stock") + } + /> +
+
+ {stockAttributeValues && + stockAttributeValues.map((attribute, attributeIndex) => ( + <> + + +
+ {attribute.name} +
+
+ +
+
+ + ))} + + )} +
+ + ); +}; + +ProductVariantCreatePrices.displayName = "ProductVariantCreatePrices"; +export default ProductVariantCreatePrices; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx new file mode 100644 index 000000000..c2953e425 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -0,0 +1,165 @@ +import { Theme } from "@material-ui/core/styles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import classNames from "classnames"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import Hr from "@saleor/components/Hr"; +import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; +import { ProductVariantCreateFormData } from "./form"; + +export interface ProductVariantCreateSummaryProps { + attributes: ProductDetails_product_productType_variantAttributes[]; + currencySymbol: string; + data: ProductVariantCreateFormData; +} + +const useStyles = makeStyles((theme: Theme) => ({ + col: { + paddingLeft: theme.spacing.unit, + paddingRight: theme.spacing.unit + }, + colName: { + paddingLeft: "0 !important", + width: "auto" + }, + colPrice: { + width: 110 + }, + colSku: { + width: 110 + }, + colStock: { + width: 110 + }, + hr: { + marginBottom: theme.spacing.unit, + marginTop: theme.spacing.unit / 2 + }, + input: { + "& input": { + padding: "16px 12px 17px" + }, + marginTop: theme.spacing.unit / 2, + width: 104 + } +})); + +function getVariantName( + variant: ProductVariantCreateInput, + attributes: ProductDetails_product_productType_variantAttributes[] +): string[] { + return attributes.reduce( + (acc, attribute) => [ + ...acc, + attribute.values.find( + value => + value.id === + variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ).values[0] + ).name + ], + [] + ); +} + +const ProductVariantCreateSummary: React.FC< + ProductVariantCreateSummaryProps +> = props => { + const { attributes, currencySymbol, data } = props; + const classes = useStyles(props); + + return ( + <> + + + +
+ + + + + + + + + + + + + + + + + + + {data.variants.map(variant => ( + attribute.id).join(":")} + > + + {getVariantName(variant, attributes).join(" ")} + + + + + + + + + + + + ))} + +
+ + ); +}; + +ProductVariantCreateSummary.displayName = "ProductVariantCreateSummary"; +export default ProductVariantCreateSummary; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx new file mode 100644 index 000000000..11a57da01 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx @@ -0,0 +1,107 @@ +import { Theme } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; +import classNames from "classnames"; +import React from "react"; +import { IntlShape, useIntl } from "react-intl"; + +import { ProductVariantCreateStep } from "./types"; + +interface Step { + label: string; + value: ProductVariantCreateStep; +} +function getSteps(intl: IntlShape): Step[] { + return [ + { + label: intl.formatMessage({ + defaultMessage: "Choose Attributes", + description: "variant creation step" + }), + value: "attributes" + }, + { + label: intl.formatMessage({ + defaultMessage: "Select Values", + description: "attribute values, variant creation step" + }), + value: "values" + }, + { + label: intl.formatMessage({ + defaultMessage: "Prices and SKU", + description: "variant creation step" + }), + value: "prices" + }, + { + label: intl.formatMessage({ + defaultMessage: "Summary", + description: "variant creation step" + }), + value: "summary" + } + ]; +} + +const useStyles = makeStyles( + (theme: Theme) => ({ + label: { + fontSize: 14, + textAlign: "center" + }, + root: { + borderBottom: `1px solid ${theme.palette.divider}`, + display: "flex", + justifyContent: "space-between", + marginBottom: theme.spacing.unit * 3 + }, + tab: { + flex: 1, + paddingBottom: theme.spacing.unit + }, + tabActive: { + fontWeight: 600 + }, + tabUnderline: { + borderBottom: `3px solid ${theme.palette.primary.main}` + } + }), + { + name: "ProductVariantCreateTabs" + } +); + +export interface ProductVariantCreateTabsProps { + step: ProductVariantCreateStep; +} + +const ProductVariantCreateTabs: React.FC< + ProductVariantCreateTabsProps +> = props => { + const { step: currentStep } = props; + const classes = useStyles(props); + const intl = useIntl(); + const steps = getSteps(intl); + + return ( +
+ {steps.map((step, stepIndex) => ( +
step.value === currentStep) >= stepIndex + })} + > + + {step.label} + +
+ ))} +
+ ); +}; + +ProductVariantCreateTabs.displayName = "ProductVariantCreateTabs"; +export default ProductVariantCreateTabs; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx new file mode 100644 index 000000000..84327bfd5 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -0,0 +1,64 @@ +import { Theme } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import makeStyles from "@material-ui/styles/makeStyles"; +import React from "react"; + +import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; +import Hr from "@saleor/components/Hr"; +import Skeleton from "@saleor/components/Skeleton"; +import { maybe } from "@saleor/misc"; +import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { isSelected } from "@saleor/utils/lists"; +import { ProductVariantCreateFormData } from "./form"; + +export interface ProductVariantCreateValuesProps { + attributes: ProductDetails_product_productType_variantAttributes[]; + data: ProductVariantCreateFormData; + onValueClick: (id: string) => void; +} + +const useStyles = makeStyles((theme: Theme) => ({ + hr: { + marginBottom: theme.spacing.unit, + marginTop: theme.spacing.unit / 2 + }, + valueContainer: { + display: "grid", + gridColumnGap: theme.spacing.unit * 3 + "px", + gridTemplateColumns: "repeat(3, 1fr)", + marginBottom: theme.spacing.unit * 3 + } +})); + +const ProductVariantCreateValues: React.FC< + ProductVariantCreateValuesProps +> = props => { + const { attributes, data, onValueClick } = props; + const classes = useStyles(props); + + return ( + <> + {attributes.map(attribute => ( + <> + + {maybe(() => attribute.name, )} + +
+
+ {attribute.values.map(value => ( + a === b)} + name={`value:${value.id}`} + label={value.name} + onChange={() => onValueClick(value.id)} + /> + ))} +
+ + ))} + + ); +}; + +ProductVariantCreateValues.displayName = "ProductVariantCreateValues"; +export default ProductVariantCreateValues; diff --git a/src/products/components/ProductVariantCreateDialog/form.ts b/src/products/components/ProductVariantCreateDialog/form.ts new file mode 100644 index 000000000..503ffbc8b --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/form.ts @@ -0,0 +1,33 @@ +import { ProductVariantCreateInput } from "../../../types/globalTypes"; + +export interface AllOrAttribute { + all: boolean; + attribute: string; + value: string; + values: string[]; +} +export interface ProductVariantCreateFormData { + attributes: string[]; + price: AllOrAttribute; + stock: AllOrAttribute; + values: string[]; + variants: ProductVariantCreateInput[]; +} + +export const initialForm: ProductVariantCreateFormData = { + attributes: [], + price: { + all: true, + attribute: undefined, + value: "", + values: [] + }, + stock: { + all: true, + attribute: undefined, + value: "", + values: [] + }, + values: [], + variants: [] +}; diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts new file mode 100644 index 000000000..25379b91d --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -0,0 +1,192 @@ +import { toggle, updateAtIndex } from "@saleor/utils/lists"; +import { initialForm, ProductVariantCreateFormData } from "./form"; + +export type ProductVariantCreateReducerActionType = + | "applyPriceToAll" + | "applyPriceToAttribute" + | "applyStockToAll" + | "applyStockToAttribute" + | "changeApplyPriceToAllValue" + | "changeApplyPriceToAttributeId" + | "changeApplyStockToAllValue" + | "changeApplyStockToAttributeId" + | "changeAttributePrice" + | "changeAttributeStock" + | "selectAttribute" + | "selectValue"; +export interface ProductVariantCreateReducerAction { + all?: boolean; + id?: string; + type: ProductVariantCreateReducerActionType; + value?: string; +} + +function selectAttribute( + state: ProductVariantCreateFormData, + attribute: string +): ProductVariantCreateFormData { + const attributes = toggle(attribute, state.attributes, (a, b) => a === b); + + return { + ...initialForm, + attributes + }; +} + +function selectValue( + state: ProductVariantCreateFormData, + value: string +): ProductVariantCreateFormData { + const values = toggle(value, state.values, (a, b) => a === b); + + return { + ...initialForm, + attributes: state.attributes, + values + }; +} + +function applyPriceToAll( + state: ProductVariantCreateFormData, + value: boolean +): ProductVariantCreateFormData { + return { + ...state, + price: { + ...state.price, + all: value + } + }; +} + +function applyStockToAll( + state: ProductVariantCreateFormData, + value: boolean +): ProductVariantCreateFormData { + return { + ...state, + stock: { + ...state.stock, + all: value + } + }; +} + +function changeAttributePrice( + state: ProductVariantCreateFormData, + attribute: string, + price: string +): ProductVariantCreateFormData { + const index = state.price.values.indexOf(attribute); + const values = updateAtIndex(price, state.price.values, index); + + return { + ...state, + price: { + ...state.price, + values + } + }; +} + +function changeAttributeStock( + state: ProductVariantCreateFormData, + attribute: string, + stock: string +): ProductVariantCreateFormData { + const index = state.stock.values.indexOf(attribute); + const values = updateAtIndex(stock, state.stock.values, index); + + return { + ...state, + stock: { + ...state.stock, + values + } + }; +} + +function changeApplyPriceToAttributeId( + state: ProductVariantCreateFormData, + attribute: string +): ProductVariantCreateFormData { + return { + ...state, + price: { + ...state.price, + attribute + } + }; +} + +function changeApplyStockToAttributeId( + state: ProductVariantCreateFormData, + attribute: string +): ProductVariantCreateFormData { + return { + ...state, + stock: { + ...state.stock, + attribute + } + }; +} + +function changeApplyPriceToAllValue( + state: ProductVariantCreateFormData, + value: string +): ProductVariantCreateFormData { + return { + ...state, + price: { + ...state.price, + value + } + }; +} + +function changeApplyStockToAllValue( + state: ProductVariantCreateFormData, + value: string +): ProductVariantCreateFormData { + return { + ...state, + stock: { + ...state.stock, + value + } + }; +} + +function reduceProductVariantCreateFormData( + prevState: ProductVariantCreateFormData, + action: ProductVariantCreateReducerAction +) { + switch (action.type) { + case "selectAttribute": + return selectAttribute(prevState, action.id); + + case "selectValue": + return selectValue(prevState, action.id); + + case "applyPriceToAll": + return applyPriceToAll(prevState, action.all); + case "applyStockToAll": + return applyStockToAll(prevState, action.all); + case "changeAttributePrice": + return changeAttributePrice(prevState, action.id, action.value); + case "changeAttributeStock": + return changeAttributeStock(prevState, action.id, action.value); + case "changeApplyPriceToAttributeId": + return changeApplyPriceToAttributeId(prevState, action.id); + case "changeApplyStockToAttributeId": + return changeApplyStockToAttributeId(prevState, action.id); + case "changeApplyPriceToAllValue": + return changeApplyPriceToAllValue(prevState, action.value); + case "changeApplyStockToAllValue": + return changeApplyStockToAllValue(prevState, action.value); + } + return prevState; +} + +export default reduceProductVariantCreateFormData; diff --git a/src/products/components/ProductVariantCreateDialog/types.ts b/src/products/components/ProductVariantCreateDialog/types.ts new file mode 100644 index 000000000..944b94e89 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/types.ts @@ -0,0 +1,5 @@ +export type ProductVariantCreateStep = + | "attributes" + | "values" + | "prices" + | "summary"; From b87b0dcb6c5cde52cce229832ee90ff31196b6c6 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 19 Sep 2019 12:13:21 +0200 Subject: [PATCH 03/48] Fix invisible adornments --- src/components/Timeline/Timeline.tsx | 3 +++ src/theme.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Timeline/Timeline.tsx b/src/components/Timeline/Timeline.tsx index 0bb77b6b5..1c449c8e8 100644 --- a/src/components/Timeline/Timeline.tsx +++ b/src/components/Timeline/Timeline.tsx @@ -30,6 +30,9 @@ const styles = (theme: Theme) => "& > div": { padding: "0 14px" }, + "& fieldset": { + background: theme.palette.background.paper + }, "& textarea": { "&::placeholder": { opacity: [[1], "!important"] as any diff --git a/src/theme.ts b/src/theme.ts index c8d42b7a8..e523b0d45 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -281,8 +281,7 @@ export default (colors: IThemeColors): Theme => "& fieldset": { "&&:not($error)": { borderColor: colors.input.border - }, - background: colors.background.paper + } }, "& legend": { display: "none" From 742523bac721e9c67404ee3e6f6bf22e1006f1d7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 19 Sep 2019 12:13:48 +0200 Subject: [PATCH 04/48] Fix variant create query --- .../ProductVariantCreateDialog/index.ts | 0 src/products/mutations.ts | 22 ++----------------- src/products/queries.ts | 10 +++++++++ src/products/types/ProductDetails.ts | 14 ++++++++++++ src/products/types/VariantCreate.ts | 10 ++------- src/types/globalTypes.ts | 11 ++++++++++ 6 files changed, 39 insertions(+), 28 deletions(-) create mode 100644 src/products/components/ProductVariantCreateDialog/index.ts diff --git a/src/products/components/ProductVariantCreateDialog/index.ts b/src/products/components/ProductVariantCreateDialog/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/products/mutations.ts b/src/products/mutations.ts index ef7024d69..563c09f84 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -319,26 +319,8 @@ export const TypedVariantUpdateMutation = TypedMutation< export const variantCreateMutation = gql` ${fragmentVariant} - mutation VariantCreate( - $attributes: [AttributeValueInput]! - $costPrice: Decimal - $priceOverride: Decimal - $product: ID! - $sku: String - $quantity: Int - $trackInventory: Boolean! - ) { - productVariantCreate( - input: { - attributes: $attributes - costPrice: $costPrice - priceOverride: $priceOverride - product: $product - sku: $sku - quantity: $quantity - trackInventory: $trackInventory - } - ) { + mutation VariantCreate($input: ProductVariantCreateInput!) { + productVariantCreate(input: $input) { errors { field message diff --git a/src/products/queries.ts b/src/products/queries.ts index fa60db7cb..efbdb9489 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -260,6 +260,16 @@ const productDetailsQuery = gql` query ProductDetails($id: ID!) { product(id: $id) { ...Product + productType { + variantAttributes { + id + name + values { + id + name + } + } + } } } `; diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index 5094afd59..803e66d8d 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -139,11 +139,25 @@ export interface ProductDetails_product_variants { stockQuantity: number; } +export interface ProductDetails_product_productType_variantAttributes_values { + __typename: "AttributeValue"; + id: string; + name: string | null; +} + +export interface ProductDetails_product_productType_variantAttributes { + __typename: "Attribute"; + id: string; + name: string | null; + values: (ProductDetails_product_productType_variantAttributes_values | null)[] | null; +} + export interface ProductDetails_product_productType { __typename: "ProductType"; id: string; name: string; hasVariants: boolean; + variantAttributes: (ProductDetails_product_productType_variantAttributes | null)[] | null; } export interface ProductDetails_product { diff --git a/src/products/types/VariantCreate.ts b/src/products/types/VariantCreate.ts index 4dcfdd5d1..b1d2783b1 100644 --- a/src/products/types/VariantCreate.ts +++ b/src/products/types/VariantCreate.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeValueInput } from "./../../types/globalTypes"; +import { ProductVariantCreateInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: VariantCreate @@ -122,11 +122,5 @@ export interface VariantCreate { } export interface VariantCreateVariables { - attributes: (AttributeValueInput | null)[]; - costPrice?: any | null; - priceOverride?: any | null; - product: string; - sku?: string | null; - quantity?: number | null; - trackInventory: boolean; + input: ProductVariantCreateInput; } diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 1e8cfaf3a..5dff42bb4 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -614,6 +614,17 @@ export interface ProductTypeInput { taxCode?: string | null; } +export interface ProductVariantCreateInput { + attributes: (AttributeValueInput | null)[]; + costPrice?: any | null; + priceOverride?: any | null; + sku?: string | null; + quantity?: number | null; + trackInventory?: boolean | null; + weight?: any | null; + product: string; +} + export interface ProductVariantInput { attributes?: (AttributeValueInput | null)[] | null; costPrice?: any | null; From 2879a80d0d4deb52b4d4c1080b8121b1efe859fd Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 23 Sep 2019 17:19:41 +0200 Subject: [PATCH 05/48] Add variant matrix generation --- .../ProductVariantCreate.stories.tsx | 79 +-- .../ProductVariantCreateAttributes.tsx | 2 +- .../ProductVariantCreateContent.tsx | 31 +- .../ProductVariantCreateDialog.tsx | 2 +- .../ProductVariantCreatePrices.tsx | 127 ++--- .../ProductVariantCreateSummary.tsx | 6 +- .../ProductVariantCreateValues.tsx | 12 +- .../__snapshots__/reducer.test.ts.snap | 457 ++++++++++++++++++ .../createVariants.test.ts | 43 ++ .../createVariants.ts | 89 ++++ .../ProductVariantCreateDialog/fixtures.ts | 63 +++ .../ProductVariantCreateDialog/form.ts | 12 +- .../reducer.test.ts | 171 +++++++ .../ProductVariantCreateDialog/reducer.ts | 130 +++-- 14 files changed, 1076 insertions(+), 148 deletions(-) create mode 100644 src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap create mode 100644 src/products/components/ProductVariantCreateDialog/createVariants.test.ts create mode 100644 src/products/components/ProductVariantCreateDialog/createVariants.ts create mode 100644 src/products/components/ProductVariantCreateDialog/fixtures.ts create mode 100644 src/products/components/ProductVariantCreateDialog/reducer.test.ts diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx index d855f58ab..f181027ed 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -4,55 +4,56 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { attributes } from "@saleor/attributes/fixtures"; -import { isSelected } from "@saleor/utils/lists"; import Decorator from "../../../storybook/Decorator"; +import { createVariants } from "./createVariants"; +import { AllOrAttribute } from "./form"; import ProductVariantCreateContent, { ProductVariantCreateContentProps } from "./ProductVariantCreateContent"; import ProductVariantCreateDialog from "./ProductVariantCreateDialog"; -const selectedAttributes = [1, 2, 4].map(index => attributes[index].id); -const selectedValues = attributes - .filter(attribute => - isSelected(attribute.id, selectedAttributes, (a, b) => a === b) - ) - .map(attribute => attribute.values.map(value => value.id)) - .reduce((acc, curr) => [...acc, ...curr], []) - .filter((_, valueIndex) => valueIndex % 2); +const selectedAttributes = [1, 4, 5].map(index => attributes[index]); + +const price: AllOrAttribute = { + all: false, + attribute: selectedAttributes[1].id, + value: "2.79", + values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ + id: attribute.id, + value: (attributeIndex + 4).toFixed(2) + })) +}; + +const stock: AllOrAttribute = { + all: false, + attribute: selectedAttributes[1].id, + value: "8", + values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ + id: attribute.id, + value: (selectedAttributes.length * 10 - attributeIndex).toString() + })) +}; + +const dataAttributes = selectedAttributes.map(attribute => ({ + id: attribute.id, + values: attribute.values + .map(value => value.id) + .filter((_, valueIndex) => valueIndex % 2 !== 1) +})); const props: ProductVariantCreateContentProps = { attributes, currencySymbol: "USD", data: { - attributes: selectedAttributes, - price: { - all: false, - attribute: selectedAttributes[1], - value: "2.79", - values: selectedAttributes.map((_, attributeIndex) => - (attributeIndex + 4).toFixed(2) - ) - }, - stock: { - all: false, - attribute: selectedAttributes[1], - value: "8", - values: selectedAttributes.map((_, attributeIndex) => - (selectedAttributes.length * 10 - attributeIndex).toString() - ) - }, - values: selectedValues, - variants: [ - { - attributes: attributes - .filter(attribute => selectedAttributes.includes(attribute.id)) - .map(attribute => ({ - id: attribute.id, - values: [attribute.values[0].id] - })), - product: "=1uahc98nas" - } - ] + attributes: dataAttributes, + price, + stock, + variants: createVariants({ + attributes: dataAttributes, + price, + stock, + variants: [] + }) }, dispatchFormDataAction: () => undefined, step: "attributes" @@ -64,7 +65,7 @@ storiesOf("Views / Products / Create multiple variants", module) style={{ margin: "auto", overflow: "visible", - width: 600 + width: 800 }} > {storyFn()} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx index 2b176c7b1..5de61f721 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx @@ -43,7 +43,7 @@ const ProductVariantCreateAttributes: React.FC< return null; } const isChecked = !!data.attributes.find( - selectedAttribute => selectedAttribute === attribute.id + selectedAttribute => selectedAttribute.id === attribute.id ); return ( diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 87a0ccd83..99bb9a27e 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -40,7 +40,11 @@ const ProductVariantCreateContent: React.FC< const classes = useStyles(props); const selectedAttributes = attributes.filter(attribute => - isSelected(attribute.id, data.attributes, (a, b) => a === b) + isSelected( + attribute.id, + data.attributes.map(dataAttribute => dataAttribute.id), + (a, b) => a === b + ) ); return ( @@ -51,9 +55,9 @@ const ProductVariantCreateContent: React.FC< + onAttributeClick={attributeId => dispatchFormDataAction({ - id, + attributeId, type: "selectAttribute" }) } @@ -63,10 +67,11 @@ const ProductVariantCreateContent: React.FC< + onValueClick={(attributeId, valueId) => dispatchFormDataAction({ - id, - type: "selectValue" + attributeId, + type: "selectValue", + valueId }) } /> @@ -90,19 +95,23 @@ const ProductVariantCreateContent: React.FC< value }) } - onAttributeSelect={(id, type) => + onAttributeSelect={(attributeId, type) => dispatchFormDataAction({ - id, + attributeId, type: type === "price" ? "changeApplyPriceToAttributeId" : "changeApplyStockToAttributeId" }) } - onValueClick={id => + onAttributeValueChange={(valueId, value, type) => dispatchFormDataAction({ - id, - type: "selectValue" + type: + type === "price" + ? "changeAttributeValuePrice" + : "changeAttributeValueStock", + value, + valueId }) } /> diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index 4143e63a3..8ba0e5a9d 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -21,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) => ({ content: { overflowX: "visible", overflowY: "hidden", - width: 600 + width: 800 } })); diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index 324feb070..b81f4a54b 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -13,7 +13,6 @@ import Grid from "@saleor/components/Grid"; import Hr from "@saleor/components/Hr"; import SingleSelectField from "@saleor/components/SingleSelectField"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; -import { isSelected } from "@saleor/utils/lists"; import { ProductVariantCreateFormData } from "./form"; const useStyles = makeStyles((theme: Theme) => ({ @@ -33,10 +32,14 @@ export type PriceOrStock = "price" | "stock"; export interface ProductVariantCreatePricesProps { attributes: ProductDetails_product_productType_variantAttributes[]; data: ProductVariantCreateFormData; - onValueClick: (id: string) => void; - onAttributeSelect: (id: string, type: PriceOrStock) => void; onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void; onApplyToAllChange: (value: string, type: PriceOrStock) => void; + onAttributeSelect: (id: string, type: PriceOrStock) => void; + onAttributeValueChange: ( + id: string, + value: string, + type: PriceOrStock + ) => void; } const ProductVariantCreatePrices: React.FC< @@ -47,31 +50,25 @@ const ProductVariantCreatePrices: React.FC< data, onApplyPriceOrStockChange, onApplyToAllChange, - onAttributeSelect + onAttributeSelect, + onAttributeValueChange } = props; const classes = useStyles(props); const intl = useIntl(); - const selectedAttributes = attributes.filter(attribute => - isSelected(attribute.id, data.attributes, (a, b) => a === b) - ); - const attributeChoices = selectedAttributes.map(attribute => ({ + const attributeChoices = attributes.map(attribute => ({ label: attribute.name, value: attribute.id })); const priceAttributeValues = data.price.all ? null : data.price.attribute - ? selectedAttributes.find( - attribute => attribute.id === data.price.attribute - ).values + ? attributes.find(attribute => attribute.id === data.price.attribute).values : []; const stockAttributeValues = data.stock.all ? null : data.stock.attribute - ? selectedAttributes.find( - attribute => attribute.id === data.stock.attribute - ).values + ? attributes.find(attribute => attribute.id === data.stock.attribute).values : []; return ( @@ -142,27 +139,36 @@ const ProductVariantCreatePrices: React.FC< {priceAttributeValues && - priceAttributeValues.map((attribute, attributeIndex) => ( - <> - - -
- {attribute.name} -
-
- -
-
- - ))} + priceAttributeValues.map( + (attributeValue, attributeValueIndex) => ( + <> + + +
+ {attributeValue.name} +
+
+ + onAttributeValueChange( + attributeValue.id, + event.target.value, + "price" + ) + } + /> +
+
+ + ) + )} )} @@ -233,27 +239,36 @@ const ProductVariantCreatePrices: React.FC< {stockAttributeValues && - stockAttributeValues.map((attribute, attributeIndex) => ( - <> - - -
- {attribute.name} -
-
- -
-
- - ))} + stockAttributeValues.map( + (attributeValue, attributeValueIndex) => ( + <> + + +
+ {attributeValue.name} +
+
+ + onAttributeValueChange( + attributeValue.id, + event.target.value, + "stock" + ) + } + /> +
+
+ + ) + )} )} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index c2953e425..210a6d4fa 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -32,13 +32,13 @@ const useStyles = makeStyles((theme: Theme) => ({ width: "auto" }, colPrice: { - width: 110 + width: 160 }, colSku: { - width: 110 + width: 210 }, colStock: { - width: 110 + width: 160 }, hr: { marginBottom: theme.spacing.unit, diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx index 84327bfd5..801dc222f 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -14,7 +14,7 @@ import { ProductVariantCreateFormData } from "./form"; export interface ProductVariantCreateValuesProps { attributes: ProductDetails_product_productType_variantAttributes[]; data: ProductVariantCreateFormData; - onValueClick: (id: string) => void; + onValueClick: (attributeId: string, valueId: string) => void; } const useStyles = makeStyles((theme: Theme) => ({ @@ -47,10 +47,16 @@ const ProductVariantCreateValues: React.FC<
{attribute.values.map(value => ( a === b)} + checked={isSelected( + value.id, + data.attributes.find( + dataAttribute => attribute.id === dataAttribute.id + ).values, + (a, b) => a === b + )} name={`value:${value.id}`} label={value.name} - onChange={() => onValueClick(value.id)} + onChange={() => onValueClick(attribute.id, value.id)} /> ))}
diff --git a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap new file mode 100644 index 000000000..d1b59f93b --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap @@ -0,0 +1,457 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Reducer is able to select attribute values 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + "val-4-5", + ], + }, + ], + "price": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "stock": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "variants": Array [], +} +`; + +exports[`Reducer is able to select attributes 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [], + }, + Object { + "id": "attr-2", + "values": Array [], + }, + Object { + "id": "attr-4", + "values": Array [], + }, + ], + "price": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "stock": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "variants": Array [], +} +`; + +exports[`Reducer is able to select price for all variants 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + "val-4-5", + ], + }, + ], + "price": Object { + "all": true, + "attribute": undefined, + "value": "45.99", + "values": Array [], + }, + "stock": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "variants": Array [], +} +`; + +exports[`Reducer is able to select price to each attribute value 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + "val-4-5", + ], + }, + ], + "price": Object { + "all": false, + "attribute": "attr-1", + "value": "", + "values": Array [ + Object { + "id": "val-1-1", + "value": "45.99", + }, + Object { + "id": "val-1-7", + "value": "51.99", + }, + ], + }, + "stock": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "variants": Array [], +} +`; + +exports[`Reducer is able to select stock for all variants 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + "val-4-5", + ], + }, + ], + "price": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "stock": Object { + "all": true, + "attribute": undefined, + "value": "45.99", + "values": Array [], + }, + "variants": Array [ + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 45, + }, + ], +} +`; + +exports[`Reducer is able to select stock to each attribute value 1`] = ` +Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + "val-4-5", + ], + }, + ], + "price": Object { + "all": true, + "attribute": undefined, + "value": "", + "values": Array [], + }, + "stock": Object { + "all": false, + "attribute": "attr-1", + "value": "", + "values": Array [ + Object { + "id": "val-1-1", + "value": "13", + }, + Object { + "id": "val-1-7", + "value": "19", + }, + ], + }, + "variants": Array [], +} +`; diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts new file mode 100644 index 000000000..86b2230e9 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts @@ -0,0 +1,43 @@ +import { + createVariantFlatMatrixDimension, + createVariants +} from "./createVariants"; +import { thirdStep } from "./fixtures"; +import { ProductVariantCreateFormData } from "./form"; + +describe("Creates variant matrix", () => { + it("with proper size", () => { + const attributes = thirdStep.attributes; + + const matrix = createVariantFlatMatrixDimension([[]], attributes); + + expect(matrix).toHaveLength( + attributes.reduce((acc, attribute) => acc * attribute.values.length, 1) + ); + }); + + it("with constant price and stock", () => { + const price = "49.99"; + const stock = 80; + + const data: ProductVariantCreateFormData = { + ...thirdStep, + price: { + ...thirdStep.price, + all: true, + value: price + }, + stock: { + ...thirdStep.stock, + all: true, + value: stock.toString() + } + }; + + const variants = createVariants(data); + variants.forEach(variant => { + expect(variant.priceOverride).toBe(price); + expect(variant.quantity).toBe(stock); + }); + }); +}); diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.ts b/src/products/components/ProductVariantCreateDialog/createVariants.ts new file mode 100644 index 000000000..fa8072136 --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/createVariants.ts @@ -0,0 +1,89 @@ +import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { Attribute, ProductVariantCreateFormData } from "./form"; + +interface CreateVariantAttributeValueInput { + attributeId: string; + attributeValueId: string; +} +type CreateVariantInput = CreateVariantAttributeValueInput[]; +function createVariant( + data: ProductVariantCreateFormData, + attributes: CreateVariantInput +): ProductVariantCreateInput { + const priceOverride = data.price.all + ? data.price.value + : data.price.values.find( + value => + attributes.find( + attribute => attribute.attributeId === data.price.attribute + ).attributeValueId === value.id + ).value; + const quantity = parseInt( + data.stock.all + ? data.stock.value + : data.stock.values.find( + value => + attributes.find( + attribute => attribute.attributeId === data.stock.attribute + ).attributeValueId === value.id + ).value, + 10 + ); + + return { + attributes: attributes.map(attribute => ({ + id: attribute.attributeId, + values: [attribute.attributeValueId] + })), + priceOverride, + product: "", + quantity + }; +} + +function addAttributeToVariant( + attribute: Attribute, + variant: CreateVariantInput +): CreateVariantInput[] { + return attribute.values.map(attributeValueId => [ + ...variant, + { + attributeId: attribute.id, + attributeValueId + } + ]); +} +function addVariantAttributeInput( + data: CreateVariantInput[], + attribute: Attribute +): CreateVariantInput[] { + const variants = data + .map(variant => addAttributeToVariant(attribute, variant)) + .reduce((acc, variantInput) => [...acc, ...variantInput]); + + return variants; +} + +export function createVariantFlatMatrixDimension( + variants: CreateVariantInput[], + attributes: Attribute[] +): CreateVariantInput[] { + if (attributes.length > 0) { + return createVariantFlatMatrixDimension( + addVariantAttributeInput(variants, attributes[0]), + attributes.slice(1) + ); + } else { + return variants; + } +} + +export function createVariants( + data: ProductVariantCreateFormData +): ProductVariantCreateInput[] { + const variants = createVariantFlatMatrixDimension([[]], data.attributes).map( + variant => createVariant(data, variant) + ); + + return variants; +} diff --git a/src/products/components/ProductVariantCreateDialog/fixtures.ts b/src/products/components/ProductVariantCreateDialog/fixtures.ts new file mode 100644 index 000000000..1a6180a2e --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/fixtures.ts @@ -0,0 +1,63 @@ +import { initialForm, ProductVariantCreateFormData } from "./form"; + +export const attributes = [ + { + id: "attr-1", + values: Array(9) + .fill(0) + .map((_, index) => `val-1-${index + 1}`) + }, + { + id: "attr-2", + values: Array(6) + .fill(0) + .map((_, index) => `val-2-${index + 1}`) + }, + { + id: "attr-3", + values: Array(4) + .fill(0) + .map((_, index) => `val-3-${index + 1}`) + }, + { + id: "attr-4", + values: Array(11) + .fill(0) + .map((_, index) => `val-4-${index + 1}`) + } +]; + +export const secondStep: ProductVariantCreateFormData = { + ...initialForm, + attributes: [ + { + id: attributes[0].id, + values: [] + }, + { + id: attributes[1].id, + values: [] + }, + { + id: attributes[3].id, + values: [] + } + ] +}; +export const thirdStep: ProductVariantCreateFormData = { + ...secondStep, + attributes: [ + { + id: attributes[0].id, + values: [0, 6].map(index => attributes[0].values[index]) + }, + { + id: attributes[1].id, + values: [1, 3].map(index => attributes[1].values[index]) + }, + { + id: attributes[3].id, + values: [0, 4].map(index => attributes[3].values[index]) + } + ] +}; diff --git a/src/products/components/ProductVariantCreateDialog/form.ts b/src/products/components/ProductVariantCreateDialog/form.ts index 503ffbc8b..157ad2c55 100644 --- a/src/products/components/ProductVariantCreateDialog/form.ts +++ b/src/products/components/ProductVariantCreateDialog/form.ts @@ -1,16 +1,23 @@ import { ProductVariantCreateInput } from "../../../types/globalTypes"; +export interface AttributeValue { + id: string; + value: string; +} export interface AllOrAttribute { all: boolean; attribute: string; value: string; + values: AttributeValue[]; +} +export interface Attribute { + id: string; values: string[]; } export interface ProductVariantCreateFormData { - attributes: string[]; + attributes: Attribute[]; price: AllOrAttribute; stock: AllOrAttribute; - values: string[]; variants: ProductVariantCreateInput[]; } @@ -28,6 +35,5 @@ export const initialForm: ProductVariantCreateFormData = { value: "", values: [] }, - values: [], variants: [] }; diff --git a/src/products/components/ProductVariantCreateDialog/reducer.test.ts b/src/products/components/ProductVariantCreateDialog/reducer.test.ts new file mode 100644 index 000000000..fecca566c --- /dev/null +++ b/src/products/components/ProductVariantCreateDialog/reducer.test.ts @@ -0,0 +1,171 @@ +import { attributes, secondStep, thirdStep } from "./fixtures"; +import { initialForm } from "./form"; +import reducer from "./reducer"; + +function execActions( + initialState: TState, + reducer: (state: TState, action: TAction) => TState, + actions: TAction[] +): TState { + return actions.reduce((acc, action) => reducer(acc, action), initialState); +} + +describe("Reducer is able to", () => { + it("select attributes", () => { + const state = execActions(initialForm, reducer, [ + { + attributeId: attributes[0].id, + type: "selectAttribute" + }, + { + attributeId: attributes[1].id, + type: "selectAttribute" + }, + { + attributeId: attributes[3].id, + type: "selectAttribute" + } + ]); + + expect(state.attributes).toHaveLength(3); + expect(state).toMatchSnapshot(); + }); + + it("select attribute values", () => { + const state = execActions(secondStep, reducer, [ + { + attributeId: attributes[0].id, + type: "selectValue", + valueId: attributes[0].values[0] + }, + { + attributeId: attributes[0].id, + type: "selectValue", + valueId: attributes[0].values[6] + }, + { + attributeId: attributes[1].id, + type: "selectValue", + valueId: attributes[1].values[1] + }, + { + attributeId: attributes[1].id, + type: "selectValue", + valueId: attributes[1].values[3] + }, + { + attributeId: attributes[3].id, + type: "selectValue", + valueId: attributes[3].values[0] + }, + { + attributeId: attributes[3].id, + type: "selectValue", + valueId: attributes[3].values[4] + } + ]); + + expect(state.attributes[0].values).toHaveLength(2); + expect(state.attributes[1].values).toHaveLength(2); + expect(state.attributes[2].values).toHaveLength(2); + expect(state).toMatchSnapshot(); + }); + + it("select price for all variants", () => { + const value = "45.99"; + const state = execActions(thirdStep, reducer, [ + { + all: true, + type: "applyPriceToAll" + }, + { + type: "changeApplyPriceToAllValue", + value + } + ]); + + expect(state.price.all).toBeTruthy(); + expect(state.price.value).toBe(value); + expect(state).toMatchSnapshot(); + }); + + it("select stock for all variants", () => { + const value = 45.99; + const state = execActions(thirdStep, reducer, [ + { + all: true, + type: "applyStockToAll" + }, + { + type: "changeApplyStockToAllValue", + value: value.toString() + } + ]); + + expect(state.stock.all).toBeTruthy(); + expect(state.stock.value).toBe(value.toString()); + expect(state).toMatchSnapshot(); + }); + + it("select price to each attribute value", () => { + const value = 45.99; + const state = execActions(thirdStep, reducer, [ + { + all: false, + type: "applyPriceToAll" + }, + { + attributeId: attributes[0].id, + type: "changeApplyPriceToAttributeId" + }, + { + type: "changeAttributeValuePrice", + value: value.toString(), + valueId: attributes[0].values[0] + }, + { + type: "changeAttributeValuePrice", + value: (value + 6).toString(), + valueId: attributes[0].values[6] + } + ]); + + expect(state.price.all).toBeFalsy(); + expect(state.price.values).toHaveLength( + state.attributes.find(attribute => state.price.attribute === attribute.id) + .values.length + ); + expect(state).toMatchSnapshot(); + }); + + it("select stock to each attribute value", () => { + const value = 13; + const state = execActions(thirdStep, reducer, [ + { + all: false, + type: "applyStockToAll" + }, + { + attributeId: attributes[0].id, + type: "changeApplyStockToAttributeId" + }, + { + type: "changeAttributeValueStock", + value: value.toString(), + valueId: attributes[0].values[0] + }, + { + type: "changeAttributeValueStock", + value: (value + 6).toString(), + valueId: attributes[0].values[6] + } + ]); + + expect(state.stock.all).toBeFalsy(); + expect(state.stock.values).toHaveLength( + state.attributes.find(attribute => state.stock.attribute === attribute.id) + .values.length + ); + expect(state).toMatchSnapshot(); + }); +}); diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index 25379b91d..e8954066c 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -1,4 +1,5 @@ -import { toggle, updateAtIndex } from "@saleor/utils/lists"; +import { add, remove, toggle, updateAtIndex } from "@saleor/utils/lists"; +import { createVariants } from "./createVariants"; import { initialForm, ProductVariantCreateFormData } from "./form"; export type ProductVariantCreateReducerActionType = @@ -10,22 +11,30 @@ export type ProductVariantCreateReducerActionType = | "changeApplyPriceToAttributeId" | "changeApplyStockToAllValue" | "changeApplyStockToAttributeId" - | "changeAttributePrice" - | "changeAttributeStock" + | "changeAttributeValuePrice" + | "changeAttributeValueStock" | "selectAttribute" | "selectValue"; export interface ProductVariantCreateReducerAction { all?: boolean; - id?: string; + attributeId?: string; type: ProductVariantCreateReducerActionType; value?: string; + valueId?: string; } function selectAttribute( state: ProductVariantCreateFormData, - attribute: string + attributeId: string ): ProductVariantCreateFormData { - const attributes = toggle(attribute, state.attributes, (a, b) => a === b); + const attributes = toggle( + { + id: attributeId, + values: [] + }, + state.attributes, + (a, b) => a.id === b.id + ); return { ...initialForm, @@ -35,14 +44,24 @@ function selectAttribute( function selectValue( state: ProductVariantCreateFormData, - value: string + attributeId: string, + valueId: string ): ProductVariantCreateFormData { - const values = toggle(value, state.values, (a, b) => a === b); + const attribute = state.attributes.find( + attribute => attribute.id === attributeId + ); + const values = toggle(valueId, attribute.values, (a, b) => a === b); + const updatedAttributes = add( + { + id: attributeId, + values + }, + remove(attribute, state.attributes, (a, b) => a.id === b.id) + ); return { ...initialForm, - attributes: state.attributes, - values + attributes: updatedAttributes }; } @@ -72,13 +91,27 @@ function applyStockToAll( }; } -function changeAttributePrice( +function changeAttributeValuePrice( state: ProductVariantCreateFormData, - attribute: string, + attributeValueId: string, price: string ): ProductVariantCreateFormData { - const index = state.price.values.indexOf(attribute); - const values = updateAtIndex(price, state.price.values, index); + const index = state.price.values.findIndex( + value => value.id === attributeValueId + ); + + if (index === -1) { + throw new Error(`Value with id ${attributeValueId} not found`); + } + + const values = updateAtIndex( + { + id: attributeValueId, + value: price + }, + state.price.values, + index + ); return { ...state, @@ -89,13 +122,27 @@ function changeAttributePrice( }; } -function changeAttributeStock( +function changeAttributeValueStock( state: ProductVariantCreateFormData, - attribute: string, + attributeValueId: string, stock: string ): ProductVariantCreateFormData { - const index = state.stock.values.indexOf(attribute); - const values = updateAtIndex(stock, state.stock.values, index); + const index = state.stock.values.findIndex( + value => value.id === attributeValueId + ); + + if (index === -1) { + throw new Error(`Value with id ${attributeValueId} not found`); + } + + const values = updateAtIndex( + { + id: attributeValueId, + value: stock + }, + state.stock.values, + index + ); return { ...state, @@ -108,13 +155,22 @@ function changeAttributeStock( function changeApplyPriceToAttributeId( state: ProductVariantCreateFormData, - attribute: string + attributeId: string ): ProductVariantCreateFormData { + const attribute = state.attributes.find( + attribute => attribute.id === attributeId + ); + const values = attribute.values.map(id => ({ + id, + value: "" + })); + return { ...state, price: { ...state.price, - attribute + attribute: attributeId, + values } }; } @@ -127,7 +183,13 @@ function changeApplyStockToAttributeId( ...state, stock: { ...state.stock, - attribute + attribute, + values: state.attributes + .find(stateAttribute => stateAttribute.id === attribute) + .values.map(attributeValue => ({ + id: attributeValue, + value: "" + })) } }; } @@ -149,13 +211,18 @@ function changeApplyStockToAllValue( state: ProductVariantCreateFormData, value: string ): ProductVariantCreateFormData { - return { + const data = { ...state, stock: { ...state.stock, value } }; + + return { + ...data, + variants: createVariants(data) + }; } function reduceProductVariantCreateFormData( @@ -164,29 +231,30 @@ function reduceProductVariantCreateFormData( ) { switch (action.type) { case "selectAttribute": - return selectAttribute(prevState, action.id); + return selectAttribute(prevState, action.attributeId); case "selectValue": - return selectValue(prevState, action.id); + return selectValue(prevState, action.attributeId, action.valueId); case "applyPriceToAll": return applyPriceToAll(prevState, action.all); case "applyStockToAll": return applyStockToAll(prevState, action.all); - case "changeAttributePrice": - return changeAttributePrice(prevState, action.id, action.value); - case "changeAttributeStock": - return changeAttributeStock(prevState, action.id, action.value); + case "changeAttributeValuePrice": + return changeAttributeValuePrice(prevState, action.valueId, action.value); + case "changeAttributeValueStock": + return changeAttributeValueStock(prevState, action.valueId, action.value); case "changeApplyPriceToAttributeId": - return changeApplyPriceToAttributeId(prevState, action.id); + return changeApplyPriceToAttributeId(prevState, action.attributeId); case "changeApplyStockToAttributeId": - return changeApplyStockToAttributeId(prevState, action.id); + return changeApplyStockToAttributeId(prevState, action.attributeId); case "changeApplyPriceToAllValue": return changeApplyPriceToAllValue(prevState, action.value); case "changeApplyStockToAllValue": return changeApplyStockToAllValue(prevState, action.value); + default: + return prevState; } - return prevState; } export default reduceProductVariantCreateFormData; From ddb2bc1bd445927fa00a1cb55537bca77a1295a9 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 23 Sep 2019 17:21:26 +0200 Subject: [PATCH 06/48] Fill cell with input --- .../ProductVariantCreateDialog/ProductVariantCreateSummary.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 210a6d4fa..5b4deb24d 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -48,8 +48,7 @@ const useStyles = makeStyles((theme: Theme) => ({ "& input": { padding: "16px 12px 17px" }, - marginTop: theme.spacing.unit / 2, - width: 104 + marginTop: theme.spacing.unit / 2 } })); From 038d6e5580dff38834fd05c4ee7fbb1202f1ab5e Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 23 Sep 2019 17:27:01 +0200 Subject: [PATCH 07/48] Add value coloring --- .../ProductVariantCreateSummary.tsx | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 5b4deb24d..746ceea89 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -1,3 +1,8 @@ +import blue from "@material-ui/core/colors/blue"; +import cyan from "@material-ui/core/colors/cyan"; +import green from "@material-ui/core/colors/green"; +import purple from "@material-ui/core/colors/purple"; +import yellow from "@material-ui/core/colors/yellow"; import { Theme } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; @@ -9,7 +14,7 @@ import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; import classNames from "classnames"; import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import Hr from "@saleor/components/Hr"; import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; @@ -22,7 +27,12 @@ export interface ProductVariantCreateSummaryProps { data: ProductVariantCreateFormData; } +const colors = [blue, cyan, green, purple, yellow].map(color => color[500]); + const useStyles = makeStyles((theme: Theme) => ({ + attributeValue: { + marginRight: theme.spacing.unit + }, col: { paddingLeft: theme.spacing.unit, paddingRight: theme.spacing.unit @@ -118,7 +128,18 @@ const ProductVariantCreateSummary: React.FC< key={variant.attributes.map(attribute => attribute.id).join(":")} > - {getVariantName(variant, attributes).join(" ")} + {getVariantName(variant, attributes).map( + (value, valueIndex) => ( + + {value} + + ) + )} Date: Mon, 23 Sep 2019 17:32:00 +0200 Subject: [PATCH 08/48] Visual fixes --- .../ProductVariantCreateSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 746ceea89..4d695d5c6 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -42,13 +42,13 @@ const useStyles = makeStyles((theme: Theme) => ({ width: "auto" }, colPrice: { - width: 160 + width: 200 }, colSku: { width: 210 }, colStock: { - width: 160 + width: 120 }, hr: { marginBottom: theme.spacing.unit, From fcfdd432b4df479acb50b3fb64983329b163975b Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 24 Sep 2019 14:27:11 +0200 Subject: [PATCH 09/48] Make it work --- .../ProductVariantCreatePrices.tsx | 232 +++++++++--------- .../ProductVariantCreateSummary.tsx | 4 +- .../createVariants.test.ts | 179 +++++++++++++- .../createVariants.ts | 42 +++- .../ProductVariantCreateDialog/fixtures.ts | 49 +++- .../reducer.test.ts | 38 ++- .../ProductVariantCreateDialog/reducer.ts | 102 ++++++-- 7 files changed, 488 insertions(+), 158 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index b81f4a54b..6a1be87ec 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -112,66 +112,64 @@ const ProductVariantCreatePrices: React.FC< })} onChange={() => onApplyPriceOrStockChange(false, "price")} /> - {!data.price.all && ( - <> - - -
- - - -
-
- - onAttributeSelect(event.target.value, "price") - } - /> -
-
- {priceAttributeValues && - priceAttributeValues.map( - (attributeValue, attributeValueIndex) => ( - <> - - -
- {attributeValue.name} -
-
- - onAttributeValueChange( - attributeValue.id, - event.target.value, - "price" - ) - } - /> -
-
- - ) - )} - - )} + {!data.price.all && ( + <> + + +
+ + + +
+
+ + onAttributeSelect(event.target.value, "price") + } + /> +
+
+ {priceAttributeValues && + priceAttributeValues.map((attributeValue, attributeValueIndex) => ( + <> + + +
+ {attributeValue.name} +
+
+ + onAttributeValueChange( + attributeValue.id, + event.target.value, + "price" + ) + } + /> +
+
+ + ))} + + )} onApplyPriceOrStockChange(false, "stock")} /> - {!data.stock.all && ( - <> - - -
- - - -
-
- - onAttributeSelect(event.target.value, "stock") - } - /> -
-
- {stockAttributeValues && - stockAttributeValues.map( - (attributeValue, attributeValueIndex) => ( - <> - - -
- {attributeValue.name} -
-
- - onAttributeValueChange( - attributeValue.id, - event.target.value, - "stock" - ) - } - /> -
-
- - ) - )} - - )} + {!data.stock.all && ( + <> + + +
+ + + +
+
+ + onAttributeSelect(event.target.value, "stock") + } + /> +
+
+ {stockAttributeValues && + stockAttributeValues.map((attributeValue, attributeValueIndex) => ( + <> + + +
+ {attributeValue.name} +
+
+ + onAttributeValueChange( + attributeValue.id, + event.target.value, + "stock" + ) + } + /> +
+
+ + ))} + + )} ); }; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 4d695d5c6..62ef027e2 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -125,7 +125,9 @@ const ProductVariantCreateSummary: React.FC< {data.variants.map(variant => ( attribute.id).join(":")} + key={variant.attributes + .map(attribute => attribute.values[0]) + .join(":")} > {getVariantName(variant, attributes).map( diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts index 86b2230e9..1ecc83b45 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts @@ -2,7 +2,7 @@ import { createVariantFlatMatrixDimension, createVariants } from "./createVariants"; -import { thirdStep } from "./fixtures"; +import { attributes, thirdStep } from "./fixtures"; import { ProductVariantCreateFormData } from "./form"; describe("Creates variant matrix", () => { @@ -35,9 +35,186 @@ describe("Creates variant matrix", () => { }; const variants = createVariants(data); + expect(variants).toHaveLength( + thirdStep.attributes.reduce( + (acc, attribute) => acc * attribute.values.length, + 1 + ) + ); + variants.forEach(variant => { expect(variant.priceOverride).toBe(price); expect(variant.quantity).toBe(stock); }); }); + + it("with constant stock and attribute dependent price", () => { + const price = 49.99; + const stock = 80; + const attribute = attributes.find( + attribute => attribute.id === thirdStep.attributes[0].id + ); + + const data: ProductVariantCreateFormData = { + ...thirdStep, + price: { + ...thirdStep.price, + all: false, + attribute: attribute.id, + values: attribute.values.map((attributeValue, attributeValueIndex) => ({ + id: attributeValue, + value: (price * (attributeValueIndex + 1)).toString() + })) + }, + stock: { + ...thirdStep.stock, + all: true, + value: stock.toString() + } + }; + + const variants = createVariants(data); + expect(variants).toHaveLength( + thirdStep.attributes.reduce( + (acc, attribute) => acc * attribute.values.length, + 1 + ) + ); + + variants.forEach(variant => { + expect(variant.quantity).toBe(stock); + }); + + attribute.values.forEach((attributeValue, attributeValueIndex) => { + variants + .filter( + variant => + variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ).values[0] === attributeValue + ) + .forEach(variant => { + expect(variant.priceOverride).toBe( + (price * (attributeValueIndex + 1)).toString() + ); + }); + }); + }); + + it("with constant price and attribute dependent stock", () => { + const price = "49.99"; + const stock = 80; + const attribute = attributes.find( + attribute => attribute.id === thirdStep.attributes[0].id + ); + + const data: ProductVariantCreateFormData = { + ...thirdStep, + price: { + ...thirdStep.price, + all: true, + value: price + }, + stock: { + ...thirdStep.stock, + all: false, + attribute: attribute.id, + values: attribute.values.map((attributeValue, attributeValueIndex) => ({ + id: attributeValue, + value: (stock * (attributeValueIndex + 1)).toString() + })) + } + }; + + const variants = createVariants(data); + expect(variants).toHaveLength( + thirdStep.attributes.reduce( + (acc, attribute) => acc * attribute.values.length, + 1 + ) + ); + + variants.forEach(variant => { + expect(variant.priceOverride).toBe(price); + }); + + attribute.values.forEach((attributeValue, attributeValueIndex) => { + variants + .filter( + variant => + variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ).values[0] === attributeValue + ) + .forEach(variant => { + expect(variant.quantity).toBe(stock * (attributeValueIndex + 1)); + }); + }); + }); + + it("with attribute dependent price and stock", () => { + const price = 49.99; + const stock = 80; + const attribute = attributes.find( + attribute => attribute.id === thirdStep.attributes[0].id + ); + + const data: ProductVariantCreateFormData = { + ...thirdStep, + price: { + ...thirdStep.price, + all: false, + attribute: attribute.id, + values: attribute.values.map((attributeValue, attributeValueIndex) => ({ + id: attributeValue, + value: (price * (attributeValueIndex + 1)).toString() + })) + }, + stock: { + ...thirdStep.stock, + all: false, + attribute: attribute.id, + values: attribute.values.map((attributeValue, attributeValueIndex) => ({ + id: attributeValue, + value: (stock * (attributeValueIndex + 1)).toString() + })) + } + }; + + const variants = createVariants(data); + expect(variants).toHaveLength( + thirdStep.attributes.reduce( + (acc, attribute) => acc * attribute.values.length, + 1 + ) + ); + + attribute.values.forEach((attributeValue, attributeValueIndex) => { + variants + .filter( + variant => + variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ).values[0] === attributeValue + ) + .forEach(variant => { + expect(variant.priceOverride).toBe( + (price * (attributeValueIndex + 1)).toString() + ); + }); + }); + + attribute.values.forEach((attributeValue, attributeValueIndex) => { + variants + .filter( + variant => + variant.attributes.find( + variantAttribute => variantAttribute.id === attribute.id + ).values[0] === attributeValue + ) + .forEach(variant => { + expect(variant.quantity).toBe(stock * (attributeValueIndex + 1)); + }); + }); + }); }); diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.ts b/src/products/components/ProductVariantCreateDialog/createVariants.ts index fa8072136..d39bc64e9 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.ts @@ -1,32 +1,42 @@ import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; -import { Attribute, ProductVariantCreateFormData } from "./form"; +import { + AllOrAttribute, + Attribute, + ProductVariantCreateFormData +} from "./form"; interface CreateVariantAttributeValueInput { attributeId: string; attributeValueId: string; } type CreateVariantInput = CreateVariantAttributeValueInput[]; + +function getAttributeValuePriceOrStock( + attributes: CreateVariantInput, + priceOrStock: AllOrAttribute +): string { + const attribute = attributes.find( + attribute => attribute.attributeId === priceOrStock.attribute + ); + + const attributeValue = priceOrStock.values.find( + attributeValue => attribute.attributeValueId === attributeValue.id + ); + + return attributeValue.value; +} + function createVariant( data: ProductVariantCreateFormData, attributes: CreateVariantInput ): ProductVariantCreateInput { const priceOverride = data.price.all ? data.price.value - : data.price.values.find( - value => - attributes.find( - attribute => attribute.attributeId === data.price.attribute - ).attributeValueId === value.id - ).value; + : getAttributeValuePriceOrStock(attributes, data.price); const quantity = parseInt( data.stock.all ? data.stock.value - : data.stock.values.find( - value => - attributes.find( - attribute => attribute.attributeId === data.stock.attribute - ).attributeValueId === value.id - ).value, + : getAttributeValuePriceOrStock(attributes, data.stock), 10 ); @@ -81,6 +91,12 @@ export function createVariantFlatMatrixDimension( export function createVariants( data: ProductVariantCreateFormData ): ProductVariantCreateInput[] { + if ( + (!data.price.all && !data.price.attribute) || + (!data.stock.all && !data.stock.attribute) + ) { + return []; + } const variants = createVariantFlatMatrixDimension([[]], data.attributes).map( variant => createVariant(data, variant) ); diff --git a/src/products/components/ProductVariantCreateDialog/fixtures.ts b/src/products/components/ProductVariantCreateDialog/fixtures.ts index 1a6180a2e..9601839f0 100644 --- a/src/products/components/ProductVariantCreateDialog/fixtures.ts +++ b/src/products/components/ProductVariantCreateDialog/fixtures.ts @@ -1,4 +1,9 @@ -import { initialForm, ProductVariantCreateFormData } from "./form"; +import { createVariants } from "./createVariants"; +import { + AllOrAttribute, + initialForm, + ProductVariantCreateFormData +} from "./form"; export const attributes = [ { @@ -44,6 +49,7 @@ export const secondStep: ProductVariantCreateFormData = { } ] }; + export const thirdStep: ProductVariantCreateFormData = { ...secondStep, attributes: [ @@ -61,3 +67,44 @@ export const thirdStep: ProductVariantCreateFormData = { } ] }; + +const price: AllOrAttribute = { + all: false, + attribute: thirdStep.attributes[1].id, + value: "", + values: [ + { + id: thirdStep.attributes[1].values[0], + value: "24.99" + }, + { + id: thirdStep.attributes[1].values[1], + value: "26.99" + } + ] +}; +const stock: AllOrAttribute = { + all: false, + attribute: thirdStep.attributes[2].id, + value: "", + values: [ + { + id: thirdStep.attributes[2].values[0], + value: "50" + }, + { + id: thirdStep.attributes[2].values[1], + value: "35" + } + ] +}; +export const fourthStep: ProductVariantCreateFormData = { + ...thirdStep, + price, + stock, + variants: createVariants({ + ...thirdStep, + price, + stock + }) +}; diff --git a/src/products/components/ProductVariantCreateDialog/reducer.test.ts b/src/products/components/ProductVariantCreateDialog/reducer.test.ts index fecca566c..5643c9816 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.test.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.test.ts @@ -1,6 +1,6 @@ -import { attributes, secondStep, thirdStep } from "./fixtures"; +import { attributes, fourthStep, secondStep, thirdStep } from "./fixtures"; import { initialForm } from "./form"; -import reducer from "./reducer"; +import reducer, { VariantField } from "./reducer"; function execActions( initialState: TState, @@ -108,6 +108,7 @@ describe("Reducer is able to", () => { }); it("select price to each attribute value", () => { + const attribute = thirdStep.attributes[0]; const value = 45.99; const state = execActions(thirdStep, reducer, [ { @@ -115,18 +116,18 @@ describe("Reducer is able to", () => { type: "applyPriceToAll" }, { - attributeId: attributes[0].id, + attributeId: attribute.id, type: "changeApplyPriceToAttributeId" }, { type: "changeAttributeValuePrice", value: value.toString(), - valueId: attributes[0].values[0] + valueId: attribute.values[0] }, { type: "changeAttributeValuePrice", value: (value + 6).toString(), - valueId: attributes[0].values[6] + valueId: attribute.values[1] } ]); @@ -139,6 +140,7 @@ describe("Reducer is able to", () => { }); it("select stock to each attribute value", () => { + const attribute = thirdStep.attributes[0]; const value = 13; const state = execActions(thirdStep, reducer, [ { @@ -146,18 +148,18 @@ describe("Reducer is able to", () => { type: "applyStockToAll" }, { - attributeId: attributes[0].id, + attributeId: attribute.id, type: "changeApplyStockToAttributeId" }, { type: "changeAttributeValueStock", value: value.toString(), - valueId: attributes[0].values[0] + valueId: attribute.values[0] }, { type: "changeAttributeValueStock", value: (value + 6).toString(), - valueId: attributes[0].values[6] + valueId: attribute.values[1] } ]); @@ -168,4 +170,24 @@ describe("Reducer is able to", () => { ); expect(state).toMatchSnapshot(); }); + + it("modify individual variant price", () => { + const field: VariantField = "price"; + const value = "49.99"; + const variantIndex = 3; + + const state = execActions(fourthStep, reducer, [ + { + field, + type: "changeVariantData", + value, + variantIndex + } + ]); + + expect(state.variants[variantIndex].priceOverride).toBe(value); + expect(state.variants[variantIndex - 1].priceOverride).toBe( + fourthStep.variants[variantIndex - 1].priceOverride + ); + }); }); diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index e8954066c..045685921 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -13,14 +13,19 @@ export type ProductVariantCreateReducerActionType = | "changeApplyStockToAttributeId" | "changeAttributeValuePrice" | "changeAttributeValueStock" + | "changeVariantData" | "selectAttribute" | "selectValue"; + +export type VariantField = "stock" | "price" | "sku"; export interface ProductVariantCreateReducerAction { all?: boolean; attributeId?: string; + field?: VariantField; type: ProductVariantCreateReducerActionType; value?: string; valueId?: string; + variantIndex?: number; } function selectAttribute( @@ -69,26 +74,36 @@ function applyPriceToAll( state: ProductVariantCreateFormData, value: boolean ): ProductVariantCreateFormData { - return { + const data = { ...state, price: { ...state.price, all: value } }; + + return { + ...data, + variants: createVariants(data) + }; } function applyStockToAll( state: ProductVariantCreateFormData, value: boolean ): ProductVariantCreateFormData { - return { + const data = { ...state, stock: { ...state.stock, all: value } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeAttributeValuePrice( @@ -113,13 +128,18 @@ function changeAttributeValuePrice( index ); - return { + const data = { ...state, price: { ...state.price, values } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeAttributeValueStock( @@ -144,13 +164,18 @@ function changeAttributeValueStock( index ); - return { + const data = { ...state, stock: { ...state.stock, values } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeApplyPriceToAttributeId( @@ -164,8 +189,7 @@ function changeApplyPriceToAttributeId( id, value: "" })); - - return { + const data = { ...state, price: { ...state.price, @@ -173,38 +197,56 @@ function changeApplyPriceToAttributeId( values } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeApplyStockToAttributeId( state: ProductVariantCreateFormData, - attribute: string + attributeId: string ): ProductVariantCreateFormData { - return { + const attribute = state.attributes.find( + attribute => attribute.id === attributeId + ); + const values = attribute.values.map(id => ({ + id, + value: "" + })); + + const data = { ...state, stock: { ...state.stock, - attribute, - values: state.attributes - .find(stateAttribute => stateAttribute.id === attribute) - .values.map(attributeValue => ({ - id: attributeValue, - value: "" - })) + attribute: attributeId, + values } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeApplyPriceToAllValue( state: ProductVariantCreateFormData, value: string ): ProductVariantCreateFormData { - return { + const data = { ...state, price: { ...state.price, value } }; + + return { + ...data, + variants: createVariants(data) + }; } function changeApplyStockToAllValue( @@ -225,6 +267,27 @@ function changeApplyStockToAllValue( }; } +function changeVariantData( + state: ProductVariantCreateFormData, + field: VariantField, + value: string, + variantIndex: number +): ProductVariantCreateFormData { + const variant = state.variants[variantIndex]; + if (field === "price") { + variant.priceOverride = value; + } else if (field === "sku") { + variant.sku = value; + } else { + variant.quantity = parseInt(value, 10); + } + + return { + ...state, + variants: updateAtIndex(variant, state.variants, variantIndex) + }; +} + function reduceProductVariantCreateFormData( prevState: ProductVariantCreateFormData, action: ProductVariantCreateReducerAction @@ -252,6 +315,13 @@ function reduceProductVariantCreateFormData( return changeApplyPriceToAllValue(prevState, action.value); case "changeApplyStockToAllValue": return changeApplyStockToAllValue(prevState, action.value); + case "changeVariantData": + return changeVariantData( + prevState, + action.field, + action.value, + action.variantIndex + ); default: return prevState; } From 41cc4b0cf8097fdb4306ff99511fda3111c33ca3 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 24 Sep 2019 14:38:52 +0200 Subject: [PATCH 10/48] Darken colors a bit --- .../ProductVariantCreateDialog/ProductVariantCreateSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 62ef027e2..4575178ac 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -27,7 +27,7 @@ export interface ProductVariantCreateSummaryProps { data: ProductVariantCreateFormData; } -const colors = [blue, cyan, green, purple, yellow].map(color => color[500]); +const colors = [blue, cyan, green, purple, yellow].map(color => color[800]); const useStyles = makeStyles((theme: Theme) => ({ attributeValue: { From cc1a42c5efc9a48b066fd2b11a07acb9eb277041 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 24 Sep 2019 14:44:56 +0200 Subject: [PATCH 11/48] Update snapshots --- .../__snapshots__/reducer.test.ts.snap | 609 +++++++++++++++++- 1 file changed, 606 insertions(+), 3 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap index d1b59f93b..e86da0c57 100644 --- a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap +++ b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap @@ -110,7 +110,208 @@ Object { "value": "", "values": Array [], }, - "variants": Array [], + "variants": Array [ + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + ], } `; @@ -160,7 +361,208 @@ Object { "value": "", "values": Array [], }, - "variants": Array [], + "variants": Array [ + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "45.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "51.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "51.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "51.99", + "product": "", + "quantity": NaN, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "51.99", + "product": "", + "quantity": NaN, + }, + ], } `; @@ -452,6 +854,207 @@ Object { }, ], }, - "variants": Array [], + "variants": Array [ + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 13, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 13, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 13, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-1", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 13, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 19, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-2", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 19, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-1", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 19, + }, + Object { + "attributes": Array [ + Object { + "id": "attr-1", + "values": Array [ + "val-1-7", + ], + }, + Object { + "id": "attr-2", + "values": Array [ + "val-2-4", + ], + }, + Object { + "id": "attr-4", + "values": Array [ + "val-4-5", + ], + }, + ], + "priceOverride": "", + "product": "", + "quantity": 19, + }, + ], } `; From 52f458e7397c7d1747fda351f995a840e9c68bb0 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 24 Sep 2019 15:41:55 +0200 Subject: [PATCH 12/48] Add ability to modify sku --- .../ProductVariantCreateContent.tsx | 8 +++ .../ProductVariantCreateDialog.tsx | 53 ++++++++++++++++++- .../ProductVariantCreateSummary.tsx | 28 +++++++++- .../createVariants.ts | 3 +- 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 99bb9a27e..9f66a7220 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -121,6 +121,14 @@ const ProductVariantCreateContent: React.FC< attributes={selectedAttributes} currencySymbol={currencySymbol} data={data} + onVariantDataChange={(variantIndex, field, value) => + dispatchFormDataAction({ + field, + type: "changeVariantData", + value, + variantIndex + }) + } /> )} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index 8ba0e5a9d..a83903d54 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -25,6 +25,51 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); +function canHitNext( + step: ProductVariantCreateStep, + data: ProductVariantCreateFormData +): boolean { + switch (step) { + case "attributes": + return data.attributes.length > 0; + case "values": + return data.attributes.every(attribute => attribute.values.length > 0); + case "prices": + if (data.price.all) { + if (data.price.value === "") { + return false; + } + } else { + if ( + data.price.attribute === "" || + data.price.values.some(attributeValue => attributeValue.value === "") + ) { + return false; + } + } + + if (data.stock.all) { + if (data.stock.value === "") { + return false; + } + } else { + if ( + data.stock.attribute === "" || + data.stock.values.some(attributeValue => attributeValue.value === "") + ) { + return false; + } + } + + return true; + case "summary": + return data.variants.every(variant => variant.sku !== ""); + + default: + return false; + } +} + export interface ProductVariantCreateDialogProps extends Omit< ProductVariantCreateContentProps, @@ -110,13 +155,19 @@ const ProductVariantCreateDialog: React.FC< ) : ( - + + 100g + + + + + + +
+ Coffee Genre +
+
+
+ + +
+
+ Collar +
+
+
+ + + +
+ + + + + +`; + +exports[`Storyshots Views / Products / Create multiple variants summary 1`] = ` +
+
+
+
+
+
+ + Choose Attributes + +
+
+ + Select Values + +
+
+ + Prices and SKU + +
+
+ + Summary + +
+
+
+
+ You will create variants below +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Variant + + Inventory + + Price + + SKU +
+ + 100g + + + Arabica + + + Round + + +
+
+ + +
+
+
+
+
+ + + USD +
+
+
+
+
+ + +
+
+
+ + 100g + + + Arabica + + + Polo + + +
+
+ + +
+
+
+
+
+ + + USD +
+
+
+
+
+ + +
+
+
+ + 500g + + + Arabica + + + Round + + +
+
+ + +
+
+
+
+
+ + + USD +
+
+
+
+
+ + +
+
+
+ + 500g + + + Arabica + + + Polo + + +
+
+ + +
+
+
+
+
+ + + USD +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+`; + exports[`Storyshots Views / Products / Create product When loading 1`] = `
Date: Wed, 2 Oct 2019 15:34:34 +0200 Subject: [PATCH 14/48] Add error handling --- .../ProductUpdatePage/ProductUpdatePage.tsx | 3 + .../ProductVariantCreate.stories.tsx | 38 +- .../ProductVariantCreateAttributes.tsx | 5 +- .../ProductVariantCreateContent.tsx | 4 + .../ProductVariantCreateDialog.tsx | 11 +- .../ProductVariantCreatePrices.tsx | 20 +- .../ProductVariantCreateSummary.tsx | 267 ++- .../ProductVariantCreateValues.tsx | 6 +- .../__snapshots__/reducer.test.ts.snap | 40 +- .../createVariants.test.ts | 8 +- .../createVariants.ts | 17 +- .../ProductVariantCreateDialog/fixtures.ts | 8 +- .../ProductVariantCreateDialog/form.ts | 6 +- .../ProductVariantCreateDialog/reducer.ts | 24 +- .../ProductVariants/ProductVariants.tsx | 18 +- .../containers/ProductUpdateOperations.tsx | 70 +- src/products/mutations.ts | 28 + src/products/queries.ts | 1 + src/products/types/ProductDetails.ts | 1 + .../types/ProductVariantBulkCreate.ts | 38 + src/products/urls.ts | 2 +- .../views/ProductUpdate/ProductUpdate.tsx | 52 + src/products/views/ProductVariantCreate.tsx | 26 +- .../__snapshots__/Stories.test.ts.snap | 1929 +++++++++++------ .../stories/products/ProductUpdatePage.tsx | 1 + src/types/globalTypes.ts | 24 + 26 files changed, 1701 insertions(+), 946 deletions(-) create mode 100644 src/products/types/ProductVariantBulkCreate.ts diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index ac57ec9fc..f341f5875 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -60,6 +60,7 @@ export interface ProductUpdatePageProps extends ListActions { saveButtonBarState: ConfirmButtonTransitionState; fetchCategories: (query: string) => void; fetchCollections: (query: string) => void; + onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; onImageDelete: (id: string) => () => void; onBack?(); @@ -100,6 +101,7 @@ export const ProductUpdatePage: React.FC = ({ onSeoClick, onSubmit, onVariantAdd, + onVariantsAdd, onVariantShow, isChecked, selected, @@ -236,6 +238,7 @@ export const ProductUpdatePage: React.FC = ({ fallbackPrice={product ? product.basePrice : undefined} onRowClick={onVariantShow} onVariantAdd={onVariantAdd} + onVariantsAdd={onVariantsAdd} toolbar={toolbar} isChecked={isChecked} selected={selected} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx index f181027ed..a0738af4c 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -4,6 +4,8 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { attributes } from "@saleor/attributes/fixtures"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import Decorator from "../../../storybook/Decorator"; import { createVariants } from "./createVariants"; import { AllOrAttribute } from "./form"; @@ -19,7 +21,7 @@ const price: AllOrAttribute = { attribute: selectedAttributes[1].id, value: "2.79", values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ - id: attribute.id, + slug: attribute.slug, value: (attributeIndex + 4).toFixed(2) })) }; @@ -29,7 +31,7 @@ const stock: AllOrAttribute = { attribute: selectedAttributes[1].id, value: "8", values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ - id: attribute.id, + slug: attribute.slug, value: (selectedAttributes.length * 10 - attributeIndex).toString() })) }; @@ -37,10 +39,20 @@ const stock: AllOrAttribute = { const dataAttributes = selectedAttributes.map(attribute => ({ id: attribute.id, values: attribute.values - .map(value => value.id) + .map(value => value.slug) .filter((_, valueIndex) => valueIndex % 2 !== 1) })); +const errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[] = [ + { + __typename: "BulkProductError", + code: ProductErrorCode.UNIQUE, + field: "sku", + index: 3, + message: "Duplicated SKU." + } +]; + const props: ProductVariantCreateContentProps = { attributes, currencySymbol: "USD", @@ -56,6 +68,7 @@ const props: ProductVariantCreateContentProps = { }) }, dispatchFormDataAction: () => undefined, + errors: [], step: "attributes" }; @@ -78,9 +91,26 @@ storiesOf("Views / Products / Create multiple variants", module) )) .add("prices and SKU", () => ( + )); + +storiesOf("Views / Products / Create multiple variants / summary", module) + .addDecorator(storyFn => ( + + {storyFn()} + )) - .add("summary", () => ( + .addDecorator(Decorator) + .add("default", () => ( + )) + .add("errors", () => ( + )); storiesOf("Views / Products / Create multiple variants", module) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx index 5de61f721..48b658d9f 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx @@ -1,4 +1,3 @@ -import { Theme } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; @@ -18,14 +17,14 @@ export interface ProductVariantCreateAttributesProps { onAttributeClick: (id: string) => void; } -const useStyles = makeStyles((theme: Theme) => ({ +const useStyles = makeStyles({ checkboxCell: { paddingLeft: 0 }, wideCell: { width: "100%" } -})); +}); const ProductVariantCreateAttributes: React.FC< ProductVariantCreateAttributesProps diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 9f66a7220..777e36632 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -2,6 +2,7 @@ import React from "react"; import { makeStyles } from "@material-ui/styles"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; import { ProductVariantCreateFormData } from "./form"; import ProductVariantCreateAttributes from "./ProductVariantCreateAttributes"; @@ -24,6 +25,7 @@ export interface ProductVariantCreateContentProps { currencySymbol: string; data: ProductVariantCreateFormData; dispatchFormDataAction: React.Dispatch; + errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; step: ProductVariantCreateStep; } @@ -35,6 +37,7 @@ const ProductVariantCreateContent: React.FC< currencySymbol, data, dispatchFormDataAction, + errors, step } = props; const classes = useStyles(props); @@ -121,6 +124,7 @@ const ProductVariantCreateContent: React.FC< attributes={selectedAttributes} currencySymbol={currencySymbol} data={data} + errors={errors} onVariantDataChange={(variantIndex, field, value) => dispatchFormDataAction({ field, diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index a83903d54..a86601cc8 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -7,6 +7,8 @@ import { Theme } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/styles"; import React from "react"; import { FormattedMessage } from "react-intl"; + +import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; import { initialForm, ProductVariantCreateFormData } from "./form"; import ProductVariantCreateContent, { ProductVariantCreateContentProps @@ -73,17 +75,17 @@ function canHitNext( export interface ProductVariantCreateDialogProps extends Omit< ProductVariantCreateContentProps, - "dispatchFormDataAction" | "step" + "data" | "dispatchFormDataAction" | "step" > { open: boolean; - onClose: () => undefined; - onSubmit: (data: ProductVariantCreateFormData) => void; + onClose: () => void; + onSubmit: (data: ProductVariantBulkCreateInput[]) => void; } const ProductVariantCreateDialog: React.FC< ProductVariantCreateDialogProps > = props => { - const { open, onClose, ...contentProps } = props; + const { open, onClose, onSubmit, ...contentProps } = props; const classes = useStyles(props); const [step, setStep] = React.useState( "attributes" @@ -167,6 +169,7 @@ const ProductVariantCreateDialog: React.FC< color="primary" disabled={!canHitNext(step, data)} variant="contained" + onClick={() => onSubmit(data.variants)} > attribute.id === data.price.attribute).values + ? attributes + .find(attribute => attribute.id === data.price.attribute) + .values.filter(value => + data.attributes + .find(attribute => attribute.id === data.price.attribute) + .values.includes(value.slug) + ) : []; const stockAttributeValues = data.stock.all ? null : data.stock.attribute - ? attributes.find(attribute => attribute.id === data.stock.attribute).values + ? attributes + .find(attribute => attribute.id === data.stock.attribute) + .values.filter(value => + data.attributes + .find(attribute => attribute.id === data.stock.attribute) + .values.includes(value.slug) + ) : []; return ( @@ -158,7 +170,7 @@ const ProductVariantCreatePrices: React.FC< value={data.price.values[attributeValueIndex].value} onChange={event => onAttributeValueChange( - attributeValue.id, + attributeValue.slug, event.target.value, "price" ) @@ -256,7 +268,7 @@ const ProductVariantCreatePrices: React.FC< value={data.stock.values[attributeValueIndex].value} onChange={event => onAttributeValueChange( - attributeValue.id, + attributeValue.slug, event.target.value, "stock" ) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 53c98d44a..1d74e48f1 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -4,11 +4,6 @@ import green from "@material-ui/core/colors/green"; import purple from "@material-ui/core/colors/purple"; import yellow from "@material-ui/core/colors/yellow"; import { Theme } from "@material-ui/core/styles"; -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; @@ -17,7 +12,9 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import Hr from "@saleor/components/Hr"; -import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { maybe } from "@saleor/misc"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; +import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; import { ProductVariantCreateFormData } from "./form"; import { VariantField } from "./reducer"; @@ -26,6 +23,7 @@ export interface ProductVariantCreateSummaryProps { attributes: ProductDetails_product_productType_variantAttributes[]; currencySymbol: string; data: ProductVariantCreateFormData; + errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; onVariantDataChange: ( variantIndex: number, field: VariantField, @@ -35,42 +33,58 @@ export interface ProductVariantCreateSummaryProps { const colors = [blue, cyan, green, purple, yellow].map(color => color[800]); -const useStyles = makeStyles((theme: Theme) => ({ - attributeValue: { - display: "inline-block", - marginRight: theme.spacing.unit - }, - col: { - paddingLeft: theme.spacing.unit, - paddingRight: theme.spacing.unit - }, - colName: { - paddingLeft: "0 !important", - width: "auto" - }, - colPrice: { - width: 200 - }, - colSku: { - width: 210 - }, - colStock: { - width: 120 - }, - hr: { - marginBottom: theme.spacing.unit, - marginTop: theme.spacing.unit / 2 - }, - input: { - "& input": { - padding: "16px 12px 17px" +const useStyles = makeStyles( + (theme: Theme) => ({ + attributeValue: { + display: "inline-block", + marginRight: theme.spacing.unit }, - marginTop: theme.spacing.unit / 2 + col: { + ...theme.typography.body2, + fontSize: 14, + paddingLeft: theme.spacing.unit, + paddingRight: theme.spacing.unit + }, + colHeader: { + ...theme.typography.body2, + fontSize: 14 + }, + colName: { + "&&": { + paddingLeft: "0 !important" + }, + "&:not($colHeader)": { + paddingTop: theme.spacing.unit * 2 + } + }, + colPrice: {}, + colSku: {}, + colStock: {}, + errorRow: {}, + hr: { + marginBottom: theme.spacing.unit, + marginTop: theme.spacing.unit / 2 + }, + input: { + "& input": { + padding: "16px 12px 17px" + }, + marginTop: theme.spacing.unit / 2 + }, + row: { + borderBottom: `1px solid ${theme.palette.divider}`, + display: "grid", + gridTemplateColumns: "1fr 200px 120px 210px", + padding: `${theme.spacing.unit}px 0` + } + }), + { + name: "ProductVariantCreateSummary" } -})); +); function getVariantName( - variant: ProductVariantCreateInput, + variant: ProductVariantBulkCreateInput, attributes: ProductDetails_product_productType_variantAttributes[] ): string[] { return attributes.reduce( @@ -78,7 +92,7 @@ function getVariantName( ...acc, attribute.values.find( value => - value.id === + value.slug === variant.attributes.find( variantAttribute => variantAttribute.id === attribute.id ).values[0] @@ -91,7 +105,13 @@ function getVariantName( const ProductVariantCreateSummary: React.FC< ProductVariantCreateSummaryProps > = props => { - const { attributes, currencySymbol, data, onVariantDataChange } = props; + const { + attributes, + currencySymbol, + data, + errors, + onVariantDataChange + } = props; const classes = useStyles(props); return ( @@ -103,40 +123,69 @@ const ProductVariantCreateSummary: React.FC< />
- - - - - - - - - - - - - - - - - - - {data.variants.map((variant, variantIndex) => ( - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {data.variants.map((variant, variantIndex) => { + const variantErrors = errors.filter( + error => error.index === variantIndex + ); + + return ( +
0 + })} key={variant.attributes .map(attribute => attribute.values[0]) .join(":")} > - +
{getVariantName(variant, attributes).map( (value, valueIndex) => ( ) )} - - - - onVariantDataChange( - variantIndex, - "stock", - event.target.value - ) - } - /> - - +
+
error.field === "priceOverride" + ) + } + helperText={maybe( + () => + variantErrors.find( + error => error.field === "priceOverride" + ).message + )} inputProps={{ min: 0, type: "number" @@ -188,21 +230,52 @@ const ProductVariantCreateSummary: React.FC< ) } /> - - +
+
error.field === "quantity") + } + helperText={maybe( + () => + variantErrors.find(error => error.field === "quantity") + .message + )} + inputProps={{ + min: 0, + type: "number" + }} + fullWidth + value={variant.quantity} + onChange={event => + onVariantDataChange( + variantIndex, + "stock", + event.target.value + ) + } + /> +
+
+ error.field === "sku")} + helperText={maybe( + () => + variantErrors.find(error => error.field === "sku").message + )} fullWidth value={variant.sku} onChange={event => onVariantDataChange(variantIndex, "sku", event.target.value) } /> - - - ))} - -
+
+ + ); + })} + ); }; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx index 801dc222f..8d73cabe3 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -48,15 +48,15 @@ const ProductVariantCreateValues: React.FC< {attribute.values.map(value => ( attribute.id === dataAttribute.id ).values, (a, b) => a === b )} - name={`value:${value.id}`} + name={`value:${value.slug}`} label={value.name} - onChange={() => onValueClick(attribute.id, value.id)} + onChange={() => onValueClick(attribute.id, value.slug)} /> ))} diff --git a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap index b40089ef1..f9b455627 100644 --- a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap +++ b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap @@ -133,7 +133,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -159,7 +158,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -185,7 +183,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -211,7 +208,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -237,7 +233,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -263,7 +258,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -289,7 +283,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -315,7 +308,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -354,11 +346,11 @@ Object { "value": "", "values": Array [ Object { - "id": "val-1-1", + "slug": "val-1-1", "value": "45.99", }, Object { - "id": "val-1-7", + "slug": "val-1-7", "value": "51.99", }, ], @@ -392,7 +384,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -418,7 +409,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -444,7 +434,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -470,7 +459,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -496,7 +484,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -522,7 +509,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -548,7 +534,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -574,7 +559,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -642,7 +626,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -668,7 +651,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -694,7 +676,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -720,7 +701,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -746,7 +726,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -772,7 +751,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -798,7 +776,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -824,7 +801,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -869,11 +845,11 @@ Object { "value": "", "values": Array [ Object { - "id": "val-1-1", + "slug": "val-1-1", "value": "13", }, Object { - "id": "val-1-7", + "slug": "val-1-7", "value": "19", }, ], @@ -901,7 +877,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -927,7 +902,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -953,7 +927,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -979,7 +952,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -1005,7 +977,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1031,7 +1002,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1057,7 +1027,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1083,7 +1052,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts index 1ecc83b45..e41c65229 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts @@ -62,7 +62,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (price * (attributeValueIndex + 1)).toString() })) }, @@ -120,7 +120,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (stock * (attributeValueIndex + 1)).toString() })) } @@ -166,7 +166,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (price * (attributeValueIndex + 1)).toString() })) }, @@ -175,7 +175,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (stock * (attributeValueIndex + 1)).toString() })) } diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.ts b/src/products/components/ProductVariantCreateDialog/createVariants.ts index 4433aa5ef..6239832e2 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.ts @@ -1,4 +1,4 @@ -import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { AllOrAttribute, Attribute, @@ -7,7 +7,7 @@ import { interface CreateVariantAttributeValueInput { attributeId: string; - attributeValueId: string; + attributeValueSlug: string; } type CreateVariantInput = CreateVariantAttributeValueInput[]; @@ -20,7 +20,7 @@ function getAttributeValuePriceOrStock( ); const attributeValue = priceOrStock.values.find( - attributeValue => attribute.attributeValueId === attributeValue.id + attributeValue => attribute.attributeValueSlug === attributeValue.slug ); return attributeValue.value; @@ -29,7 +29,7 @@ function getAttributeValuePriceOrStock( function createVariant( data: ProductVariantCreateFormData, attributes: CreateVariantInput -): ProductVariantCreateInput { +): ProductVariantBulkCreateInput { const priceOverride = data.price.all ? data.price.value : getAttributeValuePriceOrStock(attributes, data.price); @@ -43,10 +43,9 @@ function createVariant( return { attributes: attributes.map(attribute => ({ id: attribute.attributeId, - values: [attribute.attributeValueId] + values: [attribute.attributeValueSlug] })), priceOverride, - product: "", quantity, sku: "" }; @@ -56,11 +55,11 @@ function addAttributeToVariant( attribute: Attribute, variant: CreateVariantInput ): CreateVariantInput[] { - return attribute.values.map(attributeValueId => [ + return attribute.values.map(attributeValueSlug => [ ...variant, { attributeId: attribute.id, - attributeValueId + attributeValueSlug } ]); } @@ -91,7 +90,7 @@ export function createVariantFlatMatrixDimension( export function createVariants( data: ProductVariantCreateFormData -): ProductVariantCreateInput[] { +): ProductVariantBulkCreateInput[] { if ( (!data.price.all && !data.price.attribute) || (!data.stock.all && !data.stock.attribute) diff --git a/src/products/components/ProductVariantCreateDialog/fixtures.ts b/src/products/components/ProductVariantCreateDialog/fixtures.ts index 9601839f0..e53aa0aca 100644 --- a/src/products/components/ProductVariantCreateDialog/fixtures.ts +++ b/src/products/components/ProductVariantCreateDialog/fixtures.ts @@ -74,11 +74,11 @@ const price: AllOrAttribute = { value: "", values: [ { - id: thirdStep.attributes[1].values[0], + slug: thirdStep.attributes[1].values[0], value: "24.99" }, { - id: thirdStep.attributes[1].values[1], + slug: thirdStep.attributes[1].values[1], value: "26.99" } ] @@ -89,11 +89,11 @@ const stock: AllOrAttribute = { value: "", values: [ { - id: thirdStep.attributes[2].values[0], + slug: thirdStep.attributes[2].values[0], value: "50" }, { - id: thirdStep.attributes[2].values[1], + slug: thirdStep.attributes[2].values[1], value: "35" } ] diff --git a/src/products/components/ProductVariantCreateDialog/form.ts b/src/products/components/ProductVariantCreateDialog/form.ts index 157ad2c55..b6331224e 100644 --- a/src/products/components/ProductVariantCreateDialog/form.ts +++ b/src/products/components/ProductVariantCreateDialog/form.ts @@ -1,7 +1,7 @@ -import { ProductVariantCreateInput } from "../../../types/globalTypes"; +import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; export interface AttributeValue { - id: string; + slug: string; value: string; } export interface AllOrAttribute { @@ -18,7 +18,7 @@ export interface ProductVariantCreateFormData { attributes: Attribute[]; price: AllOrAttribute; stock: AllOrAttribute; - variants: ProductVariantCreateInput[]; + variants: ProductVariantBulkCreateInput[]; } export const initialForm: ProductVariantCreateFormData = { diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index 045685921..f1492ed22 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -108,20 +108,20 @@ function applyStockToAll( function changeAttributeValuePrice( state: ProductVariantCreateFormData, - attributeValueId: string, + attributeValueSlug: string, price: string ): ProductVariantCreateFormData { const index = state.price.values.findIndex( - value => value.id === attributeValueId + value => value.slug === attributeValueSlug ); if (index === -1) { - throw new Error(`Value with id ${attributeValueId} not found`); + throw new Error(`Value with id ${attributeValueSlug} not found`); } const values = updateAtIndex( { - id: attributeValueId, + slug: attributeValueSlug, value: price }, state.price.values, @@ -144,20 +144,20 @@ function changeAttributeValuePrice( function changeAttributeValueStock( state: ProductVariantCreateFormData, - attributeValueId: string, + attributeValueSlug: string, stock: string ): ProductVariantCreateFormData { const index = state.stock.values.findIndex( - value => value.id === attributeValueId + value => value.slug === attributeValueSlug ); if (index === -1) { - throw new Error(`Value with id ${attributeValueId} not found`); + throw new Error(`Value with id ${attributeValueSlug} not found`); } const values = updateAtIndex( { - id: attributeValueId, + slug: attributeValueSlug, value: stock }, state.stock.values, @@ -185,8 +185,8 @@ function changeApplyPriceToAttributeId( const attribute = state.attributes.find( attribute => attribute.id === attributeId ); - const values = attribute.values.map(id => ({ - id, + const values = attribute.values.map(slug => ({ + slug, value: "" })); const data = { @@ -211,8 +211,8 @@ function changeApplyStockToAttributeId( const attribute = state.attributes.find( attribute => attribute.id === attributeId ); - const values = attribute.values.map(id => ({ - id, + const values = attribute.values.map(slug => ({ + slug, value: "" })); diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index bf6332396..0de9f974d 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -69,6 +69,7 @@ interface ProductVariantsProps extends ListActions, WithStyles { fallbackPrice?: ProductVariant_costPrice; onRowClick: (id: string) => () => void; onVariantAdd?(); + onVariantsAdd?(); } const numberOfColumns = 5; @@ -81,6 +82,7 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })( fallbackPrice, onRowClick, onVariantAdd, + onVariantsAdd, isChecked, selected, toggle, @@ -98,7 +100,7 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })( description: "section header" })} toolbar={ - <> + hasVariants ? ( - + ) : ( + + ) } /> {!variants.length && ( diff --git a/src/products/containers/ProductUpdateOperations.tsx b/src/products/containers/ProductUpdateOperations.tsx index 3ed040bbc..73940d8e5 100644 --- a/src/products/containers/ProductUpdateOperations.tsx +++ b/src/products/containers/ProductUpdateOperations.tsx @@ -7,6 +7,7 @@ import { TypedProductImageCreateMutation, TypedProductImageDeleteMutation, TypedProductUpdateMutation, + TypedProductVariantBulkCreateMutation, TypedProductVariantBulkDeleteMutation, TypedSimpleProductUpdateMutation } from "../mutations"; @@ -25,6 +26,10 @@ import { ProductImageReorderVariables } from "../types/ProductImageReorder"; import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate"; +import { + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +} from "../types/ProductVariantBulkCreate"; import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -38,6 +43,10 @@ import ProductImagesReorderProvider from "./ProductImagesReorder"; interface ProductUpdateOperationsProps { product: ProductDetails_product; children: (props: { + bulkProductVariantCreate: PartialMutationProviderOutput< + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables + >; bulkProductVariantDelete: PartialMutationProviderOutput< ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -67,6 +76,7 @@ interface ProductUpdateOperationsProps { SimpleProductUpdateVariables >; }) => React.ReactNode; + onBulkProductVariantCreate?: (data: ProductVariantBulkCreate) => void; onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void; onDelete?: (data: ProductDelete) => void; onImageCreate?: (data: ProductImageCreate) => void; @@ -80,6 +90,7 @@ const ProductUpdateOperations: React.StatelessComponent< > = ({ product, children, + onBulkProductVariantCreate, onBulkProductVariantDelete, onDelete, onImageDelete, @@ -112,31 +123,40 @@ const ProductUpdateOperations: React.StatelessComponent< - {(...bulkProductVariantDelete) => - children({ - bulkProductVariantDelete: getMutationProviderData( - ...bulkProductVariantDelete - ), - createProductImage: getMutationProviderData( - ...createProductImage - ), - deleteProduct: getMutationProviderData( - ...deleteProduct - ), - deleteProductImage: getMutationProviderData( - ...deleteProductImage - ), - reorderProductImages: getMutationProviderData( - ...reorderProductImages - ), - updateProduct: getMutationProviderData( - ...updateProduct - ), - updateSimpleProduct: getMutationProviderData( - ...updateSimpleProduct - ) - }) - } + {(...bulkProductVariantDelete) => ( + + {(...bulkProductVariantCreate) => + children({ + bulkProductVariantCreate: getMutationProviderData( + ...bulkProductVariantCreate + ), + bulkProductVariantDelete: getMutationProviderData( + ...bulkProductVariantDelete + ), + createProductImage: getMutationProviderData( + ...createProductImage + ), + deleteProduct: getMutationProviderData( + ...deleteProduct + ), + deleteProductImage: getMutationProviderData( + ...deleteProductImage + ), + reorderProductImages: getMutationProviderData( + ...reorderProductImages + ), + updateProduct: getMutationProviderData( + ...updateProduct + ), + updateSimpleProduct: getMutationProviderData( + ...updateSimpleProduct + ) + }) + } + + )} )} diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 563c09f84..c612d576d 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -45,6 +45,10 @@ import { productBulkPublish, productBulkPublishVariables } from "./types/productBulkPublish"; +import { + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +} from "./types/ProductVariantBulkCreate"; import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -440,6 +444,30 @@ export const TypedProductBulkPublishMutation = TypedMutation< productBulkPublishVariables >(productBulkPublishMutation); +export const ProductVariantBulkCreateMutation = gql` + mutation ProductVariantBulkCreate( + $id: ID! + $inputs: [ProductVariantBulkCreateInput]! + ) { + productVariantBulkCreate(product: $id, variants: $inputs) { + bulkProductErrors { + field + message + code + index + } + errors { + field + message + } + } + } +`; +export const TypedProductVariantBulkCreateMutation = TypedMutation< + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +>(ProductVariantBulkCreateMutation); + export const ProductVariantBulkDeleteMutation = gql` mutation ProductVariantBulkDelete($ids: [ID!]!) { productVariantBulkDelete(ids: $ids) { diff --git a/src/products/queries.ts b/src/products/queries.ts index efbdb9489..626b6ff3a 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -267,6 +267,7 @@ const productDetailsQuery = gql` values { id name + slug } } } diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index 803e66d8d..3ed43c47a 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -143,6 +143,7 @@ export interface ProductDetails_product_productType_variantAttributes_values { __typename: "AttributeValue"; id: string; name: string | null; + slug: string | null; } export interface ProductDetails_product_productType_variantAttributes { diff --git a/src/products/types/ProductVariantBulkCreate.ts b/src/products/types/ProductVariantBulkCreate.ts new file mode 100644 index 000000000..f0a0160ff --- /dev/null +++ b/src/products/types/ProductVariantBulkCreate.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ProductVariantBulkCreateInput, ProductErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ProductVariantBulkCreate +// ==================================================== + +export interface ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors { + __typename: "BulkProductError"; + field: string | null; + message: string | null; + code: ProductErrorCode | null; + index: number | null; +} + +export interface ProductVariantBulkCreate_productVariantBulkCreate_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface ProductVariantBulkCreate_productVariantBulkCreate { + __typename: "ProductVariantBulkCreate"; + bulkProductErrors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[] | null; + errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[] | null; +} + +export interface ProductVariantBulkCreate { + productVariantBulkCreate: ProductVariantBulkCreate_productVariantBulkCreate | null; +} + +export interface ProductVariantBulkCreateVariables { + id: string; + inputs: (ProductVariantBulkCreateInput | null)[]; +} diff --git a/src/products/urls.ts b/src/products/urls.ts index 5ebfe8d31..2f240496f 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -53,7 +53,7 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string => export const productPath = (id: string) => urlJoin(productSection + id); export type ProductUrlDialog = "remove"; export type ProductUrlQueryParams = BulkAction & - Dialog<"remove" | "remove-variants">; + Dialog<"create-variants" | "remove" | "remove-variants">; export const productUrl = (id: string, params?: ProductUrlQueryParams) => productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index d86c58c25..c12d53bc3 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -10,7 +10,10 @@ import { WindowTitle } from "@saleor/components/WindowTitle"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; +import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; +import ProductVariantCreateDialog from "@saleor/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog"; +import { ProductVariantBulkCreate } from "@saleor/products/types/ProductVariantBulkCreate"; import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config"; import SearchCategories from "../../../containers/SearchCategories"; import SearchCollections from "../../../containers/SearchCollections"; @@ -54,6 +57,7 @@ export const ProductUpdate: React.StatelessComponent = ({ params.ids ); const intl = useIntl(); + const shop = useShop(); const openModal = (action: ProductUrlDialog) => navigate( @@ -115,6 +119,15 @@ export const ProductUpdate: React.StatelessComponent = ({ const handleVariantAdd = () => navigate(productVariantAddUrl(id)); + const handleBulkProductVariantCreate = ( + data: ProductVariantBulkCreate + ) => { + if (data.productVariantBulkCreate.errors.length === 0) { + navigate(productUrl(id), true); + refetch(); + } + }; + const handleBulkProductVariantDelete = ( data: ProductVariantBulkDelete ) => { @@ -125,10 +138,19 @@ export const ProductUpdate: React.StatelessComponent = ({ } }; + const handleVariantCreatorOpen = () => + navigate( + productUrl(id, { + ...params, + action: "create-variants" + }) + ); + const product = data ? data.product : undefined; return ( = ({ onUpdate={handleUpdate} > {({ + bulkProductVariantCreate, bulkProductVariantDelete, createProductImage, deleteProduct, @@ -245,6 +268,7 @@ export const ProductUpdate: React.StatelessComponent = ({ onImageReorder={handleImageReorder} onSubmit={handleSubmit} onVariantAdd={handleVariantAdd} + onVariantsAdd={handleVariantCreatorOpen} onVariantShow={variantId => () => navigate( productVariantEditUrl(product.id, variantId) @@ -328,6 +352,34 @@ export const ProductUpdate: React.StatelessComponent = ({ /> + + bulkProductVariantCreate.opts.data + .productVariantBulkCreate.bulkProductErrors, + [] + )} + open={params.action === "create-variants"} + attributes={maybe( + () => data.product.productType.variantAttributes, + [] + )} + currencySymbol={maybe(() => shop.defaultCurrency)} + onClose={() => + navigate( + productUrl(id, { + ...params, + action: undefined + }) + ) + } + onSubmit={inputs => + bulkProductVariantCreate.mutate({ + id, + inputs + }) + } + /> ); }} diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index b78ca66a9..a8ac30785 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -58,18 +58,20 @@ export const ProductVariant: React.StatelessComponent = ({ ) => variantCreate({ variables: { - attributes: formData.attributes - .filter(attribute => attribute.value !== "") - .map(attribute => ({ - id: attribute.id, - values: [attribute.value] - })), - costPrice: decimal(formData.costPrice), - priceOverride: decimal(formData.priceOverride), - product: productId, - quantity: formData.quantity || null, - sku: formData.sku, - trackInventory: true + input: { + attributes: formData.attributes + .filter(attribute => attribute.value !== "") + .map(attribute => ({ + id: attribute.id, + values: [attribute.value] + })), + costPrice: decimal(formData.costPrice), + priceOverride: decimal(formData.priceOverride), + product: productId, + quantity: formData.quantity || null, + sku: formData.sku, + trackInventory: true + } } }); const handleVariantClick = (id: string) => diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 8fbbd2cc5..3c6fd9f16 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -82051,6 +82051,1203 @@ exports[`Storyshots Views / Product types / Unassign multiple attributes default /> `; +exports[`Storyshots Views / Products / Create multiple variants / summary default 1`] = ` +
+
+
+
+
+
+ + Choose Attributes + +
+
+ + Select Values + +
+
+ + Prices and SKU + +
+
+ + Summary + +
+
+
+
+ You will create variants below +
+
+
+
+
+ Variant +
+
+ Price +
+
+ Inventory +
+
+ SKU +
+
+
+
+ + 100g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 100g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots Views / Products / Create multiple variants / summary errors 1`] = ` +
+
+
+
+
+
+ + Choose Attributes + +
+
+ + Select Values + +
+
+ + Prices and SKU + +
+
+ + Summary + +
+
+
+
+ You will create variants below +
+
+
+
+
+ Variant +
+
+ Price +
+
+ Inventory +
+
+ SKU +
+
+
+
+ + 100g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 100g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+

+ Duplicated SKU. +

+
+
+
+
+
+
+
+
+
+`; + exports[`Storyshots Views / Products / Create multiple variants choose attributes 1`] = `
-
-
-

- Robusta -

-
-
-
- -
- - -
-
-
-
-
@@ -83019,58 +84164,6 @@ exports[`Storyshots Views / Products / Create multiple variants prices and SKU 1
-
-
-
-

- Robusta -

-
-
-
- -
- - -
-
-
-
@@ -83154,7 +84247,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83175,7 +84268,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83196,7 +84289,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83217,7 +84310,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83250,7 +84343,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83271,7 +84364,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83304,7 +84397,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83325,7 +84418,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83346,7 +84439,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83365,616 +84458,6 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` `; -exports[`Storyshots Views / Products / Create multiple variants summary 1`] = ` -
-
-
-
-
-
- - Choose Attributes - -
-
- - Select Values - -
-
- - Prices and SKU - -
-
- - Summary - -
-
-
-
- You will create variants below -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Variant - - Inventory - - Price - - SKU -
- - 100g - - - Arabica - - - Round - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 100g - - - Arabica - - - Polo - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 500g - - - Arabica - - - Round - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 500g - - - Arabica - - - Polo - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
-
-
-
-
-
-`; - exports[`Storyshots Views / Products / Create product When loading 1`] = `
undefined, onVariantAdd: () => undefined, onVariantShow: () => undefined, + onVariantsAdd: () => undefined, placeholderImage, product, saveButtonBarState: "default", diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 5dff42bb4..0c23293c3 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -193,6 +193,20 @@ export enum PermissionEnum { MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS", } +export enum ProductErrorCode { + ALREADY_EXISTS = "ALREADY_EXISTS", + ATTRIBUTE_ALREADY_ASSIGNED = "ATTRIBUTE_ALREADY_ASSIGNED", + ATTRIBUTE_CANNOT_BE_ASSIGNED = "ATTRIBUTE_CANNOT_BE_ASSIGNED", + ATTRIBUTE_VARIANTS_DISABLED = "ATTRIBUTE_VARIANTS_DISABLED", + GRAPHQL_ERROR = "GRAPHQL_ERROR", + INVALID = "INVALID", + NOT_FOUND = "NOT_FOUND", + NOT_PRODUCTS_IMAGE = "NOT_PRODUCTS_IMAGE", + REQUIRED = "REQUIRED", + UNIQUE = "UNIQUE", + VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT", +} + export enum ProductOrderField { DATE = "DATE", MINIMAL_PRICE = "MINIMAL_PRICE", @@ -614,6 +628,16 @@ export interface ProductTypeInput { taxCode?: string | null; } +export interface ProductVariantBulkCreateInput { + attributes: (AttributeValueInput | null)[]; + costPrice?: any | null; + priceOverride?: any | null; + sku: string; + quantity?: number | null; + trackInventory?: boolean | null; + weight?: any | null; +} + export interface ProductVariantCreateInput { attributes: (AttributeValueInput | null)[]; costPrice?: any | null; From 9b9a6648cdaf59d44f1d3347ef2e02ac3690df00 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 2 Oct 2019 16:29:22 +0200 Subject: [PATCH 15/48] Allow variant deleting --- .../ProductVariantCreateContent.tsx | 6 + .../ProductVariantCreateSummary.tsx | 20 +- .../ProductVariantCreateTabs.tsx | 1 + .../reducer.test.ts | 13 + .../ProductVariantCreateDialog/reducer.ts | 21 +- .../__snapshots__/Stories.test.ts.snap | 232 ++++++++++++++++++ 6 files changed, 290 insertions(+), 3 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 777e36632..d451c1a75 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -133,6 +133,12 @@ const ProductVariantCreateContent: React.FC< variantIndex }) } + onVariantDelete={variantIndex => + dispatchFormDataAction({ + type: "deleteVariant", + variantIndex + }) + } /> )}
diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 1d74e48f1..0dbf89d26 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -3,9 +3,11 @@ import cyan from "@material-ui/core/colors/cyan"; import green from "@material-ui/core/colors/green"; import purple from "@material-ui/core/colors/purple"; import yellow from "@material-ui/core/colors/yellow"; +import IconButton from "@material-ui/core/IconButton"; import { Theme } from "@material-ui/core/styles"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; +import DeleteIcon from "@material-ui/icons/Delete"; import { makeStyles } from "@material-ui/styles"; import classNames from "classnames"; import React from "react"; @@ -29,6 +31,7 @@ export interface ProductVariantCreateSummaryProps { field: VariantField, value: string ) => void; + onVariantDelete: (variantIndex: number) => void; } const colors = [blue, cyan, green, purple, yellow].map(color => color[800]); @@ -60,6 +63,9 @@ const useStyles = makeStyles( colPrice: {}, colSku: {}, colStock: {}, + delete: { + marginTop: theme.spacing.unit / 2 + }, errorRow: {}, hr: { marginBottom: theme.spacing.unit, @@ -74,7 +80,7 @@ const useStyles = makeStyles( row: { borderBottom: `1px solid ${theme.palette.divider}`, display: "grid", - gridTemplateColumns: "1fr 200px 120px 210px", + gridTemplateColumns: "1fr 180px 120px 180px 64px", padding: `${theme.spacing.unit}px 0` } }), @@ -110,7 +116,8 @@ const ProductVariantCreateSummary: React.FC< currencySymbol, data, errors, - onVariantDataChange + onVariantDataChange, + onVariantDelete } = props; const classes = useStyles(props); @@ -272,6 +279,15 @@ const ProductVariantCreateSummary: React.FC< } /> +
+ onVariantDelete(variantIndex)} + > + + +
); })} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx index 11a57da01..94115a6a2 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx @@ -93,6 +93,7 @@ const ProductVariantCreateTabs: React.FC< [classes.tabUnderline]: steps.findIndex(step => step.value === currentStep) >= stepIndex })} + key={step.value} > {step.label} diff --git a/src/products/components/ProductVariantCreateDialog/reducer.test.ts b/src/products/components/ProductVariantCreateDialog/reducer.test.ts index 5643c9816..39fd9395d 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.test.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.test.ts @@ -190,4 +190,17 @@ describe("Reducer is able to", () => { fourthStep.variants[variantIndex - 1].priceOverride ); }); + + it("delete variant", () => { + const variantIndex = 3; + + const state = execActions(fourthStep, reducer, [ + { + type: "deleteVariant", + variantIndex + } + ]); + + expect(state.variants.length).toBe(fourthStep.variants.length - 1); + }); }); diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index f1492ed22..367a69320 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -1,4 +1,10 @@ -import { add, remove, toggle, updateAtIndex } from "@saleor/utils/lists"; +import { + add, + remove, + toggle, + updateAtIndex, + removeAtIndex +} from "@saleor/utils/lists"; import { createVariants } from "./createVariants"; import { initialForm, ProductVariantCreateFormData } from "./form"; @@ -14,6 +20,7 @@ export type ProductVariantCreateReducerActionType = | "changeAttributeValuePrice" | "changeAttributeValueStock" | "changeVariantData" + | "deleteVariant" | "selectAttribute" | "selectValue"; @@ -288,6 +295,16 @@ function changeVariantData( }; } +function deleteVariant( + state: ProductVariantCreateFormData, + variantIndex: number +): ProductVariantCreateFormData { + return { + ...state, + variants: removeAtIndex(state.variants, variantIndex) + }; +} + function reduceProductVariantCreateFormData( prevState: ProductVariantCreateFormData, action: ProductVariantCreateReducerAction @@ -322,6 +339,8 @@ function reduceProductVariantCreateFormData( action.value, action.variantIndex ); + case "deleteVariant": + return deleteVariant(prevState, action.variantIndex); default: return prevState; } diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 3c6fd9f16..a1f38c0cc 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -82263,6 +82263,35 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul +
+ +
+
+ +
+
+ +
+
+ +
@@ -82859,6 +82975,35 @@ exports[`Storyshots Views / Products / Create multiple variants / summary errors +
+ +
+
+ +
+
+ +
+
+ +
From 0148bc452703d294717a4b1e1565cee3e95166a7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 3 Oct 2019 11:02:37 +0200 Subject: [PATCH 16/48] Fix build warnings --- .../ProductVariantCreatePrices.tsx | 4 ++-- .../ProductVariantCreateSummary.tsx | 2 +- .../ProductVariantCreateValues.tsx | 7 ++++--- .../components/ProductVariantCreateDialog/reducer.ts | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index 2b9148375..480fbfa6b 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -85,7 +85,7 @@ const ProductVariantCreatePrices: React.FC< return ( <> - + )} - + - + {attributes.map(attribute => ( - <> - + + {maybe(() => attribute.name, )}
@@ -57,10 +57,11 @@ const ProductVariantCreateValues: React.FC< name={`value:${value.slug}`} label={value.name} onChange={() => onValueClick(attribute.id, value.slug)} + key={value.slug} /> ))} - +
))} ); diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index 367a69320..12d9de6f5 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -1,9 +1,9 @@ import { add, remove, + removeAtIndex, toggle, - updateAtIndex, - removeAtIndex + updateAtIndex } from "@saleor/utils/lists"; import { createVariants } from "./createVariants"; import { initialForm, ProductVariantCreateFormData } from "./form"; From 36396c9d3ebd040d7774c633014d3ff5e5e3d060 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 3 Oct 2019 16:49:58 +0200 Subject: [PATCH 17/48] Remove attribute selection --- .../ProductVariantCreateAttributes.tsx | 77 ------------------- .../ProductVariantCreateContent.tsx | 13 ---- .../ProductVariantCreateDialog.tsx | 38 +++++---- .../ProductVariantCreateTabs.tsx | 7 -- .../ProductVariantCreateDialog/form.ts | 15 +++- .../ProductVariantCreateDialog/reducer.ts | 37 +++------ .../ProductVariantCreateDialog/types.ts | 6 +- .../views/ProductUpdate/ProductUpdate.tsx | 3 + 8 files changed, 47 insertions(+), 149 deletions(-) delete mode 100644 src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx deleted file mode 100644 index 48b658d9f..000000000 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableRow from "@material-ui/core/TableRow"; -import makeStyles from "@material-ui/styles/makeStyles"; -import React from "react"; -import { FormattedMessage } from "react-intl"; - -import Checkbox from "@saleor/components/Checkbox"; -import { maybe, renderCollection } from "@saleor/misc"; -import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; -import { ProductVariantCreateFormData } from "./form"; - -export interface ProductVariantCreateAttributesProps { - attributes: ProductDetails_product_productType_variantAttributes[]; - data: ProductVariantCreateFormData; - onAttributeClick: (id: string) => void; -} - -const useStyles = makeStyles({ - checkboxCell: { - paddingLeft: 0 - }, - wideCell: { - width: "100%" - } -}); - -const ProductVariantCreateAttributes: React.FC< - ProductVariantCreateAttributesProps -> = props => { - const { attributes, data, onAttributeClick } = props; - const classes = useStyles(props); - - return ( - - - {renderCollection( - attributes, - attribute => { - if (!attribute) { - return null; - } - const isChecked = !!data.attributes.find( - selectedAttribute => selectedAttribute.id === attribute.id - ); - - return ( - attribute.id)}> - - onAttributeClick(attribute.id)} - /> - - - {attribute.name} - - - ); - }, - () => ( - - - - - - ) - )} - -
- ); -}; - -ProductVariantCreateAttributes.displayName = "ProductVariantCreateAttributes"; -export default ProductVariantCreateAttributes; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index d451c1a75..ef044632a 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -5,7 +5,6 @@ import { ProductDetails_product_productType_variantAttributes } from "@saleor/pr import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; import { ProductVariantCreateFormData } from "./form"; -import ProductVariantCreateAttributes from "./ProductVariantCreateAttributes"; import ProductVariantCreatePrices from "./ProductVariantCreatePrices"; import ProductVariantCreateSummary from "./ProductVariantCreateSummary"; import ProductVariantCreateTabs from "./ProductVariantCreateTabs"; @@ -54,18 +53,6 @@ const ProductVariantCreateContent: React.FC<
- {step === "attributes" && ( - - dispatchFormDataAction({ - attributeId, - type: "selectAttribute" - }) - } - /> - )} {step === "values" && ( 0; case "values": return data.attributes.every(attribute => attribute.values.length > 0); case "prices": @@ -77,6 +75,7 @@ export interface ProductVariantCreateDialogProps ProductVariantCreateContentProps, "data" | "dispatchFormDataAction" | "step" > { + defaultPrice: string; open: boolean; onClose: () => void; onSubmit: (data: ProductVariantBulkCreateInput[]) => void; @@ -85,17 +84,19 @@ export interface ProductVariantCreateDialogProps const ProductVariantCreateDialog: React.FC< ProductVariantCreateDialogProps > = props => { - const { open, onClose, onSubmit, ...contentProps } = props; + const { + attributes, + defaultPrice, + open, + onClose, + onSubmit, + ...contentProps + } = props; const classes = useStyles(props); - const [step, setStep] = React.useState( - "attributes" - ); + const [step, setStep] = React.useState("values"); function handleNextStep() { switch (step) { - case "attributes": - setStep("values"); - break; case "values": setStep("prices"); break; @@ -107,9 +108,6 @@ const ProductVariantCreateDialog: React.FC< function handlePrevStep() { switch (step) { - case "values": - setStep("attributes"); - break; case "prices": setStep("values"); break; @@ -121,7 +119,16 @@ const ProductVariantCreateDialog: React.FC< const [data, dispatchFormDataAction] = React.useReducer( reduceProductVariantCreateFormData, - initialForm + createInitialForm(attributes, defaultPrice) + ); + + React.useEffect( + () => + dispatchFormDataAction({ + data: createInitialForm(attributes, defaultPrice), + type: "reload" + }), + [attributes.length] ); return ( @@ -135,6 +142,7 @@ const ProductVariantCreateDialog: React.FC< - {step !== "attributes" && ( + {step !== "values" && ( + + 100g + + + + + +
+
+ Coffee Genre +
+
+
+ + +
+
+ Collar +
+
+
+ + + +
+
@@ -84070,284 +84296,6 @@ exports[`Storyshots Views / Products / Create multiple variants prices and SKU 1 `; -exports[`Storyshots Views / Products / Create multiple variants select values 1`] = ` -
-
-
-
-
-
- - Select Values - -
-
- - Prices and SKU - -
-
- - Summary - -
-
-
-
- Box Size -
-
-
- - - - -
-
- Coffee Genre -
-
-
- - -
-
- Collar -
-
-
- - - -
-
-
-
-
-
-`; - exports[`Storyshots Views / Products / Create product When loading 1`] = `
Date: Mon, 7 Oct 2019 12:02:09 +0200 Subject: [PATCH 22/48] Fix checkbox selection --- .../ProductVariantCreateValues.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx index b906904c3..3744bf90a 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -4,6 +4,7 @@ import makeStyles from "@material-ui/styles/makeStyles"; import React from "react"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; +import Debounce from "@saleor/components/Debounce"; import Hr from "@saleor/components/Hr"; import Skeleton from "@saleor/components/Skeleton"; import { maybe } from "@saleor/misc"; @@ -46,19 +47,26 @@ const ProductVariantCreateValues: React.FC<
{attribute.values.map(value => ( - attribute.id === dataAttribute.id - ).values, - (a, b) => a === b + onValueClick(attribute.id, value.slug)} + time={100} + > + {change => ( + attribute.id === dataAttribute.id + ).values, + (a, b) => a === b + )} + name={`value:${value.slug}`} + label={value.name} + onChange={change} + key={value.slug} + /> )} - name={`value:${value.slug}`} - label={value.name} - onChange={() => onValueClick(attribute.id, value.slug)} - key={value.slug} - /> + ))}
From 8e9e412865535717b9fabff6075f7eaaa879fea9 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 7 Oct 2019 14:20:27 +0200 Subject: [PATCH 23/48] Fix ripple clipping --- .../ProductVariantCreateContent.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index ef044632a..28ee51a60 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -1,6 +1,7 @@ +import { Theme } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; import React from "react"; -import { makeStyles } from "@material-ui/styles"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; @@ -12,12 +13,16 @@ import ProductVariantCreateValues from "./ProductVariantCreateValues"; import { ProductVariantCreateReducerAction } from "./reducer"; import { ProductVariantCreateStep } from "./types"; -const useStyles = makeStyles({ +const useStyles = makeStyles((theme: Theme) => ({ root: { maxHeight: 400, - overflowY: "scroll" + overflowY: "scroll", + paddingLeft: theme.spacing.unit * 3, + position: "relative", + right: theme.spacing.unit * 3, + width: `calc(100% + ${theme.spacing.unit * 3}px)` } -}); +})); export interface ProductVariantCreateContentProps { attributes: ProductDetails_product_productType_variantAttributes[]; From be674522e11910733a0e05df289676ea094838b8 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 7 Oct 2019 15:58:51 +0200 Subject: [PATCH 24/48] Shorten inputs --- .../ProductVariantCreateContent.tsx | 2 ++ .../ProductVariantCreatePrices.tsx | 25 ++++++++++++++--- .../__snapshots__/Stories.test.ts.snap | 27 ++++++++++++------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 28ee51a60..01944bf5c 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -16,6 +16,7 @@ import { ProductVariantCreateStep } from "./types"; const useStyles = makeStyles((theme: Theme) => ({ root: { maxHeight: 400, + overflowX: "hidden", overflowY: "scroll", paddingLeft: theme.spacing.unit * 3, position: "relative", @@ -74,6 +75,7 @@ const ProductVariantCreateContent: React.FC< {step === "prices" && ( dispatchFormDataAction({ diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index 480fbfa6b..d8d2fb0ea 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -20,6 +20,9 @@ const useStyles = makeStyles((theme: Theme) => ({ marginBottom: theme.spacing.unit, marginTop: theme.spacing.unit / 2 }, + hrAttribute: { + marginTop: theme.spacing.unit * 2 + }, label: { alignSelf: "center" }, @@ -31,6 +34,7 @@ const useStyles = makeStyles((theme: Theme) => ({ export type PriceOrStock = "price" | "stock"; export interface ProductVariantCreatePricesProps { attributes: ProductDetails_product_productType_variantAttributes[]; + currencySymbol: string; data: ProductVariantCreateFormData; onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void; onApplyToAllChange: (value: string, type: PriceOrStock) => void; @@ -47,6 +51,7 @@ const ProductVariantCreatePrices: React.FC< > = props => { const { attributes, + currencySymbol, data, onApplyPriceOrStockChange, onApplyToAllChange, @@ -108,6 +113,9 @@ const ProductVariantCreatePrices: React.FC< min: 0, type: "number" }} + InputProps={{ + endAdornment: currencySymbol + }} label={intl.formatMessage({ defaultMessage: "Price", id: "productVariantCreatePricesPriceInputLabel" @@ -128,7 +136,7 @@ const ProductVariantCreatePrices: React.FC< {!data.price.all && ( <> - +
+
{priceAttributeValues && priceAttributeValues.map((attributeValue, attributeValueIndex) => ( <> - +
{attributeValue.name}
@@ -166,6 +175,13 @@ const ProductVariantCreatePrices: React.FC< description: "variant price", id: "productVariantCreatePricesSetPricePlaceholder" })} + inputProps={{ + min: 0, + type: "number" + }} + InputProps={{ + endAdornment: currencySymbol + }} fullWidth value={data.price.values[attributeValueIndex].value} onChange={event => @@ -226,7 +242,7 @@ const ProductVariantCreatePrices: React.FC< {!data.stock.all && ( <> - +
+
{stockAttributeValues && stockAttributeValues.map((attributeValue, attributeValueIndex) => ( <> - +
{attributeValue.name}
diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index a4f3bf4d2..5cae43c9c 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -83850,7 +83850,7 @@ exports[`Storyshots Views / Products / Create multiple variants prices and SKU 1 Price
+
@@ -84167,7 +84173,7 @@ exports[`Storyshots Views / Products / Create multiple variants prices and SKU 1 class="FormSpacer-spacer-id" />
+
Date: Mon, 7 Oct 2019 16:06:14 +0200 Subject: [PATCH 25/48] Fix bug after taking step back --- .../ProductVariantCreatePrices.tsx | 14 +++++++++++--- .../ProductVariantCreateDialog/reducer.ts | 14 +++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index d8d2fb0ea..74d692bfd 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -183,7 +183,11 @@ const ProductVariantCreatePrices: React.FC< endAdornment: currencySymbol }} fullWidth - value={data.price.values[attributeValueIndex].value} + value={ + data.price.values.find( + value => value.slug === attributeValue.slug + ).value + } onChange={event => onAttributeValueChange( attributeValue.slug, @@ -267,7 +271,7 @@ const ProductVariantCreatePrices: React.FC<
{stockAttributeValues && - stockAttributeValues.map((attributeValue, attributeValueIndex) => ( + stockAttributeValues.map(attributeValue => ( <> @@ -282,7 +286,11 @@ const ProductVariantCreatePrices: React.FC< id: "productVariantCreatePricesSetStockPlaceholder" })} fullWidth - value={data.stock.values[attributeValueIndex].value} + value={ + data.stock.values.find( + value => value.slug === attributeValue.slug + ).value + } onChange={event => onAttributeValueChange( attributeValue.slug, diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index aa5b09ef2..d8c05c2a2 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -55,7 +55,19 @@ function selectValue( return { ...prevState, - attributes: updatedAttributes + attributes: updatedAttributes, + price: { + ...prevState.price, + all: true, + attribute: undefined, + values: [] + }, + stock: { + ...prevState.stock, + all: true, + attribute: undefined, + values: [] + } }; } From a770c464c7f7d6a162eb77469beca2502de41af7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 7 Oct 2019 16:54:05 +0200 Subject: [PATCH 26/48] Remove unused value --- .../ProductVariantCreateDialog/ProductVariantCreatePrices.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index 74d692bfd..2880d8ad4 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -161,7 +161,7 @@ const ProductVariantCreatePrices: React.FC<
{priceAttributeValues && - priceAttributeValues.map((attributeValue, attributeValueIndex) => ( + priceAttributeValues.map(attributeValue => ( <> From 03374fb00bf3a55477539bff3c4ae997b691cd51 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 8 Oct 2019 12:56:24 +0200 Subject: [PATCH 27/48] Minor fixes --- .../ProductVariantCreateContent.tsx | 6 ++- .../ProductVariantCreateDialog.tsx | 10 ++++- .../ProductVariantCreateTabs.tsx | 44 +++++++++++-------- .../ProductVariantCreateValues.tsx | 2 +- .../__snapshots__/Stories.test.ts.snap | 18 ++++---- src/theme.ts | 2 +- 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 01944bf5c..8e5462237 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -32,6 +32,7 @@ export interface ProductVariantCreateContentProps { dispatchFormDataAction: React.Dispatch; errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; step: ProductVariantCreateStep; + onStepClick: (step: ProductVariantCreateStep) => void; } const ProductVariantCreateContent: React.FC< @@ -43,7 +44,8 @@ const ProductVariantCreateContent: React.FC< data, dispatchFormDataAction, errors, - step + step, + onStepClick } = props; const classes = useStyles(props); @@ -57,7 +59,7 @@ const ProductVariantCreateContent: React.FC< return (
- +
{step === "values" && ( ({ overflowX: "visible", overflowY: "hidden", width: 800 + }, + spacer: { + flex: 1 } })); @@ -146,19 +149,24 @@ const ProductVariantCreateDialog: React.FC< data={data} dispatchFormDataAction={dispatchFormDataAction} step={step} + onStepClick={step => setStep(step)} /> +
{step !== "values" && ( )} {step !== "summary" ? ( diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx index 5dca6bf57..3d4667f23 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateTabs.tsx @@ -51,13 +51,15 @@ const useStyles = makeStyles( }, tab: { flex: 1, - paddingBottom: theme.spacing.unit + paddingBottom: theme.spacing.unit, + userSelect: "none" }, tabActive: { fontWeight: 600 }, - tabUnderline: { - borderBottom: `3px solid ${theme.palette.primary.main}` + tabVisited: { + borderBottom: `3px solid ${theme.palette.primary.main}`, + cursor: "pointer" } }), { @@ -67,32 +69,38 @@ const useStyles = makeStyles( export interface ProductVariantCreateTabsProps { step: ProductVariantCreateStep; + onStepClick: (step: ProductVariantCreateStep) => void; } const ProductVariantCreateTabs: React.FC< ProductVariantCreateTabsProps > = props => { - const { step: currentStep } = props; + const { step: currentStep, onStepClick } = props; const classes = useStyles(props); const intl = useIntl(); const steps = getSteps(intl); return (
- {steps.map((step, stepIndex) => ( -
step.value === currentStep) >= stepIndex - })} - key={step.value} - > - - {step.label} - -
- ))} + {steps.map((step, stepIndex) => { + const visitedStep = + steps.findIndex(step => step.value === currentStep) >= stepIndex; + + return ( +
onStepClick(step.value) : undefined} + key={step.value} + > + + {step.label} + +
+ ); + })}
); }; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx index 3744bf90a..dd6fe6dc3 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -50,6 +50,7 @@ const ProductVariantCreateValues: React.FC< onValueClick(attribute.id, value.slug)} time={100} + key={value.slug} > {change => ( )} diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 5cae43c9c..c97b20359 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -82067,7 +82067,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul class="ProductVariantCreateTabs-root-id" >
}, flat: { "& span": { - color: colors.primary + color: colors.font.gray } }, flatPrimary: { From 485a471fe3850e671eb0da44923f86daa0ba3ede Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 8 Oct 2019 13:34:47 +0200 Subject: [PATCH 28/48] Update messages --- locale/messages.pot | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index 78fc1bb3a..1dcc13d48 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-10-09T10:24:36.690Z\n" +"POT-Creation-Date: 2019-10-09T10:25:56.800Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -1487,10 +1487,6 @@ msgstr "" #. [src.back] - button #. defaultMessage is: #. Back -#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.json -#. [src.products.components.ProductVariantCreateDialog.1347475195] - button -#. defaultMessage is: -#. Back msgctxt "button" msgid "Back" msgstr "" @@ -5743,6 +5739,14 @@ msgctxt "order payment" msgid "Preauthorized amount" msgstr "" +#: build/locale/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.json +#. [src.products.components.ProductVariantCreateDialog.904693740] - previous step, button +#. defaultMessage is: +#. Previous +msgctxt "previous step, button" +msgid "Previous" +msgstr "" + #: build/locale/src/categories/components/CategoryProductList/CategoryProductList.json #. [src.categories.components.CategoryProductList.1134347598] - product price #. defaultMessage is: From 0318074806f8413c6b41e0369ecdba17db728ad8 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 8 Oct 2019 14:08:41 +0200 Subject: [PATCH 29/48] Fix types --- .../ProductVariantCreateDialog/ProductVariantCreateDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index f46e984c2..dcb42c9db 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -76,7 +76,7 @@ function canHitNext( export interface ProductVariantCreateDialogProps extends Omit< ProductVariantCreateContentProps, - "data" | "dispatchFormDataAction" | "step" + "data" | "dispatchFormDataAction" | "step" | "onStepClick" > { defaultPrice: string; open: boolean; From 12aed6457ed639fb4dfe1953a99c308f971ebb60 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 8 Oct 2019 14:38:15 +0200 Subject: [PATCH 30/48] Fix story --- .../ProductVariantCreateDialog/ProductVariantCreate.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx index 31722b97b..2caf5fba2 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -69,6 +69,7 @@ const props: ProductVariantCreateContentProps = { }, dispatchFormDataAction: () => undefined, errors: [], + onStepClick: () => undefined, step: "values" }; From b75ceed7cce8b706afbfd1c306dde2698d7dcae7 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 9 Oct 2019 12:45:27 +0200 Subject: [PATCH 31/48] Add attribute values to matrix --- .../ProductVariantCreateDialog/reducer.ts | 39 ++++++++++++++----- .../__snapshots__/Stories.test.ts.snap | 8 ++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index d8c05c2a2..ee8480ef0 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -1,3 +1,4 @@ +//#region import { add, remove, @@ -35,16 +36,16 @@ export interface ProductVariantCreateReducerAction { valueId?: string; variantIndex?: number; } - +//#endregion function selectValue( prevState: ProductVariantCreateFormData, attributeId: string, - valueId: string + valueSlug: string ): ProductVariantCreateFormData { const attribute = prevState.attributes.find( attribute => attribute.id === attributeId ); - const values = toggle(valueId, attribute.values, (a, b) => a === b); + const values = toggle(valueSlug, attribute.values, (a, b) => a === b); const updatedAttributes = add( { id: attributeId, @@ -53,20 +54,40 @@ function selectValue( remove(attribute, prevState.attributes, (a, b) => a.id === b.id) ); + const priceValues = + prevState.price.attribute === attributeId + ? toggle( + { + slug: valueSlug, + value: "" + }, + prevState.price.values, + (a, b) => a.slug === b.slug + ) + : prevState.price.values; + + const stockValues = + prevState.stock.attribute === attributeId + ? toggle( + { + slug: valueSlug, + value: "" + }, + prevState.stock.values, + (a, b) => a.slug === b.slug + ) + : prevState.stock.values; + return { ...prevState, attributes: updatedAttributes, price: { ...prevState.price, - all: true, - attribute: undefined, - values: [] + values: priceValues }, stock: { ...prevState.stock, - all: true, - attribute: undefined, - values: [] + values: stockValues } }; } diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index c97b20359..f1fffe1cf 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -82056,7 +82056,7 @@ exports[`Storyshots Views / Products / Create multiple variants / summary defaul style="padding:24px" >
Date: Wed, 9 Oct 2019 12:52:51 +0200 Subject: [PATCH 32/48] Reset form state after closing it --- .../ProductVariantCreateDialog.tsx | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index dcb42c9db..c898e9157 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -8,6 +8,8 @@ import { makeStyles } from "@material-ui/styles"; import React from "react"; import { FormattedMessage } from "react-intl"; +import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; +import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; import { createInitialForm, ProductVariantCreateFormData } from "./form"; import ProductVariantCreateContent, { @@ -90,6 +92,7 @@ const ProductVariantCreateDialog: React.FC< const { attributes, defaultPrice, + errors: apiErrors, open, onClose, onSubmit, @@ -125,14 +128,22 @@ const ProductVariantCreateDialog: React.FC< createInitialForm(attributes, defaultPrice) ); - React.useEffect( - () => - dispatchFormDataAction({ - data: createInitialForm(attributes, defaultPrice), - type: "reload" - }), - [attributes.length] - ); + const reloadForm = () => + dispatchFormDataAction({ + data: createInitialForm(attributes, defaultPrice), + type: "reload" + }); + + React.useEffect(reloadForm, [attributes.length]); + + useModalDialogOpen(open, { + onClose: () => { + reloadForm(); + setStep("values"); + } + }); + + const errors = useModalDialogErrors(apiErrors, open); return ( @@ -148,6 +159,7 @@ const ProductVariantCreateDialog: React.FC< attributes={attributes} data={data} dispatchFormDataAction={dispatchFormDataAction} + errors={errors} step={step} onStepClick={step => setStep(step)} /> From da58dabeb69980da86a2997ebe42cbafbd3783d6 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 9 Oct 2019 13:31:45 +0200 Subject: [PATCH 33/48] Add right padding --- .../ProductVariantCreateDialog/ProductVariantCreateContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 8e5462237..cbc32664f 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -19,6 +19,7 @@ const useStyles = makeStyles((theme: Theme) => ({ overflowX: "hidden", overflowY: "scroll", paddingLeft: theme.spacing.unit * 3, + paddingRight: theme.spacing.unit * 2, position: "relative", right: theme.spacing.unit * 3, width: `calc(100% + ${theme.spacing.unit * 3}px)` From e58a0157b189ea7497c79ec4ba308af78d153406 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 9 Oct 2019 17:33:55 +0200 Subject: [PATCH 34/48] Capitalize headers --- .../CategoryBackground/CategoryBackground.tsx | 2 +- .../CollectionCreatePage.tsx | 2 +- .../CollectionImage/CollectionImage.tsx | 2 +- .../CustomerAddresses/CustomerAddresses.tsx | 4 +- .../CustomerCreateAddress.tsx | 2 +- .../CustomerCreateDetails.tsx | 2 +- .../CustomerCreatePage/CustomerCreatePage.tsx | 2 +- .../CustomerOrders/CustomerOrders.tsx | 2 +- src/customers/views/CustomerDetails.tsx | 2 +- .../views/CustomerList/CustomerList.tsx | 2 +- .../HomeProductListCard.tsx | 2 +- src/navigation/views/MenuDetails/index.tsx | 2 +- src/navigation/views/MenuList.tsx | 4 +- .../OrderAddressEditDialog.tsx | 4 +- .../OrderCancelDialog/OrderCancelDialog.tsx | 2 +- .../OrderCustomer/OrderCustomer.tsx | 2 +- .../OrderCustomerEditDialog.tsx | 2 +- .../OrderDraftCancelDialog.tsx | 2 +- .../OrderDraftFinalizeDialog.tsx | 2 +- .../OrderFulfillmentDialog.tsx | 2 +- .../OrderFulfillmentTrackingDialog.tsx | 2 +- src/pages/views/PageCreate.tsx | 2 +- .../ShippingZonesList/ShippingZonesList.tsx | 2 +- .../ShippingZoneDetailsDialogs.tsx | 2 +- .../SiteSettingsAddress.tsx | 2 +- .../__snapshots__/Stories.test.ts.snap | 130 +++++++++--------- 26 files changed, 93 insertions(+), 93 deletions(-) diff --git a/src/categories/components/CategoryBackground/CategoryBackground.tsx b/src/categories/components/CategoryBackground/CategoryBackground.tsx index 2c5d9925e..2d43de581 100644 --- a/src/categories/components/CategoryBackground/CategoryBackground.tsx +++ b/src/categories/components/CategoryBackground/CategoryBackground.tsx @@ -60,7 +60,7 @@ const CategoryBackground: React.FC = props => { diff --git a/src/collections/components/CollectionImage/CollectionImage.tsx b/src/collections/components/CollectionImage/CollectionImage.tsx index 1ebd5eb85..8162c60b6 100644 --- a/src/collections/components/CollectionImage/CollectionImage.tsx +++ b/src/collections/components/CollectionImage/CollectionImage.tsx @@ -80,7 +80,7 @@ export const CollectionImage = withStyles(styles)( @@ -84,7 +84,7 @@ const CustomerAddresses = withStyles(styles, { name: "CustomerAddresses" })( diff --git a/src/customers/components/CustomerCreateAddress/CustomerCreateAddress.tsx b/src/customers/components/CustomerCreateAddress/CustomerCreateAddress.tsx index 40012bae8..efc42590a 100644 --- a/src/customers/components/CustomerCreateAddress/CustomerCreateAddress.tsx +++ b/src/customers/components/CustomerCreateAddress/CustomerCreateAddress.tsx @@ -47,7 +47,7 @@ const CustomerCreateAddress = withStyles(styles, { diff --git a/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.tsx b/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.tsx index b64276b28..f5220669f 100644 --- a/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.tsx +++ b/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.tsx @@ -48,7 +48,7 @@ const CustomerCreateDetails = withStyles(styles, { diff --git a/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx b/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx index c40bb6929..3b89b0157 100644 --- a/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx +++ b/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx @@ -89,7 +89,7 @@ const CustomerCreatePage: React.StatelessComponent = ({ diff --git a/src/customers/components/CustomerOrders/CustomerOrders.tsx b/src/customers/components/CustomerOrders/CustomerOrders.tsx index 1fc5e0d26..61a8cb392 100644 --- a/src/customers/components/CustomerOrders/CustomerOrders.tsx +++ b/src/customers/components/CustomerOrders/CustomerOrders.tsx @@ -51,7 +51,7 @@ const CustomerOrders = withStyles(styles, { name: "CustomerOrders" })( navigate(customerUrl(id), true)} onConfirm={() => removeCustomer()} title={intl.formatMessage({ - defaultMessage: "Delete customer", + defaultMessage: "Delete Customer", description: "dialog header" })} variant="delete" diff --git a/src/customers/views/CustomerList/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx index 68348ee99..3742426c6 100644 --- a/src/customers/views/CustomerList/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -216,7 +216,7 @@ export const CustomerList: React.StatelessComponent = ({ } variant="delete" title={intl.formatMessage({ - defaultMessage: "Delete customers", + defaultMessage: "Delete Customers", description: "dialog header" })} > diff --git a/src/home/components/HomeProductListCard/HomeProductListCard.tsx b/src/home/components/HomeProductListCard/HomeProductListCard.tsx index 6b5964d18..80fb454ce 100644 --- a/src/home/components/HomeProductListCard/HomeProductListCard.tsx +++ b/src/home/components/HomeProductListCard/HomeProductListCard.tsx @@ -57,7 +57,7 @@ export const HomeProductList = withStyles(styles, { name: "HomeProductList" })( = ({ id, params }) => { } variant="delete" title={intl.formatMessage({ - defaultMessage: "Delete menu", + defaultMessage: "Delete Menu", description: "dialog header", id: "menuDetailsDeleteMenuHeader" })} diff --git a/src/navigation/views/MenuList.tsx b/src/navigation/views/MenuList.tsx index 2995e0199..9781d0697 100644 --- a/src/navigation/views/MenuList.tsx +++ b/src/navigation/views/MenuList.tsx @@ -202,7 +202,7 @@ const MenuList: React.FC = ({ params }) => { } variant="delete" title={intl.formatMessage({ - defaultMessage: "Delete menu", + defaultMessage: "Delete Menu", description: "dialog header", id: "menuListDeleteMenuHeader" })} @@ -239,7 +239,7 @@ const MenuList: React.FC = ({ params }) => { } variant="delete" title={intl.formatMessage({ - defaultMessage: "Delete menus", + defaultMessage: "Delete Menus", description: "dialog header", id: "menuListDeleteMenusHeader" })} diff --git a/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.tsx b/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.tsx index e6468a9bf..b79f0777f 100644 --- a/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.tsx +++ b/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.tsx @@ -83,11 +83,11 @@ const OrderAddressEditDialog = withStyles(styles, { {variant === "billing" ? intl.formatMessage({ - defaultMessage: "Edit billing address", + defaultMessage: "Edit Billing Address", description: "dialog header" }) : intl.formatMessage({ - defaultMessage: "Edit shipping address", + defaultMessage: "Edit Shipping Address", description: "dialog header" })} diff --git a/src/orders/components/OrderCancelDialog/OrderCancelDialog.tsx b/src/orders/components/OrderCancelDialog/OrderCancelDialog.tsx index 0bc4e8e13..0546b5528 100644 --- a/src/orders/components/OrderCancelDialog/OrderCancelDialog.tsx +++ b/src/orders/components/OrderCancelDialog/OrderCancelDialog.tsx @@ -67,7 +67,7 @@ const OrderCancelDialog = withStyles(styles, { name: "OrderCancelDialog" })( <> diff --git a/src/orders/components/OrderCustomer/OrderCustomer.tsx b/src/orders/components/OrderCustomer/OrderCustomer.tsx index 3f800300c..55fadd593 100644 --- a/src/orders/components/OrderCustomer/OrderCustomer.tsx +++ b/src/orders/components/OrderCustomer/OrderCustomer.tsx @@ -187,7 +187,7 @@ const OrderCustomer = withStyles(styles, { name: "OrderCustomer" })(
diff --git a/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.tsx b/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.tsx index a629fc285..891e901c7 100644 --- a/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.tsx +++ b/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.tsx @@ -79,7 +79,7 @@ const OrderCustomerEditDialog = withStyles(styles, { diff --git a/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.tsx b/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.tsx index 0baeb130c..a4cbe1e7d 100644 --- a/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.tsx +++ b/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.tsx @@ -25,7 +25,7 @@ const OrderDraftCancelDialog: React.StatelessComponent< onConfirm={onConfirm} open={open} title={intl.formatMessage({ - defaultMessage: "Delete draft order", + defaultMessage: "Delete Daft Order", description: "dialog header" })} variant="delete" diff --git a/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.tsx b/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.tsx index a6138c3aa..91ae73a5f 100644 --- a/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.tsx +++ b/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.tsx @@ -64,7 +64,7 @@ const OrderDraftFinalizeDialog: React.StatelessComponent< onConfirm={onConfirm} open={open} title={intl.formatMessage({ - defaultMessage: "Finalize draft order", + defaultMessage: "Finalize Draft Order", description: "dialog header" })} confirmButtonLabel={ diff --git a/src/orders/components/OrderFulfillmentDialog/OrderFulfillmentDialog.tsx b/src/orders/components/OrderFulfillmentDialog/OrderFulfillmentDialog.tsx index 6c7bfcee6..12541335f 100644 --- a/src/orders/components/OrderFulfillmentDialog/OrderFulfillmentDialog.tsx +++ b/src/orders/components/OrderFulfillmentDialog/OrderFulfillmentDialog.tsx @@ -120,7 +120,7 @@ const OrderFulfillmentDialog = withStyles(styles, { <> diff --git a/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.tsx b/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.tsx index 4de13a599..0bfa43ab8 100644 --- a/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.tsx +++ b/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.tsx @@ -37,7 +37,7 @@ const OrderFulfillmentTrackingDialog: React.StatelessComponent< <> diff --git a/src/pages/views/PageCreate.tsx b/src/pages/views/PageCreate.tsx index d264b709e..ff431ff8b 100644 --- a/src/pages/views/PageCreate.tsx +++ b/src/pages/views/PageCreate.tsx @@ -43,7 +43,7 @@ export const PageCreate: React.StatelessComponent = () => { <> diff --git a/src/shipping/components/ShippingZonesList/ShippingZonesList.tsx b/src/shipping/components/ShippingZonesList/ShippingZonesList.tsx index 56c2b6ff7..920d955b3 100644 --- a/src/shipping/components/ShippingZonesList/ShippingZonesList.tsx +++ b/src/shipping/components/ShippingZonesList/ShippingZonesList.tsx @@ -81,7 +81,7 @@ const ShippingZonesList = withStyles(styles, { name: "ShippingZonesList" })( diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index f1fffe1cf..3763dfa36 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -6050,7 +6050,7 @@ exports[`Storyshots Orders / OrderCustomer default 1`] = `

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Background image (optional) + Background Image (optional)
- Add collection + Add Collection
- Background image (optional) + Background Image (optional)
- Add collection + Add Collection
- Background image (optional) + Background Image (optional)
- Add customer + Add Customer
- Customer overview + Customer Overview
- Primary address + Primary Address
- Add customer + Add Customer
- Customer overview + Customer Overview
- Primary address + Primary Address
- Add customer + Add Customer
- Customer overview + Customer Overview
- Primary address + Primary Address
- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Billing address + Billing Address

- Shipping address + Shipping Address

- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Recent orders + Recent Orders
- Shipping address + Shipping Address

- Recent orders + Recent Orders
- Billing address + Billing Address

- Top products + Top Products
- Top products + Top Products
- Top products + Top Products
- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Contact information + Contact Information

- Shipping by zone + Shipping By Zone

- Shipping by zone + Shipping By Zone
- Shipping by zone + Shipping By Zone
- Store information + Store Information
- Store information + Store Information
- Store information + Store Information
Date: Wed, 9 Oct 2019 17:35:42 +0200 Subject: [PATCH 35/48] Add messages --- locale/messages.pot | 358 ++++++++++++++++++++++---------------------- 1 file changed, 179 insertions(+), 179 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index 1dcc13d48..4f2858b52 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-10-09T10:25:56.800Z\n" +"POT-Creation-Date: 2019-10-09T15:30:47.333Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -131,6 +131,22 @@ msgctxt "dialog title" msgid "Add Address" msgstr "" +#: build/locale/src/collections/components/CollectionCreatePage/CollectionCreatePage.json +#. [src.collections.components.CollectionCreatePage.951411809] - page header +#. defaultMessage is: +#. Add Collection +msgctxt "page header" +msgid "Add Collection" +msgstr "" + +#: build/locale/src/customers/components/CustomerCreatePage/CustomerCreatePage.json +#. [src.customers.components.CustomerCreatePage.2622255457] - page header +#. defaultMessage is: +#. Add Customer +msgctxt "page header" +msgid "Add Customer" +msgstr "" + #: build/locale/src/components/Filter/Filter.json #. [src.components.Filter.2852521946] - button #. defaultMessage is: @@ -171,6 +187,14 @@ msgctxt "dialog header" msgid "Add Price Rate" msgstr "" +#: build/locale/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.json +#. [src.orders.components.OrderFulfillmentTrackingDialog.606831229] - dialog header +#. defaultMessage is: +#. Add Tracking Code +msgctxt "dialog header" +msgid "Add Tracking Code" +msgstr "" + #: build/locale/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.json #. [src.attributes.components.AttributeValueEditDialog.1841790893] - add attribute value #. defaultMessage is: @@ -203,22 +227,6 @@ msgctxt "button" msgid "Add authentication" msgstr "" -#: build/locale/src/collections/components/CollectionCreatePage/CollectionCreatePage.json -#. [src.collections.components.CollectionCreatePage.3958681866] - page header -#. defaultMessage is: -#. Add collection -msgctxt "page header" -msgid "Add collection" -msgstr "" - -#: build/locale/src/customers/components/CustomerCreatePage/CustomerCreatePage.json -#. [src.customers.components.CustomerCreatePage.1934221653] - page header -#. defaultMessage is: -#. Add customer -msgctxt "page header" -msgid "Add customer" -msgstr "" - #: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json #. [src.customers.components.CustomerListPage.1934221653] - button #. defaultMessage is: @@ -359,14 +367,6 @@ msgctxt "fulfillment group tracking number" msgid "Add tracking" msgstr "" -#: build/locale/src/orders/components/OrderFulfillmentTrackingDialog/OrderFulfillmentTrackingDialog.json -#. [src.orders.components.OrderFulfillmentTrackingDialog.3680864271] - dialog header -#. defaultMessage is: -#. Add tracking code -msgctxt "dialog header" -msgid "Add tracking code" -msgstr "" - #: build/locale/src/products/components/ProductVariantNavigation/ProductVariantNavigation.json #. [src.products.components.ProductVariantNavigation.2845381934] - button #. defaultMessage is: @@ -1508,15 +1508,15 @@ msgid "Back to login" msgstr "" #: build/locale/src/categories/components/CategoryBackground/CategoryBackground.json -#. [src.categories.components.CategoryBackground.1849089820] - section header +#. [src.categories.components.CategoryBackground.2224943474] - section header #. defaultMessage is: -#. Background image (optional) +#. Background Image (optional) #: build/locale/src/collections/components/CollectionImage/CollectionImage.json -#. [src.collections.components.CollectionImage.1849089820] - section header +#. [src.collections.components.CollectionImage.2224943474] - section header #. defaultMessage is: -#. Background image (optional) +#. Background Image (optional) msgctxt "section header" -msgid "Background image (optional)" +msgid "Background Image (optional)" msgstr "" #: build/locale/src/misc.json @@ -1527,6 +1527,14 @@ msgctxt "tax rate" msgid "Bikes" msgstr "" +#: build/locale/src/customers/components/CustomerAddresses/CustomerAddresses.json +#. [src.customers.components.CustomerAddresses.4282475982] - subsection header +#. defaultMessage is: +#. Billing Address +msgctxt "subsection header" +msgid "Billing Address" +msgstr "" + #: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json #. [src.orders.components.OrderCustomer.4282475982] #. defaultMessage is: @@ -1535,14 +1543,6 @@ msgctxt "description" msgid "Billing Address" msgstr "" -#: build/locale/src/customers/components/CustomerAddresses/CustomerAddresses.json -#. [src.customers.components.CustomerAddresses.2428885633] - subsection header -#. defaultMessage is: -#. Billing address -msgctxt "subsection header" -msgid "Billing address" -msgstr "" - #: build/locale/src/misc.json #. [src.books] - tax rate #. defaultMessage is: @@ -1579,6 +1579,14 @@ msgctxt "dialog header" msgid "Cancel Fulfillment" msgstr "" +#: build/locale/src/orders/components/OrderCancelDialog/OrderCancelDialog.json +#. [src.orders.components.OrderCancelDialog.1258942306] - dialog header +#. defaultMessage is: +#. Cancel Order +msgctxt "dialog header" +msgid "Cancel Order" +msgstr "" + #: build/locale/src/orders/components/OrderBulkCancelDialog/OrderBulkCancelDialog.json #. [src.orders.components.OrderBulkCancelDialog.1528036340] - dialog header #. defaultMessage is: @@ -1595,14 +1603,6 @@ msgctxt "button" msgid "Cancel fulfillment" msgstr "" -#: build/locale/src/orders/components/OrderCancelDialog/OrderCancelDialog.json -#. [src.orders.components.OrderCancelDialog.1854613983] - dialog header -#. defaultMessage is: -#. Cancel order -msgctxt "dialog header" -msgid "Cancel order" -msgstr "" - #: build/locale/src/orders/components/OrderDetailsPage/OrderDetailsPage.json #. [src.orders.components.OrderDetailsPage.1854613983] - button #. defaultMessage is: @@ -1984,11 +1984,11 @@ msgid "Confirm Password" msgstr "" #: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json -#. [src.orders.components.OrderCustomer.1111991638] - subheader +#. [src.orders.components.OrderCustomer.2312694610] - subheader #. defaultMessage is: -#. Contact information +#. Contact Information msgctxt "subheader" -msgid "Contact information" +msgid "Contact Information" msgstr "" #: build/locale/src/pages/components/PageInfo/PageInfo.json @@ -2267,6 +2267,14 @@ msgctxt "page header" msgid "Create Page" msgstr "" +#: build/locale/src/pages/views/PageCreate.json +#. [src.pages.views.1068617485] - header +#. defaultMessage is: +#. Create Page +msgctxt "header" +msgid "Create Page" +msgstr "" + #: build/locale/src/orders/components/OrderProductAddDialog/OrderProductAddDialog.json #. [src.orders.components.OrderProductAddDialog.1542417144] - dialog header #. defaultMessage is: @@ -2447,14 +2455,6 @@ msgctxt "button" msgid "Create page" msgstr "" -#: build/locale/src/pages/views/PageCreate.json -#. [src.pages.views.3785394515] - header -#. defaultMessage is: -#. Create page -msgctxt "header" -msgid "Create page" -msgstr "" - #: build/locale/src/shipping/components/ShippingZoneRateDialog/ShippingZoneRateDialog.json #. [src.shipping.components.ShippingZoneRateDialog.16061680] - button #. defaultMessage is: @@ -2611,6 +2611,14 @@ msgctxt "description" msgid "Customer Name" msgstr "" +#: build/locale/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.json +#. [src.customers.components.CustomerCreateDetails.3063084773] - header +#. defaultMessage is: +#. Customer Overview +msgctxt "header" +msgid "Customer Overview" +msgstr "" + #: build/locale/src/customers/views/CustomerDetails.json #. [src.customers.views.3901579344] #. defaultMessage is: @@ -2627,14 +2635,6 @@ msgctxt "description" msgid "Customer created" msgstr "" -#: build/locale/src/customers/components/CustomerCreateDetails/CustomerCreateDetails.json -#. [src.customers.components.CustomerCreateDetails.4157831287] - header -#. defaultMessage is: -#. Customer overview -msgctxt "header" -msgid "Customer overview" -msgstr "" - #: build/locale/src/customers/components/CustomerDetails/CustomerDetails.json #. [src.customers.components.CustomerDetails.2200102325] - section subheader #. defaultMessage is: @@ -2811,6 +2811,30 @@ msgctxt "dialog title" msgid "Delete Collection" msgstr "" +#: build/locale/src/customers/views/CustomerDetails.json +#. [src.customers.views.1998583641] - dialog header +#. defaultMessage is: +#. Delete Customer +msgctxt "dialog header" +msgid "Delete Customer" +msgstr "" + +#: build/locale/src/customers/views/CustomerList/CustomerList.json +#. [src.customers.views.CustomerList.2136923553] - dialog header +#. defaultMessage is: +#. Delete Customers +msgctxt "dialog header" +msgid "Delete Customers" +msgstr "" + +#: build/locale/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.json +#. [src.orders.components.OrderDraftCancelDialog.1961675716] - dialog header +#. defaultMessage is: +#. Delete Daft Order +msgctxt "dialog header" +msgid "Delete Daft Order" +msgstr "" + #: build/locale/src/products/views/ProductImage.json #. [src.products.views.1731766393] - dialog header #. defaultMessage is: @@ -2819,6 +2843,26 @@ msgctxt "dialog header" msgid "Delete Image" msgstr "" +#: build/locale/src/navigation/views/MenuDetails/index.json +#. [menuDetailsDeleteMenuHeader] - dialog header +#. defaultMessage is: +#. Delete Menu +#: build/locale/src/navigation/views/MenuList.json +#. [menuListDeleteMenuHeader] - dialog header +#. defaultMessage is: +#. Delete Menu +msgctxt "dialog header" +msgid "Delete Menu" +msgstr "" + +#: build/locale/src/navigation/views/MenuList.json +#. [menuListDeleteMenusHeader] - dialog header +#. defaultMessage is: +#. Delete Menus +msgctxt "dialog header" +msgid "Delete Menus" +msgstr "" + #: build/locale/src/orders/views/OrderDraftList/OrderDraftList.json #. [src.orders.views.OrderDraftList.1161115149] - dialog header #. defaultMessage is: @@ -3059,36 +3103,12 @@ msgctxt "dialog title" msgid "Delete collections" msgstr "" -#: build/locale/src/customers/views/CustomerDetails.json -#. [src.customers.views.442409664] - dialog header -#. defaultMessage is: -#. Delete customer -msgctxt "dialog header" -msgid "Delete customer" -msgstr "" - -#: build/locale/src/customers/views/CustomerList/CustomerList.json -#. [src.customers.views.CustomerList.1946482599] - dialog header -#. defaultMessage is: -#. Delete customers -msgctxt "dialog header" -msgid "Delete customers" -msgstr "" - -#: build/locale/src/orders/components/OrderDraftCancelDialog/OrderDraftCancelDialog.json -#. [src.orders.components.OrderDraftCancelDialog.632633254] - dialog header -#. defaultMessage is: -#. Delete draft order -msgctxt "dialog header" -msgid "Delete draft order" -msgstr "" - #: build/locale/src/shipping/views/ShippingZoneDetails/ShippingZoneDetailsDialogs.json -#. [src.shipping.views.ShippingZoneDetails.1947090060] - unassign country, dialog header +#. [src.shipping.views.ShippingZoneDetails.3133838427] - unassign country, dialog header #. defaultMessage is: -#. Delete from shipping zone +#. Delete from Shipping Zone msgctxt "unassign country, dialog header" -msgid "Delete from shipping zone" +msgid "Delete from Shipping Zone" msgstr "" #: build/locale/src/collections/views/CollectionDetails.json @@ -3099,26 +3119,6 @@ msgctxt "dialog title" msgid "Delete image" msgstr "" -#: build/locale/src/navigation/views/MenuDetails/index.json -#. [menuDetailsDeleteMenuHeader] - dialog header -#. defaultMessage is: -#. Delete menu -#: build/locale/src/navigation/views/MenuList.json -#. [menuListDeleteMenuHeader] - dialog header -#. defaultMessage is: -#. Delete menu -msgctxt "dialog header" -msgid "Delete menu" -msgstr "" - -#: build/locale/src/navigation/views/MenuList.json -#. [menuListDeleteMenusHeader] - dialog header -#. defaultMessage is: -#. Delete menus -msgctxt "dialog header" -msgid "Delete menus" -msgstr "" - #: build/locale/src/staff/components/StaffProperties/StaffProperties.json #. [src.staff.components.StaffProperties.457748370] - button #. defaultMessage is: @@ -3391,6 +3391,22 @@ msgctxt "dialog title" msgid "Edit Address" msgstr "" +#: build/locale/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.json +#. [src.orders.components.OrderAddressEditDialog.3982060155] - dialog header +#. defaultMessage is: +#. Edit Billing Address +msgctxt "dialog header" +msgid "Edit Billing Address" +msgstr "" + +#: build/locale/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.json +#. [src.orders.components.OrderCustomerEditDialog.1549172886] - dialog header +#. defaultMessage is: +#. Edit Customer Details +msgctxt "dialog header" +msgid "Edit Customer Details" +msgstr "" + #: build/locale/src/navigation/components/MenuItemDialog/MenuItemDialog.json #. [menuItemDialogEditItem] - edit menu item, header #. defaultMessage is: @@ -3415,6 +3431,14 @@ msgctxt "dialog header" msgid "Edit Price Rate" msgstr "" +#: build/locale/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.json +#. [src.orders.components.OrderAddressEditDialog.3278396777] - dialog header +#. defaultMessage is: +#. Edit Shipping Address +msgctxt "dialog header" +msgid "Edit Shipping Address" +msgstr "" + #: build/locale/src/orders/components/OrderShippingMethodEditDialog/OrderShippingMethodEditDialog.json #. [src.orders.components.OrderShippingMethodEditDialog.3369240294] - dialog header #. defaultMessage is: @@ -3439,30 +3463,6 @@ msgctxt "edit weight based shipping method, dialog header" msgid "Edit Weight Rate" msgstr "" -#: build/locale/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.json -#. [src.orders.components.OrderAddressEditDialog.2935008093] - dialog header -#. defaultMessage is: -#. Edit billing address -msgctxt "dialog header" -msgid "Edit billing address" -msgstr "" - -#: build/locale/src/orders/components/OrderCustomerEditDialog/OrderCustomerEditDialog.json -#. [src.orders.components.OrderCustomerEditDialog.1411666943] - dialog header -#. defaultMessage is: -#. Edit customer details -msgctxt "dialog header" -msgid "Edit customer details" -msgstr "" - -#: build/locale/src/orders/components/OrderAddressEditDialog/OrderAddressEditDialog.json -#. [src.orders.components.OrderAddressEditDialog.462765358] - dialog header -#. defaultMessage is: -#. Edit shipping address -msgctxt "dialog header" -msgid "Edit shipping address" -msgstr "" - #: build/locale/src/components/SeoForm/SeoForm.json #. [src.components.SeoForm.3198271020] - button #. defaultMessage is: @@ -3635,6 +3635,14 @@ msgctxt "button" msgid "Finalize" msgstr "" +#: build/locale/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.json +#. [src.orders.components.OrderDraftFinalizeDialog.1161061962] - dialog header +#. defaultMessage is: +#. Finalize Draft Order +msgctxt "dialog header" +msgid "Finalize Draft Order" +msgstr "" + #: build/locale/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.json #. [src.orders.components.OrderDraftFinalizeDialog.678764806] - button #. defaultMessage is: @@ -3643,14 +3651,6 @@ msgctxt "button" msgid "Finalize anyway" msgstr "" -#: build/locale/src/orders/components/OrderDraftFinalizeDialog/OrderDraftFinalizeDialog.json -#. [src.orders.components.OrderDraftFinalizeDialog.845440998] - dialog header -#. defaultMessage is: -#. Finalize draft order -msgctxt "dialog header" -msgid "Finalize draft order" -msgstr "" - #: build/locale/src/intl.json #. [src.firstName] #. defaultMessage is: @@ -3700,11 +3700,11 @@ msgid "Fulfill" msgstr "" #: build/locale/src/orders/components/OrderFulfillmentDialog/OrderFulfillmentDialog.json -#. [src.orders.components.OrderFulfillmentDialog.3236546219] - dialog header +#. [src.orders.components.OrderFulfillmentDialog.3928354289] - dialog header #. defaultMessage is: -#. Fulfill products +#. Fulfill Oroducts msgctxt "dialog header" -msgid "Fulfill products" +msgid "Fulfill Oroducts" msgstr "" #: build/locale/src/misc.json @@ -5900,11 +5900,11 @@ msgid "Pricing" msgstr "" #: build/locale/src/customers/components/CustomerCreateAddress/CustomerCreateAddress.json -#. [src.customers.components.CustomerCreateAddress.1922654050] - page header +#. [src.customers.components.CustomerCreateAddress.1751533141] - page header #. defaultMessage is: -#. Primary address +#. Primary Address msgctxt "page header" -msgid "Primary address" +msgid "Primary Address" msgstr "" #: build/locale/src/orders/components/OrderDraftDetailsProducts/OrderDraftDetailsProducts.json @@ -6360,11 +6360,11 @@ msgid "Ready to Capture" msgstr "" #: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json -#. [src.customers.components.CustomerOrders.3878642352] - section header +#. [src.customers.components.CustomerOrders.1899831623] - section header #. defaultMessage is: -#. Recent orders +#. Recent Orders msgctxt "section header" -msgid "Recent orders" +msgid "Recent Orders" msgstr "" #: build/locale/src/taxes/components/CountryList/CountryList.json @@ -7163,6 +7163,14 @@ msgctxt "header" msgid "Shipping" msgstr "" +#: build/locale/src/customers/components/CustomerAddresses/CustomerAddresses.json +#. [src.customers.components.CustomerAddresses.2758581442] - subsection header +#. defaultMessage is: +#. Shipping Address +msgctxt "subsection header" +msgid "Shipping Address" +msgstr "" + #: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json #. [src.orders.components.OrderCustomer.2758581442] #. defaultMessage is: @@ -7171,6 +7179,14 @@ msgctxt "description" msgid "Shipping Address" msgstr "" +#: build/locale/src/shipping/components/ShippingZonesList/ShippingZonesList.json +#. [src.shipping.components.ShippingZonesList.120574110] - sort shipping methods by zone, section header +#. defaultMessage is: +#. Shipping By Zone +msgctxt "sort shipping methods by zone, section header" +msgid "Shipping By Zone" +msgstr "" + #: build/locale/src/intl.json #. [src.shipping] - shipping section name #. defaultMessage is: @@ -7195,22 +7211,6 @@ msgctxt "description" msgid "Shipping Zone Name" msgstr "" -#: build/locale/src/customers/components/CustomerAddresses/CustomerAddresses.json -#. [src.customers.components.CustomerAddresses.3517722732] - subsection header -#. defaultMessage is: -#. Shipping address -msgctxt "subsection header" -msgid "Shipping address" -msgstr "" - -#: build/locale/src/shipping/components/ShippingZonesList/ShippingZonesList.json -#. [src.shipping.components.ShippingZonesList.2942726079] - sort shipping methods by zone, section header -#. defaultMessage is: -#. Shipping by zone -msgctxt "sort shipping methods by zone, section header" -msgid "Shipping by zone" -msgstr "" - #: build/locale/src/orders/components/OrderHistory/OrderHistory.json #. [src.orders.components.OrderHistory.651019008] - order history message #. defaultMessage is: @@ -7535,6 +7535,14 @@ msgctxt "description" msgid "Store Description" msgstr "" +#: build/locale/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.json +#. [src.siteSettings.components.SiteSettingsAddress.229184360] - section header +#. defaultMessage is: +#. Store Information +msgctxt "section header" +msgid "Store Information" +msgstr "" + #: build/locale/src/siteSettings/components/SiteSettingsDetails/SiteSettingsDetails.json #. [src.siteSettings.components.SiteSettingsDetails.529433178] #. defaultMessage is: @@ -7543,14 +7551,6 @@ msgctxt "description" msgid "Store description is shown on taskbar after your store name" msgstr "" -#: build/locale/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.json -#. [src.siteSettings.components.SiteSettingsAddress.1150975268] - section header -#. defaultMessage is: -#. Store information -msgctxt "section header" -msgid "Store information" -msgstr "" - #: build/locale/src/attributes/components/AttributeProperties/AttributeProperties.json #. [src.attributes.components.AttributeProperties.1877630205] - attribute properties regarding storefront #. defaultMessage is: @@ -7926,9 +7926,9 @@ msgstr "" #: build/locale/src/home/components/HomeProductListCard/HomeProductListCard.json #. [homeProductsListCardHeader] - header #. defaultMessage is: -#. Top products +#. Top Products msgctxt "header" -msgid "Top products" +msgid "Top Products" msgstr "" #: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json From 3a4cb531dd4333cd4ef516c14ca98b02140d017f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 12:15:19 +0200 Subject: [PATCH 36/48] Add git hooks --- package-lock.json | 69 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 7 +++++ 2 files changed, 76 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6881d9537..98e668ceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10911,6 +10911,48 @@ } } }, + "husky": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.8.tgz", + "integrity": "sha512-HFOsgcyrX3qe/rBuqyTt+P4Gxn5P0seJmr215LAZ/vnwK3jWB3r0ck7swbzGRUbufCf9w/lgHPVbF/YXQALgfQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cosmiconfig": "^5.2.1", + "execa": "^1.0.0", + "get-stdin": "^7.0.0", + "is-ci": "^2.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "read-pkg": "^5.1.1", + "run-node": "^1.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, "hyperlinker": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", @@ -14588,6 +14630,12 @@ "is-wsl": "^1.1.0" } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, "opn": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/opn/-/opn-3.0.3.tgz", @@ -15205,6 +15253,15 @@ } } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "plop": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/plop/-/plop-2.4.0.tgz", @@ -17124,6 +17181,12 @@ "is-promise": "^2.1.0" } }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -17239,6 +17302,12 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", diff --git a/package.json b/package.json index a02ae882e..640dcfaba 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "file-loader": "^1.1.11", "fork-ts-checker-webpack-plugin": "^0.5.2", "html-webpack-plugin": "^3.2.0", + "husky": "^3.0.8", "jest": "^24.8.0", "jest-file": "^1.0.0", "plop": "^2.4.0", @@ -157,6 +158,11 @@ "^lodash-es(.*)$": "lodash/$1" } }, + "husky": { + "hooks": { + "pre-push": "npm run check-types && npm run lint" + } + }, "scripts": { "build": "webpack -p", "extract-json-messages": "rimraf build/locale && babel src 'src/**/*.{ts,tsx}' -o build/dashboard.bundle.js", @@ -164,6 +170,7 @@ "extract-messages": "npm run extract-json-messages && npm run extract-pot-messages", "build-messages": "rip po2json 'locale/**/*.po' -m 'build/locale/**/*.json' -o 'locale' -c 'description'", "build-types": "apollo client:codegen --target=typescript types --globalTypesFile=src/types/globalTypes.ts", + "check-types": "tsc --noEmit", "generate-component": "plop --plopfile .plop/plopfile.js", "lint": "tslint 'src/**/*.{ts,tsx}'", "lint-fix": "tslint 'src/**/*.{ts,tsx}' --fix", From d447d162fc7286eebf1fcb683243c1054047bd13 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 12:18:25 +0200 Subject: [PATCH 37/48] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe07521bb..eda06c478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,3 +33,4 @@ All notable, unreleased changes to this project will be documented in this file. - Hide variants and attributes if product has none - #179 by @dominik-zeglen - Add service account section - #188 by @dominik-zeglen - Add variant creator - #177 by @dominik-zeglen +- Add git hooks - #209 by @dominik-zeglen From 05d9aff656324a192e8f547e9a17093ec15c61dd Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 12:31:01 +0200 Subject: [PATCH 38/48] Lint only staged files --- package-lock.json | 433 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 12 +- 2 files changed, 442 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98e668ceb..e437333ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1742,12 +1742,40 @@ "glob-to-regexp": "^0.3.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + } + } + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, "@oclif/color": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@oclif/color/-/color-0.0.0.tgz", @@ -3416,6 +3444,30 @@ "es6-promisify": "^5.0.0" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, "airbnb-js-shims": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.0.tgz", @@ -8938,6 +8990,15 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "fault": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.3.tgz", @@ -9967,6 +10028,12 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-own-enumerable-property-symbols": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz", + "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -11578,6 +11645,12 @@ "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", "dev": true }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, "is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", @@ -11653,6 +11726,12 @@ "has": "^1.0.1" } }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -12982,6 +13061,319 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "lint-staged": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz", + "integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^2.20.0", + "cosmiconfig": "^5.2.1", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "del": "^5.0.0", + "execa": "^2.0.3", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "string-argv": "^0.3.0", + "stringify-object": "^3.3.0" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "del": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "dev": true, + "requires": { + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "execa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^3.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fast-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "linux-platform-info": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/linux-platform-info/-/linux-platform-info-0.0.3.tgz", @@ -15119,6 +15511,12 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -17137,6 +17535,12 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.0.tgz", @@ -17187,6 +17591,12 @@ "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", "dev": true }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -18109,6 +18519,12 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -18233,6 +18649,17 @@ } } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -18251,6 +18678,12 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, "style-loader": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", diff --git a/package.json b/package.json index 640dcfaba..9ba48a59b 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "husky": "^3.0.8", "jest": "^24.8.0", "jest-file": "^1.0.0", + "lint-staged": "^9.4.2", "plop": "^2.4.0", "react-intl-po": "^2.2.2", "react-test-renderer": "^16.8.6", @@ -160,9 +161,16 @@ }, "husky": { "hooks": { - "pre-push": "npm run check-types && npm run lint" + "pre-commit": "lint-staged", + "pre-push": "npm run check-types" } }, + "lint-staged": { + "*.{ts,tsx}": [ + "tslint --fix", + "git add" + ] + }, "scripts": { "build": "webpack -p", "extract-json-messages": "rimraf build/locale && babel src 'src/**/*.{ts,tsx}' -o build/dashboard.bundle.js", @@ -172,8 +180,6 @@ "build-types": "apollo client:codegen --target=typescript types --globalTypesFile=src/types/globalTypes.ts", "check-types": "tsc --noEmit", "generate-component": "plop --plopfile .plop/plopfile.js", - "lint": "tslint 'src/**/*.{ts,tsx}'", - "lint-fix": "tslint 'src/**/*.{ts,tsx}' --fix", "start": "webpack-dev-server --open -d", "storybook": "start-storybook -p 3000 -c src/storybook/", "build-storybook": "build-storybook -c src/storybook/ -o build/storybook", From 7a998ac80937a57153165e9db4b9248b0bb79f20 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 13:04:51 +0200 Subject: [PATCH 39/48] Update address only one time --- .../components/CustomerAddressDialog/CustomerAddressDialog.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/customers/components/CustomerAddressDialog/CustomerAddressDialog.tsx b/src/customers/components/CustomerAddressDialog/CustomerAddressDialog.tsx index 4d4253657..6ae1cc938 100644 --- a/src/customers/components/CustomerAddressDialog/CustomerAddressDialog.tsx +++ b/src/customers/components/CustomerAddressDialog/CustomerAddressDialog.tsx @@ -88,7 +88,7 @@ const CustomerAddressDialog = withStyles(styles, {})( maxWidth="sm" >
- {({ change, data, errors, submit }) => { + {({ change, data, errors }) => { const handleCountrySelect = createSingleAutocompleteSelectHandler( change, setCountryDisplayName, @@ -128,7 +128,6 @@ const CustomerAddressDialog = withStyles(styles, {})( transitionState={confirmButtonState} color="primary" variant="contained" - onClick={submit} type="submit" > From d4d432d97aed1969f6c3665ba77bf04035470047 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 13:10:43 +0200 Subject: [PATCH 40/48] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda06c478..719ab17b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,3 +34,4 @@ All notable, unreleased changes to this project will be documented in this file. - Add service account section - #188 by @dominik-zeglen - Add variant creator - #177 by @dominik-zeglen - Add git hooks - #209 by @dominik-zeglen +- Send address update mutation only once - #210 by @dominik-zeglen From aec3e3ec738703520ac99c31f20b86f7c5cb7b11 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 14:58:14 +0200 Subject: [PATCH 41/48] Do not send invitation email --- src/customers/views/CustomerCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/customers/views/CustomerCreate.tsx b/src/customers/views/CustomerCreate.tsx index e0a273f51..679864da5 100644 --- a/src/customers/views/CustomerCreate.tsx +++ b/src/customers/views/CustomerCreate.tsx @@ -79,7 +79,7 @@ export const CustomerCreate: React.StatelessComponent<{}> = () => { firstName: formData.customerFirstName, lastName: formData.customerLastName, note: formData.note, - sendPasswordEmail: true + sendPasswordEmail: false } } }); From 086eb737b243a0972e3a6dbb0771faa9ed7edef5 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 15:03:00 +0200 Subject: [PATCH 42/48] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda06c478..66e5d148a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,3 +34,4 @@ All notable, unreleased changes to this project will be documented in this file. - Add service account section - #188 by @dominik-zeglen - Add variant creator - #177 by @dominik-zeglen - Add git hooks - #209 by @dominik-zeglen +- Do not send customer invitation email - #211 by @dominik-zeglen From 99d0053b206474028da384d70f5f6c84f6c294ae Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 10 Oct 2019 14:45:25 +0200 Subject: [PATCH 43/48] Fix RadioGroupField styling --- .../RadioGroupField/RadioGroupField.tsx | 125 ++++++++-------- .../SaleDetailsPage/SaleDetailsPage.tsx | 16 +- .../components/SalePricing/SalePricing.tsx | 138 ------------------ src/discounts/components/SalePricing/index.ts | 2 - .../components/SaleType/SaleType.tsx | 84 +++++++++++ src/discounts/components/SaleType/index.ts | 2 + .../components/SaleValue/SaleValue.tsx | 0 src/discounts/components/SaleValue/index.ts | 2 + .../VoucherRequirements.tsx | 4 +- .../components/VoucherValue/VoucherValue.tsx | 31 ++-- src/theme.ts | 5 +- 11 files changed, 187 insertions(+), 222 deletions(-) delete mode 100644 src/discounts/components/SalePricing/SalePricing.tsx delete mode 100644 src/discounts/components/SalePricing/index.ts create mode 100644 src/discounts/components/SaleType/SaleType.tsx create mode 100644 src/discounts/components/SaleType/index.ts create mode 100644 src/discounts/components/SaleValue/SaleValue.tsx create mode 100644 src/discounts/components/SaleValue/index.ts diff --git a/src/components/RadioGroupField/RadioGroupField.tsx b/src/components/RadioGroupField/RadioGroupField.tsx index 85d3f3ad4..725e982e9 100644 --- a/src/components/RadioGroupField/RadioGroupField.tsx +++ b/src/components/RadioGroupField/RadioGroupField.tsx @@ -5,28 +5,39 @@ import FormLabel from "@material-ui/core/FormLabel"; import MenuItem from "@material-ui/core/MenuItem"; import Radio from "@material-ui/core/Radio"; import RadioGroup from "@material-ui/core/RadioGroup"; -import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles"; +import { Theme } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; import classNames from "classnames"; import React from "react"; import { FormattedMessage } from "react-intl"; -const styles = createStyles({ - formControl: { - padding: 0, - width: "100%" - }, - formLabel: { - marginLeft: "-5px", - paddingBottom: "10px" - }, - radioLabel: { - "& > span": { - padding: "6px" +const useStyles = makeStyles( + (theme: Theme) => ({ + formLabel: { + marginBottom: theme.spacing.unit + }, + radioLabel: { + marginBottom: -theme.spacing.unit * 1.5 + }, + root: { + "& $radioLabel": { + "&:last-of-type": { + marginBottom: 0 + } + }, + padding: 0, + width: "100%" + }, + rootNoLabel: { + marginTop: -theme.spacing.unit * 1.5 } + }), + { + name: "RadioGroupField" } -}); +); -interface RadioGroupFieldChoice { +export interface RadioGroupFieldChoice { value: string; label: React.ReactNode; } @@ -39,16 +50,13 @@ interface RadioGroupFieldProps { hint?: string; label?: string; name?: string; - value?: string; + value: string; onChange: (event: React.ChangeEvent) => void; } -export const RadioGroupField = withStyles(styles, { - name: "RadioGroupField" -})( - ({ +export const RadioGroupField: React.FC = props => { + const { className, - classes, disabled, error, label, @@ -57,42 +65,45 @@ export const RadioGroupField = withStyles(styles, { onChange, name, hint - }: RadioGroupFieldProps & WithStyles) => { - return ( - + {label ? ( + {label} + ) : null} + - {label ? ( - {label} - ) : null} - - {choices.length > 0 ? ( - choices.map(choice => ( - } - label={choice.label} - key={choice.value} - /> - )) - ) : ( - - - - )} - - {hint && {hint}} - - ); - } -); + {choices.length > 0 ? ( + choices.map(choice => ( + } + label={choice.label} + key={choice.value} + /> + )) + ) : ( + + + + )} + + {hint && {hint}} + + ); +}; RadioGroupField.displayName = "RadioGroupField"; export default RadioGroupField; diff --git a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx index 83f361676..870c50362 100644 --- a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx +++ b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx @@ -13,21 +13,21 @@ import { Tab, TabContainer } from "@saleor/components/Tab"; import { sectionNames } from "@saleor/intl"; import { maybe } from "../../../misc"; import { ListProps, TabListActions, UserError } from "../../../types"; -import { SaleType } from "../../../types/globalTypes"; +import { SaleType as SaleTypeEnum } from "../../../types/globalTypes"; import { SaleDetails_sale } from "../../types/SaleDetails"; import DiscountCategories from "../DiscountCategories"; import DiscountCollections from "../DiscountCollections"; import DiscountProducts from "../DiscountProducts"; import SaleInfo from "../SaleInfo"; -import SalePricing from "../SalePricing"; import SaleSummary from "../SaleSummary"; +import SaleType from "../SaleType"; export interface FormData { name: string; startDate: string; endDate: string; value: string; - type: SaleType; + type: SaleTypeEnum; } export enum SaleDetailsPageTab { @@ -109,7 +109,7 @@ const SaleDetailsPage: React.StatelessComponent = ({ endDate: maybe(() => (sale.endDate ? sale.endDate : ""), ""), name: maybe(() => sale.name, ""), startDate: maybe(() => sale.startDate, ""), - type: maybe(() => sale.type, SaleType.FIXED), + type: maybe(() => sale.type, SaleTypeEnum.FIXED), value: maybe(() => sale.value.toString(), "") }; return ( @@ -129,13 +129,7 @@ const SaleDetailsPage: React.StatelessComponent = ({ onChange={change} /> - + ; - onChange: (event: React.ChangeEvent) => void; -} - -const styles = (theme: Theme) => - createStyles({ - root: { - display: "grid", - gridColumnGap: theme.spacing.unit * 2 + "px", - gridTemplateColumns: "1fr 1fr" - }, - subheading: { - gridColumnEnd: "span 2", - marginBottom: theme.spacing.unit * 2 - } - }); - -const SalePricing = withStyles(styles, { - name: "SalePricing" -})( - ({ - classes, - data, - defaultCurrency, - disabled, - errors, - onChange - }: SalePricingProps & WithStyles) => { - const intl = useIntl(); - - return ( - - - - - -
- - - - - - - -
- ); - } -); -SalePricing.displayName = "SalePricing"; -export default SalePricing; diff --git a/src/discounts/components/SalePricing/index.ts b/src/discounts/components/SalePricing/index.ts deleted file mode 100644 index 347c08adc..000000000 --- a/src/discounts/components/SalePricing/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./SalePricing"; -export * from "./SalePricing"; diff --git a/src/discounts/components/SaleType/SaleType.tsx b/src/discounts/components/SaleType/SaleType.tsx new file mode 100644 index 000000000..8a977f8b1 --- /dev/null +++ b/src/discounts/components/SaleType/SaleType.tsx @@ -0,0 +1,84 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import { Theme } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; +import React from "react"; +import { IntlShape, useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; +import RadioGroupField, { + RadioGroupFieldChoice +} from "@saleor/components/RadioGroupField"; +import { FormChange } from "@saleor/hooks/useForm"; +import { SaleType as SaleTypeEnum } from "@saleor/types/globalTypes"; +import { FormData } from "../SaleDetailsPage"; + +export interface SaleTypeProps { + data: FormData; + disabled: boolean; + onChange: FormChange; +} + +const useStyles = makeStyles( + (theme: Theme) => ({ + root: { + "&&": { + paddingBottom: theme.spacing.unit * 1.5 + } + } + }), + { + name: "SaleType" + } +); + +function createChoices(intl: IntlShape): RadioGroupFieldChoice[] { + return [ + { + label: intl.formatMessage({ + defaultMessage: "Percentage", + description: "discount type" + }), + value: SaleTypeEnum.PERCENTAGE + }, + { + label: intl.formatMessage({ + defaultMessage: "Fixed Amount", + description: "discount type" + }), + value: SaleTypeEnum.FIXED + } + ]; +} + +const SaleType: React.FC = props => { + const { data, disabled, onChange } = props; + + const classes = useStyles(props); + const intl = useIntl(); + + const choices = createChoices(intl); + + return ( + + + + + + + ); +}; + +SaleType.displayName = "SaleType"; +export default SaleType; diff --git a/src/discounts/components/SaleType/index.ts b/src/discounts/components/SaleType/index.ts new file mode 100644 index 000000000..ea6da6728 --- /dev/null +++ b/src/discounts/components/SaleType/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SaleType"; +export * from "./SaleType"; diff --git a/src/discounts/components/SaleValue/SaleValue.tsx b/src/discounts/components/SaleValue/SaleValue.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/discounts/components/SaleValue/index.ts b/src/discounts/components/SaleValue/index.ts new file mode 100644 index 000000000..d3aba981f --- /dev/null +++ b/src/discounts/components/SaleValue/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SaleValue"; +export * from "./SaleValue"; diff --git a/src/discounts/components/VoucherRequirements/VoucherRequirements.tsx b/src/discounts/components/VoucherRequirements/VoucherRequirements.tsx index ee9e0b50b..9f3043213 100644 --- a/src/discounts/components/VoucherRequirements/VoucherRequirements.tsx +++ b/src/discounts/components/VoucherRequirements/VoucherRequirements.tsx @@ -70,7 +70,9 @@ const VoucherRequirements = ({ value={data.requirementsPicker} onChange={onChange} /> - + {[RequirementsPicker.ORDER, RequirementsPicker.ITEM].includes( + data.requirementsPicker + ) && } {data.requirementsPicker === RequirementsPicker.ORDER ? ( { +const useStyles = makeStyles( + (theme: Theme) => ({ + hr: { + margin: `${theme.spacing.unit * 2}px 0` + } + }), + { + name: "VoucherValue" + } +); + +const VoucherValue: React.FC = props => { + const { data, defaultCurrency, disabled, errors, variant, onChange } = props; + + const classes = useStyles(props); const intl = useIntl(); const translatedVoucherTypes = translateVoucherTypes(intl); @@ -81,22 +90,22 @@ const VoucherValue = ({ {variant === "update" && ( <> +
- )} -
+
MuiFormControlLabel: { root: { display: "grid", - gridTemplateColumns: "50px 6fr" + gridTemplateColumns: "48px 1fr" } }, MuiFormLabel: { @@ -453,7 +453,8 @@ export default (colors: IThemeColors): Theme => ripple: { "&$rippleVisible": { backgroundColor: fade(colors.primary, 0.2) - } + }, + borderRadius: "100%" } } }, From 636ddca9e3bf644964b87f7a8b138b5907f01a68 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 10 Oct 2019 15:33:50 +0200 Subject: [PATCH 44/48] Fix sale dates --- .../DiscountDates/DiscountDates.tsx | 119 ++++++++++++++++++ .../components/DiscountDates/index.ts | 2 + .../SaleDetailsPage/SaleDetailsPage.tsx | 25 +++- .../VoucherDetailsPage/VoucherDetailsPage.tsx | 4 +- src/discounts/views/SaleDetails.tsx | 20 +-- 5 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 src/discounts/components/DiscountDates/DiscountDates.tsx create mode 100644 src/discounts/components/DiscountDates/index.ts diff --git a/src/discounts/components/DiscountDates/DiscountDates.tsx b/src/discounts/components/DiscountDates/DiscountDates.tsx new file mode 100644 index 000000000..0a8e935f3 --- /dev/null +++ b/src/discounts/components/DiscountDates/DiscountDates.tsx @@ -0,0 +1,119 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import TextField from "@material-ui/core/TextField"; +import React from "react"; +import { useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; +import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; +import Grid from "@saleor/components/Grid"; +import { commonMessages } from "@saleor/intl"; +import { FormErrors } from "../../../types"; + +interface DiscountDatesProps { + data: { + endDate: string; + endTime: string; + hasEndDate: boolean; + startDate: string; + startTime: string; + }; + defaultCurrency: string; + disabled: boolean; + errors: FormErrors<"endDate" | "startDate">; + onChange: (event: React.ChangeEvent) => void; +} + +const DiscountDates = ({ + data, + disabled, + errors, + onChange +}: DiscountDatesProps) => { + const intl = useIntl(); + + return ( + + + + + + + + + {data.hasEndDate && ( + + + + + )} + + + ); +}; +export default DiscountDates; diff --git a/src/discounts/components/DiscountDates/index.ts b/src/discounts/components/DiscountDates/index.ts new file mode 100644 index 000000000..263111c31 --- /dev/null +++ b/src/discounts/components/DiscountDates/index.ts @@ -0,0 +1,2 @@ +export { default } from "./DiscountDates"; +export * from "./DiscountDates"; diff --git a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx index 870c50362..ef52fabb3 100644 --- a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx +++ b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx @@ -11,23 +11,27 @@ import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; import { Tab, TabContainer } from "@saleor/components/Tab"; import { sectionNames } from "@saleor/intl"; -import { maybe } from "../../../misc"; +import { maybe, splitDateTime } from "../../../misc"; import { ListProps, TabListActions, UserError } from "../../../types"; import { SaleType as SaleTypeEnum } from "../../../types/globalTypes"; import { SaleDetails_sale } from "../../types/SaleDetails"; import DiscountCategories from "../DiscountCategories"; import DiscountCollections from "../DiscountCollections"; +import DiscountDates from "../DiscountDates"; import DiscountProducts from "../DiscountProducts"; import SaleInfo from "../SaleInfo"; import SaleSummary from "../SaleSummary"; import SaleType from "../SaleType"; export interface FormData { + endDate: string; + endTime: string; + hasEndDate: boolean; name: string; startDate: string; - endDate: string; - value: string; + startTime: string; type: SaleTypeEnum; + value: string; } export enum SaleDetailsPageTab { @@ -106,9 +110,12 @@ const SaleDetailsPage: React.StatelessComponent = ({ const intl = useIntl(); const initialForm: FormData = { - endDate: maybe(() => (sale.endDate ? sale.endDate : ""), ""), + endDate: splitDateTime(maybe(() => sale.endDate, "")).date, + endTime: splitDateTime(maybe(() => sale.endDate, "")).time, + hasEndDate: maybe(() => !!sale.endDate), name: maybe(() => sale.name, ""), - startDate: maybe(() => sale.startDate, ""), + startDate: splitDateTime(maybe(() => sale.startDate, "")).date, + startTime: splitDateTime(maybe(() => sale.startDate, "")).time, type: maybe(() => sale.type, SaleTypeEnum.FIXED), value: maybe(() => sale.value.toString(), "") }; @@ -237,6 +244,14 @@ const SaleDetailsPage: React.StatelessComponent = ({ toolbar={productListToolbar} /> )} + +
diff --git a/src/discounts/components/VoucherDetailsPage/VoucherDetailsPage.tsx b/src/discounts/components/VoucherDetailsPage/VoucherDetailsPage.tsx index 780ba974e..cf783f291 100644 --- a/src/discounts/components/VoucherDetailsPage/VoucherDetailsPage.tsx +++ b/src/discounts/components/VoucherDetailsPage/VoucherDetailsPage.tsx @@ -23,8 +23,8 @@ import { import { VoucherDetails_voucher } from "../../types/VoucherDetails"; import DiscountCategories from "../DiscountCategories"; import DiscountCollections from "../DiscountCollections"; +import DiscountDates from "../DiscountDates"; import DiscountProducts from "../DiscountProducts"; -import VoucherDates from "../VoucherDates"; import VoucherInfo from "../VoucherInfo"; import VoucherLimits from "../VoucherLimits"; import VoucherRequirements from "../VoucherRequirements"; @@ -349,7 +349,7 @@ const VoucherDetailsPage: React.StatelessComponent = ({ onChange={change} /> - = ({ variables: { id, input: { - endDate: - formData.endDate === "" - ? null - : formData.endDate, + endDate: formData.hasEndDate + ? joinDateTime( + formData.endDate, + formData.endTime + ) + : null, name: formData.name, - startDate: - formData.startDate === "" - ? null - : formData.startDate, + startDate: joinDateTime( + formData.startDate, + formData.startTime + ), type: discountValueTypeEnum( formData.type ), From 7ce4008c475fbf19dd73424dcfbb18522db48664 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 10 Oct 2019 15:46:22 +0200 Subject: [PATCH 45/48] Fix types --- .../SaleCreatePage/SaleCreatePage.tsx | 23 +- .../ProductVariantCreatePrices.tsx | 8 +- .../ProductVariantCreateSummary.tsx | 1 + .../__snapshots__/Stories.test.ts.snap | 2607 ++++++++++------- 4 files changed, 1562 insertions(+), 1077 deletions(-) diff --git a/src/discounts/components/SaleCreatePage/SaleCreatePage.tsx b/src/discounts/components/SaleCreatePage/SaleCreatePage.tsx index 67577afc6..800ced791 100644 --- a/src/discounts/components/SaleCreatePage/SaleCreatePage.tsx +++ b/src/discounts/components/SaleCreatePage/SaleCreatePage.tsx @@ -11,16 +11,20 @@ import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; import { sectionNames } from "@saleor/intl"; import { UserError } from "../../../types"; -import { SaleType } from "../../../types/globalTypes"; +import { SaleType as SaleTypeEnum } from "../../../types/globalTypes"; +import DiscountDates from "../DiscountDates"; import SaleInfo from "../SaleInfo"; -import SalePricing from "../SalePricing"; +import SaleType from "../SaleType"; export interface FormData { + endDate: string; + endTime: string; + hasEndDate: boolean; name: string; startDate: string; - endDate: string; + startTime: string; + type: SaleTypeEnum; value: string; - type: SaleType; } export interface SaleCreatePageProps { @@ -44,9 +48,12 @@ const SaleCreatePage: React.StatelessComponent = ({ const initialForm: FormData = { endDate: "", + endTime: "", + hasEndDate: false, name: "", startDate: "", - type: SaleType.FIXED, + startTime: "", + type: SaleTypeEnum.FIXED, value: "" }; return ( @@ -71,10 +78,12 @@ const SaleCreatePage: React.StatelessComponent = ({ onChange={change} /> - + + diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx index 2880d8ad4..bdfe40469 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx @@ -162,7 +162,7 @@ const ProductVariantCreatePrices: React.FC<
{priceAttributeValues && priceAttributeValues.map(attributeValue => ( - <> +
@@ -198,7 +198,7 @@ const ProductVariantCreatePrices: React.FC< />
- +
))} )} @@ -272,7 +272,7 @@ const ProductVariantCreatePrices: React.FC<
{stockAttributeValues && stockAttributeValues.map(attributeValue => ( - <> +
@@ -301,7 +301,7 @@ const ProductVariantCreatePrices: React.FC< />
- +
))} )} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index b251e7723..ff87cda05 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -200,6 +200,7 @@ const ProductVariantCreateSummary: React.FC< style={{ color: colors[valueIndex % colors.length] }} + key={value} > {value} diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 3763dfa36..f06fe7349 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -37820,7 +37820,7 @@ exports[`Storyshots Views / Discounts / Sale create default 1`] = ` - Pricing + Discount Type
-
- - -
- USD + + + + - -
+ Percentage + + +
+
+
+
+
+ + Active Dates + +
+
+

-
- Time Frame -
-
- + +
+
+
+ +
+ + +
-
- -
- -
-
+ + + Set end date + +
@@ -38092,7 +38153,7 @@ exports[`Storyshots Views / Discounts / Sale create form errors 1`] = ` - Pricing + Discount Type
-
- - -
- USD + + + + - -
+ Percentage + + +
-

- Generic form error -

+
+
+
+
+ + Active Dates + +
+
+

-
- Time Frame -
-
- + +
+

+ Generic form error +

-

- Generic form error -

+ +
+ + +
+

+ Generic form error +

+
-
- -
- -
-

+ - Generic form error -

-
+ Set end date + +
@@ -38375,7 +38492,7 @@ exports[`Storyshots Views / Discounts / Sale create loading 1`] = ` - Pricing + Discount Type
-
- - -
- USD + + + + - -
+ Percentage + + +
+
+
+
+
+ + Active Dates + +
+
+

-
- Time Frame -
-
- + +
+
+
+ +
+ + +
-
- -
- -
-
+ + + Set end date + +
@@ -38645,7 +38826,7 @@ exports[`Storyshots Views / Discounts / Sale details collections 1`] = ` - Pricing + Discount Type
-
- - -
- % + + + + - -
-
-
-
-
-
-
- Time Frame -
-
- -
- - -
-
-
- -
- - + Fixed Amount + +
@@ -39046,6 +39164,130 @@ exports[`Storyshots Views / Discounts / Sale details collections 1`] = `
+
+
+
+ + Active Dates + +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
- Pricing + Discount Type
-
- - -
- % + + + + - -
-
-
-
-
-
-
- Time Frame -
-
- -
- - -
-
-
- -
- - + Fixed Amount + +
@@ -39638,6 +39817,130 @@ exports[`Storyshots Views / Discounts / Sale details default 1`] = `
+
+
+
+ + Active Dates + +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
- Pricing + Discount Type
-
- - -
- % + + + + - -
-
-

- Generic form error -

-
-
-
-
-
- Time Frame -
-
- -
- - -
-

- Generic form error -

-
-
- -
- - + Fixed Amount + +
-

- Generic form error -

@@ -40250,6 +40475,140 @@ exports[`Storyshots Views / Discounts / Sale details form errors 1`] = `
+
+
+
+ + Active Dates + +
+
+
+
+
+
+
+ +
+ + +
+

+ Generic form error +

+
+
+ +
+ + +
+

+ Generic form error +

+
+
+ +
+
- Pricing + Discount Type
-
- - -
- USD + + + + - -
-
-
-
-
-
-
- Time Frame -
-
- -
- - -
-
-
- -
- - + Fixed Amount + +
@@ -40865,6 +41162,132 @@ exports[`Storyshots Views / Discounts / Sale details loading 1`] = `
+
+
+
+ + Active Dates + +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
- Pricing + Discount Type
-
- - -
- % + + + + - -
-
-
-
-
-
-
- Time Frame -
-
- -
- - -
-
-
- -
- - + Fixed Amount + +
@@ -41757,6 +42117,130 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = `
+
+
+
+ + Active Dates + +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+

-

-
+
-

+
-

+
-

-
Date: Thu, 10 Oct 2019 15:48:06 +0200 Subject: [PATCH 46/48] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3958a7019..bf77d62bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,3 +36,4 @@ All notable, unreleased changes to this project will be documented in this file. - Add git hooks - #209 by @dominik-zeglen - Do not send customer invitation email - #211 by @dominik-zeglen - Send address update mutation only once - #210 by @dominik-zeglen +- Update sale details design - #207 by @dominik-zeglen From 0dc950947b34dd9cbc17d4939720dfe63b6f32cb Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 10 Oct 2019 15:48:13 +0200 Subject: [PATCH 47/48] Update messages --- locale/messages.pot | 68 ++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index 4f2858b52..c28f2104a 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-10-09T15:30:47.333Z\n" +"POT-Creation-Date: 2019-10-15T15:53:11.572Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -99,6 +99,14 @@ msgctxt "staff member status" msgid "Active" msgstr "" +#: build/locale/src/discounts/components/DiscountDates/DiscountDates.json +#. [src.discounts.components.DiscountDates.1662220323] - time during discount is active, header +#. defaultMessage is: +#. Active Dates +msgctxt "time during discount is active, header" +msgid "Active Dates" +msgstr "" + #: build/locale/src/discounts/components/VoucherDates/VoucherDates.json #. [src.discounts.components.VoucherDates.1662220323] - time during voucher is active, header #. defaultMessage is: @@ -3235,12 +3243,12 @@ msgctxt "description" msgid "Discount Code" msgstr "" -#: build/locale/src/discounts/components/VoucherValue/VoucherValue.json -#. [src.discounts.components.VoucherValue.1971417066] +#: build/locale/src/discounts/components/SaleType/SaleType.json +#. [src.discounts.components.SaleType.3216816841] - percentage or fixed, header #. defaultMessage is: -#. Discount Specific Information -msgctxt "description" -msgid "Discount Specific Information" +#. Discount Type +msgctxt "percentage or fixed, header" +msgid "Discount Type" msgstr "" #: build/locale/src/discounts/components/VoucherTypes/VoucherTypes.json @@ -3251,10 +3259,6 @@ msgctxt "header" msgid "Discount Type" msgstr "" -#: build/locale/src/discounts/components/SalePricing/SalePricing.json -#. [src.discounts.components.SalePricing.1205967018] -#. defaultMessage is: -#. Discount Value #: build/locale/src/discounts/components/VoucherValue/VoucherValue.json #. [src.discounts.components.VoucherValue.1205967018] #. defaultMessage is: @@ -3659,6 +3663,14 @@ msgctxt "description" msgid "First Name" msgstr "" +#: build/locale/src/discounts/components/SaleType/SaleType.json +#. [src.discounts.components.SaleType.46415128] - discount type +#. defaultMessage is: +#. Fixed Amount +msgctxt "discount type" +msgid "Fixed Amount" +msgstr "" + #: build/locale/src/discounts/components/VoucherTypes/VoucherTypes.json #. [src.discounts.components.VoucherTypes.46415128] - voucher discount type #. defaultMessage is: @@ -5607,6 +5619,14 @@ msgctxt "order history message" msgid "Payment was voided" msgstr "" +#: build/locale/src/discounts/components/SaleType/SaleType.json +#. [src.discounts.components.SaleType.3688224049] - discount type +#. defaultMessage is: +#. Percentage +msgctxt "discount type" +msgid "Percentage" +msgstr "" + #: build/locale/src/discounts/components/VoucherTypes/VoucherTypes.json #. [src.discounts.components.VoucherTypes.3688224049] - voucher discount type #. defaultMessage is: @@ -5875,14 +5895,6 @@ msgctxt "variant creation step" msgid "Prices and SKU" msgstr "" -#: build/locale/src/discounts/components/SalePricing/SalePricing.json -#. [src.discounts.components.SalePricing.1099355007] - sale pricing, header -#. defaultMessage is: -#. Pricing -msgctxt "sale pricing, header" -msgid "Pricing" -msgstr "" - #: build/locale/src/products/components/ProductPricing/ProductPricing.json #. [src.products.components.ProductPricing.1099355007] - product pricing #. defaultMessage is: @@ -7091,6 +7103,10 @@ msgctxt "button" msgid "Set as default shipping address" msgstr "" +#: build/locale/src/discounts/components/DiscountDates/DiscountDates.json +#. [src.discounts.components.DiscountDates.1596226028] - voucher end date, switch button +#. defaultMessage is: +#. Set end date #: build/locale/src/discounts/components/VoucherDates/VoucherDates.json #. [src.discounts.components.VoucherDates.1596226028] - voucher end date, switch button #. defaultMessage is: @@ -7879,14 +7895,6 @@ msgctxt "description" msgid "This will be shown to customers at checkout" msgstr "" -#: build/locale/src/discounts/components/SalePricing/SalePricing.json -#. [src.discounts.components.SalePricing.2503204759] - time during which sale is active -#. defaultMessage is: -#. Time Frame -msgctxt "time during which sale is active" -msgid "Time Frame" -msgstr "" - #: build/locale/src/pages/components/PageInfo/PageInfo.json #. [src.pages.components.PageInfo.1124600214] - page title #. defaultMessage is: @@ -8787,6 +8795,14 @@ msgctxt "description" msgid "Voucher Name" msgstr "" +#: build/locale/src/discounts/components/VoucherValue/VoucherValue.json +#. [src.discounts.components.VoucherValue.1960678372] +#. defaultMessage is: +#. Voucher Specific Information +msgctxt "description" +msgid "Voucher Specific Information" +msgstr "" + #: build/locale/src/discounts/components/VoucherDetailsPage/VoucherDetailsPage.json #. [src.discounts.components.VoucherDetailsPage.2071139683] #. defaultMessage is: From e6dbd3a89aa0ad955c38bfd51289f641eed3d9c3 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 11 Oct 2019 11:33:56 +0200 Subject: [PATCH 48/48] Fix sale value --- locale/messages.pot | 18 +- .../SaleDetailsPage/SaleDetailsPage.tsx | 9 + .../components/SaleValue/SaleValue.tsx | 61 ++++ .../components/VoucherValue/VoucherValue.tsx | 4 +- src/discounts/views/SaleDetails.tsx | 2 +- .../__snapshots__/Stories.test.ts.snap | 331 ++++++++++++++++++ 6 files changed, 421 insertions(+), 4 deletions(-) diff --git a/locale/messages.pot b/locale/messages.pot index c28f2104a..a5f2fceae 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-10-15T15:53:11.572Z\n" +"POT-Creation-Date: 2019-10-15T15:56:00.137Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -3259,6 +3259,14 @@ msgctxt "header" msgid "Discount Type" msgstr "" +#: build/locale/src/discounts/components/SaleValue/SaleValue.json +#. [src.discounts.components.SaleValue.1205967018] - sale discount +#. defaultMessage is: +#. Discount Value +msgctxt "sale discount" +msgid "Discount Value" +msgstr "" + #: build/locale/src/discounts/components/VoucherValue/VoucherValue.json #. [src.discounts.components.VoucherValue.1205967018] #. defaultMessage is: @@ -8563,6 +8571,14 @@ msgctxt "sale value" msgid "Value" msgstr "" +#: build/locale/src/discounts/components/SaleValue/SaleValue.json +#. [src.discounts.components.SaleValue.1148029984] - sale value, header +#. defaultMessage is: +#. Value +msgctxt "sale value, header" +msgid "Value" +msgstr "" + #: build/locale/src/discounts/components/VoucherList/VoucherList.json #. [src.discounts.components.VoucherList.1148029984] - voucher value #. defaultMessage is: diff --git a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx index ef52fabb3..830e96e57 100644 --- a/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx +++ b/src/discounts/components/SaleDetailsPage/SaleDetailsPage.tsx @@ -22,6 +22,7 @@ import DiscountProducts from "../DiscountProducts"; import SaleInfo from "../SaleInfo"; import SaleSummary from "../SaleSummary"; import SaleType from "../SaleType"; +import SaleValue from "../SaleValue"; export interface FormData { endDate: string; @@ -138,6 +139,14 @@ const SaleDetailsPage: React.StatelessComponent = ({ + + ; + onChange: FormChange; +} + +const SaleValue: React.FC = ({ + currencySymbol, + data, + disabled, + errors, + onChange +}) => { + const intl = useIntl(); + + return ( + + + + + + + ); +}; + +SaleValue.displayName = "SaleValue"; +export default SaleValue; diff --git a/src/discounts/components/VoucherValue/VoucherValue.tsx b/src/discounts/components/VoucherValue/VoucherValue.tsx index c0b8e59c8..da47235df 100644 --- a/src/discounts/components/VoucherValue/VoucherValue.tsx +++ b/src/discounts/components/VoucherValue/VoucherValue.tsx @@ -1,10 +1,10 @@ import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; +import { Theme } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/styles"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { Theme } from "@material-ui/core/styles"; -import { makeStyles } from "@material-ui/styles"; import CardTitle from "@saleor/components/CardTitle"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; diff --git a/src/discounts/views/SaleDetails.tsx b/src/discounts/views/SaleDetails.tsx index de060015b..83f17b7a6 100644 --- a/src/discounts/views/SaleDetails.tsx +++ b/src/discounts/views/SaleDetails.tsx @@ -22,7 +22,7 @@ import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config"; import SearchCategories from "../../containers/SearchCategories"; import SearchCollections from "../../containers/SearchCollections"; import SearchProducts from "../../containers/SearchProducts"; -import { decimal, getMutationState, maybe, joinDateTime } from "../../misc"; +import { decimal, getMutationState, joinDateTime, maybe } from "../../misc"; import { productUrl } from "../../products/urls"; import { DiscountValueTypeEnum, SaleType } from "../../types/globalTypes"; import SaleDetailsPage, { diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index f06fe7349..afe0e6323 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -38925,6 +38925,71 @@ exports[`Storyshots Views / Discounts / Sale details collections 1`] = `
+
+
+ + Value + +
+
+
+
+
+
+ +
+ + + % +
+
+
+
+
@@ -39578,6 +39643,71 @@ exports[`Storyshots Views / Discounts / Sale details default 1`] = `
+
+
+ + Value + +
+
+
+
+
+
+ +
+ + + % +
+
+
+
+
@@ -40236,6 +40366,76 @@ exports[`Storyshots Views / Discounts / Sale details form errors 1`] = `
+
+
+ + Value + +
+
+
+
+
+
+ +
+ + + % +
+

+ Generic form error +

+
+
+
+
@@ -40909,6 +41109,72 @@ exports[`Storyshots Views / Discounts / Sale details loading 1`] = `
+
+
+ + Value + +
+
+
+
+
+
+ +
+ + + USD +
+
+
+
+
@@ -41594,6 +41860,71 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = `
+
+
+ + Value + +
+
+
+
+
+
+ +
+ + + % +
+
+
+
+