Make variant creator view instead of dialog

This commit is contained in:
dominik-zeglen 2020-04-01 18:28:47 +02:00
parent 50ff050155
commit e41a79fe78
41 changed files with 1352 additions and 1104 deletions

View file

@ -1,147 +0,0 @@
import { makeStyles } from "@material-ui/core/styles";
import React from "react";
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
import { isSelected } from "@saleor/utils/lists";
import { ProductVariantCreateFormData } from "./form";
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(
theme => ({
root: {
maxHeight: 400,
overflowX: "hidden",
overflowY: "scroll",
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(2),
position: "relative",
right: theme.spacing(3),
width: `calc(100% + ${theme.spacing(3)}px)`
}
}),
{ name: "ProductVariantCreateContent" }
);
export interface ProductVariantCreateContentProps {
attributes: ProductDetails_product_productType_variantAttributes[];
currencySymbol: string;
data: ProductVariantCreateFormData;
dispatchFormDataAction: React.Dispatch<ProductVariantCreateReducerAction>;
errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[];
step: ProductVariantCreateStep;
onStepClick: (step: ProductVariantCreateStep) => void;
}
const ProductVariantCreateContent: React.FC<ProductVariantCreateContentProps> = props => {
const {
attributes,
currencySymbol,
data,
dispatchFormDataAction,
errors,
step,
onStepClick
} = props;
const classes = useStyles(props);
const selectedAttributes = attributes.filter(attribute =>
isSelected(
attribute.id,
data.attributes.map(dataAttribute => dataAttribute.id),
(a, b) => a === b
)
);
return (
<div>
<ProductVariantCreateTabs step={step} onStepClick={onStepClick} />
<div className={classes.root}>
{step === ProductVariantCreateStep.values && (
<ProductVariantCreateValues
attributes={selectedAttributes}
data={data}
onValueClick={(attributeId, valueId) =>
dispatchFormDataAction({
attributeId,
type: "selectValue",
valueId
})
}
/>
)}
{step === ProductVariantCreateStep.prices && (
<ProductVariantCreatePrices
attributes={selectedAttributes}
currencySymbol={currencySymbol}
data={data}
onApplyPriceOrStockChange={(all, type) =>
dispatchFormDataAction({
all,
type: type === "price" ? "applyPriceToAll" : "applyStockToAll"
})
}
onApplyToAllChange={(value, type) =>
dispatchFormDataAction({
type:
type === "price"
? "changeApplyPriceToAllValue"
: "changeApplyStockToAllValue",
value
})
}
onAttributeSelect={(attributeId, type) =>
dispatchFormDataAction({
attributeId,
type:
type === "price"
? "changeApplyPriceToAttributeId"
: "changeApplyStockToAttributeId"
})
}
onAttributeValueChange={(valueId, value, type) =>
dispatchFormDataAction({
type:
type === "price"
? "changeAttributeValuePrice"
: "changeAttributeValueStock",
value,
valueId
})
}
/>
)}
{step === ProductVariantCreateStep.summary && (
<ProductVariantCreateSummary
attributes={selectedAttributes}
currencySymbol={currencySymbol}
data={data}
errors={errors}
onVariantDataChange={(variantIndex, field, value) =>
dispatchFormDataAction({
field,
type: "changeVariantData",
value,
variantIndex
})
}
onVariantDelete={variantIndex =>
dispatchFormDataAction({
type: "deleteVariant",
variantIndex
})
}
/>
)}
</div>
</div>
);
};
ProductVariantCreateContent.displayName = "ProductVariantCreateContent";
export default ProductVariantCreateContent;

View file

@ -1,315 +0,0 @@
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
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 { ProductVariantCreateFormData } from "./form";
const useStyles = makeStyles(
theme => ({
hr: {
marginBottom: theme.spacing(),
marginTop: theme.spacing(0.5)
},
hrAttribute: {
marginTop: theme.spacing(2)
},
label: {
alignSelf: "center"
},
shortInput: {
width: "50%"
}
}),
{ name: "ProductVariantCreatePrices" }
);
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;
onAttributeSelect: (id: string, type: PriceOrStock) => void;
onAttributeValueChange: (
id: string,
value: string,
type: PriceOrStock
) => void;
}
const ProductVariantCreatePrices: React.FC<
ProductVariantCreatePricesProps
> = props => {
const {
attributes,
currencySymbol,
data,
onApplyPriceOrStockChange,
onApplyToAllChange,
onAttributeSelect,
onAttributeValueChange
} = props;
const classes = useStyles(props);
const intl = useIntl();
const attributeChoices = attributes.map(attribute => ({
label: attribute.name,
value: attribute.id
}));
const priceAttributeValues = data.price.all
? null
: data.price.attribute
? 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.filter(value =>
data.attributes
.find(attribute => attribute.id === data.stock.attribute)
.values.includes(value.slug)
)
: [];
return (
<>
<Typography color="textSecondary" variant="h5">
<FormattedMessage
defaultMessage="Price"
description="variant price, header"
/>
</Typography>
<Hr className={classes.hr} />
<RadioGroup value={data.price.all ? "applyToAll" : "applyToAttribute"}>
<FormControlLabel
value="applyToAll"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply single price to all SKUs"
})}
onChange={() => onApplyPriceOrStockChange(true, "price")}
/>
<FormSpacer />
<TextField
className={classes.shortInput}
inputProps={{
min: 0,
type: "number"
}}
InputProps={{
endAdornment: currencySymbol
}}
label={intl.formatMessage({
defaultMessage: "Price",
id: "productVariantCreatePricesPriceInputLabel"
})}
value={data.price.value}
onChange={event => onApplyToAllChange(event.target.value, "price")}
/>
<FormSpacer />
<FormControlLabel
value="applyToAttribute"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply unique prices by attribute to each SKU"
})}
onChange={() => onApplyPriceOrStockChange(false, "price")}
/>
</RadioGroup>
{!data.price.all && (
<>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>
<FormattedMessage
defaultMessage="Choose attribute"
description="variant attribute"
/>
</Typography>
</div>
<div>
<SingleSelectField
choices={attributeChoices}
label={intl.formatMessage({
defaultMessage: "Attribute",
description: "variant attribute"
})}
value={data.price.attribute}
onChange={event =>
onAttributeSelect(event.target.value, "price")
}
/>
</div>
</Grid>
<Hr className={classes.hrAttribute} />
{priceAttributeValues &&
priceAttributeValues.map(attributeValue => (
<React.Fragment key={attributeValue.id}>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
label={intl.formatMessage({
defaultMessage: "Price",
description: "variant price",
id: "productVariantCreatePricesSetPricePlaceholder"
})}
inputProps={{
min: 0,
type: "number"
}}
InputProps={{
endAdornment: currencySymbol
}}
fullWidth
value={
data.price.values.find(
value => value.slug === attributeValue.slug
).value
}
onChange={event =>
onAttributeValueChange(
attributeValue.slug,
event.target.value,
"price"
)
}
/>
</div>
</Grid>
</React.Fragment>
))}
</>
)}
<FormSpacer />
<Typography color="textSecondary" variant="h5">
<FormattedMessage
defaultMessage="Stock"
description="variant stock, header"
/>
</Typography>
<Hr className={classes.hr} />
<RadioGroup value={data.stock.all ? "applyToAll" : "applyToAttribute"}>
<FormControlLabel
value="applyToAll"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply single stock to all SKUs"
})}
onChange={() => onApplyPriceOrStockChange(true, "stock")}
/>
<FormSpacer />
<TextField
className={classes.shortInput}
inputProps={{
min: 0,
type: "number"
}}
label={intl.formatMessage({
defaultMessage: "Stock",
id: "productVariantCreatePricesStockInputLabel"
})}
value={data.stock.value}
onChange={event => onApplyToAllChange(event.target.value, "stock")}
/>
<FormSpacer />
<FormControlLabel
value="applyToAttribute"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply unique stock by attribute to each SKU"
})}
onChange={() => onApplyPriceOrStockChange(false, "stock")}
/>
</RadioGroup>
{!data.stock.all && (
<>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>
<FormattedMessage
defaultMessage="Choose attribute"
description="variant attribute"
/>
</Typography>
</div>
<div>
<SingleSelectField
choices={attributeChoices}
label={intl.formatMessage({
defaultMessage: "Attribute",
description: "variant attribute"
})}
value={data.stock.attribute}
onChange={event =>
onAttributeSelect(event.target.value, "stock")
}
/>
</div>
</Grid>
<Hr className={classes.hrAttribute} />
{stockAttributeValues &&
stockAttributeValues.map(attributeValue => (
<React.Fragment key={attributeValue.id}>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
label={intl.formatMessage({
defaultMessage: "Stock",
description: "variant stock",
id: "productVariantCreatePricesSetStockPlaceholder"
})}
fullWidth
value={
data.stock.values.find(
value => value.slug === attributeValue.slug
).value
}
onChange={event =>
onAttributeValueChange(
attributeValue.slug,
event.target.value,
"stock"
)
}
/>
</div>
</Grid>
</React.Fragment>
))}
</>
)}
</>
);
};
ProductVariantCreatePrices.displayName = "ProductVariantCreatePrices";
export default ProductVariantCreatePrices;

View file

@ -1,81 +0,0 @@
import makeStyles from "@material-ui/core/styles/makeStyles";
import Typography from "@material-ui/core/Typography";
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";
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: (attributeId: string, valueId: string) => void;
}
const useStyles = makeStyles(
theme => ({
hr: {
marginBottom: theme.spacing(),
marginTop: theme.spacing(0.5)
},
valueContainer: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "repeat(3, 1fr)",
marginBottom: theme.spacing(3)
}
}),
{ name: "ProductVariantCreateValues" }
);
const ProductVariantCreateValues: React.FC<
ProductVariantCreateValuesProps
> = props => {
const { attributes, data, onValueClick } = props;
const classes = useStyles(props);
return (
<>
{attributes.map(attribute => (
<React.Fragment key={attribute.id}>
<Typography color="textSecondary" variant="h5">
{maybe<React.ReactNode>(() => attribute.name, <Skeleton />)}
</Typography>
<Hr className={classes.hr} />
<div className={classes.valueContainer}>
{attribute.values.map(value => (
<Debounce
debounceFn={() => onValueClick(attribute.id, value.slug)}
time={100}
key={value.slug}
>
{change => (
<ControlledCheckbox
checked={isSelected(
value.slug,
data.attributes.find(
dataAttribute => attribute.id === dataAttribute.id
).values,
(a, b) => a === b
)}
name={`value:${value.slug}`}
label={value.name}
onChange={change}
/>
)}
</Debounce>
))}
</div>
</React.Fragment>
))}
</>
);
};
ProductVariantCreateValues.displayName = "ProductVariantCreateValues";
export default ProductVariantCreateValues;

View file

@ -1,5 +0,0 @@
export enum ProductVariantCreateStep {
values,
prices,
summary
}

View file

@ -1,18 +1,18 @@
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import { attributes } from "@saleor/attributes/fixtures"; import { attributes } from "@saleor/attributes/fixtures";
import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
import { ProductErrorCode } from "@saleor/types/globalTypes"; import { ProductErrorCode } from "@saleor/types/globalTypes";
import Container from "@saleor/components/Container";
import Decorator from "../../../storybook/Decorator"; import Decorator from "../../../storybook/Decorator";
import { createVariants } from "./createVariants"; import { createVariants } from "./createVariants";
import { AllOrAttribute } from "./form"; import { AllOrAttribute } from "./form";
import ProductVariantCreateContent, { import ProductVariantCreatorContent, {
ProductVariantCreateContentProps ProductVariantCreatorContentProps
} from "./ProductVariantCreateContent"; } from "./ProductVariantCreatorContent";
import ProductVariantCreateDialog from "./ProductVariantCreateDialog"; import ProductVariantCreatorPage from "./ProductVariantCreatorPage";
import { ProductVariantCreatorStep } from "./types";
const selectedAttributes = [1, 4, 5].map(index => attributes[index]); const selectedAttributes = [1, 4, 5].map(index => attributes[index]);
@ -52,7 +52,7 @@ const errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[] = [
} }
]; ];
const props: ProductVariantCreateContentProps = { const props: ProductVariantCreatorContentProps = {
attributes, attributes,
currencySymbol: "USD", currencySymbol: "USD",
data: { data: {
@ -68,56 +68,43 @@ const props: ProductVariantCreateContentProps = {
}, },
dispatchFormDataAction: () => undefined, dispatchFormDataAction: () => undefined,
errors: [], errors: [],
onStepClick: () => undefined, step: ProductVariantCreatorStep.values
step: "values"
}; };
storiesOf("Views / Products / Create multiple variants", module) storiesOf("Views / Products / Create multiple variants", module)
.addDecorator(storyFn => ( .addDecorator(storyFn => <Container>{storyFn()}</Container>)
<Card
style={{
margin: "auto",
overflow: "visible",
width: 800
}}
>
<CardContent>{storyFn()}</CardContent>
</Card>
))
.addDecorator(Decorator) .addDecorator(Decorator)
.add("choose values", () => <ProductVariantCreateContent {...props} />) .add("choose values", () => <ProductVariantCreatorContent {...props} />)
.add("prices and SKU", () => ( .add("prices and SKU", () => (
<ProductVariantCreateContent {...props} step="prices" /> <ProductVariantCreatorContent
{...props}
step={ProductVariantCreatorStep.prices}
/>
)); ));
storiesOf("Views / Products / Create multiple variants / summary", module) storiesOf("Views / Products / Create multiple variants / summary", module)
.addDecorator(storyFn => ( .addDecorator(storyFn => <Container>{storyFn()}</Container>)
<Card
style={{
margin: "auto",
overflow: "visible",
width: 800
}}
>
<CardContent>{storyFn()}</CardContent>
</Card>
))
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => ( .add("default", () => (
<ProductVariantCreateContent {...props} step="summary" /> <ProductVariantCreatorContent
{...props}
step={ProductVariantCreatorStep.summary}
/>
)) ))
.add("errors", () => ( .add("errors", () => (
<ProductVariantCreateContent {...props} step="summary" errors={errors} /> <ProductVariantCreatorContent
{...props}
step={ProductVariantCreatorStep.summary}
errors={errors}
/>
)); ));
storiesOf("Views / Products / Create multiple variants", module) storiesOf("Views / Products / Create multiple variants", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("interactive", () => ( .add("interactive", () => (
<ProductVariantCreateDialog <ProductVariantCreatorPage
{...props} {...props}
defaultPrice="10.99" defaultPrice="10.99"
open={true}
onClose={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
/> />
)); ));

View file

@ -0,0 +1,122 @@
import React from "react";
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
import { isSelected } from "@saleor/utils/lists";
import { ProductVariantCreateFormData } from "./form";
import ProductVariantCreatePrices from "./ProductVariantCreatorPrices";
import ProductVariantCreateSummary from "./ProductVariantCreatorSummary";
import ProductVariantCreateValues from "./ProductVariantCreatorValues";
import { ProductVariantCreateReducerAction } from "./reducer";
import { ProductVariantCreatorStep } from "./types";
export interface ProductVariantCreatorContentProps {
attributes: ProductDetails_product_productType_variantAttributes[];
currencySymbol: string;
data: ProductVariantCreateFormData;
dispatchFormDataAction: React.Dispatch<ProductVariantCreateReducerAction>;
errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[];
step: ProductVariantCreatorStep;
}
const ProductVariantCreatorContent: React.FC<ProductVariantCreatorContentProps> = props => {
const {
attributes,
currencySymbol,
data,
dispatchFormDataAction,
errors,
step
} = props;
const selectedAttributes = attributes.filter(attribute =>
isSelected(
attribute.id,
data.attributes.map(dataAttribute => dataAttribute.id),
(a, b) => a === b
)
);
return (
<>
{step === ProductVariantCreatorStep.values && (
<ProductVariantCreateValues
attributes={selectedAttributes}
data={data}
onValueClick={(attributeId, valueId) =>
dispatchFormDataAction({
attributeId,
type: "selectValue",
valueId
})
}
/>
)}
{step === ProductVariantCreatorStep.prices && (
<ProductVariantCreatePrices
attributes={selectedAttributes}
currencySymbol={currencySymbol}
data={data}
onApplyPriceOrStockChange={(all, type) =>
dispatchFormDataAction({
all,
type: type === "price" ? "applyPriceToAll" : "applyStockToAll"
})
}
onApplyToAllChange={(value, type) =>
dispatchFormDataAction({
type:
type === "price"
? "changeApplyPriceToAllValue"
: "changeApplyStockToAllValue",
value
})
}
onAttributeSelect={(attributeId, type) =>
dispatchFormDataAction({
attributeId,
type:
type === "price"
? "changeApplyPriceToAttributeId"
: "changeApplyStockToAttributeId"
})
}
onAttributeValueChange={(valueId, value, type) =>
dispatchFormDataAction({
type:
type === "price"
? "changeAttributeValuePrice"
: "changeAttributeValueStock",
value,
valueId
})
}
/>
)}
{step === ProductVariantCreatorStep.summary && (
<ProductVariantCreateSummary
attributes={selectedAttributes}
currencySymbol={currencySymbol}
data={data}
errors={errors}
onVariantDataChange={(variantIndex, field, value) =>
dispatchFormDataAction({
field,
type: "changeVariantData",
value,
variantIndex
})
}
onVariantDelete={variantIndex =>
dispatchFormDataAction({
type: "deleteVariant",
variantIndex
})
}
/>
)}
</>
);
};
ProductVariantCreatorContent.displayName = "ProductVariantCreatorContent";
export default ProductVariantCreatorContent;

View file

@ -1,22 +1,19 @@
import Button from "@material-ui/core/Button"; 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 { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage, useIntl, IntlShape } from "react-intl";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import useWizard from "@saleor/hooks/useWizard"; import useWizard from "@saleor/hooks/useWizard";
import PageHeader from "@saleor/components/PageHeader";
import Container from "@saleor/components/Container";
import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; import { ProductVariantBulkCreateInput } from "../../../types/globalTypes";
import { createInitialForm, ProductVariantCreateFormData } from "./form"; import { createInitialForm, ProductVariantCreateFormData } from "./form";
import ProductVariantCreateContent, { import ProductVariantCreatorContent, {
ProductVariantCreateContentProps ProductVariantCreatorContentProps
} from "./ProductVariantCreateContent"; } from "./ProductVariantCreatorContent";
import reduceProductVariantCreateFormData from "./reducer"; import reduceProductVariantCreateFormData from "./reducer";
import { ProductVariantCreateStep } from "./types"; import { ProductVariantCreatorStep } from "./types";
import ProductVariantCreateTabs from "./ProductVariantCreatorTabs";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -27,22 +24,19 @@ const useStyles = makeStyles(
overflowX: "visible", overflowX: "visible",
overflowY: "hidden", overflowY: "hidden",
width: 800 width: 800
},
spacer: {
flex: 1
} }
}), }),
{ name: "ProductVariantCreateDialog" } { name: "ProductVariantCreatePage" }
); );
function canHitNext( function canHitNext(
step: ProductVariantCreateStep, step: ProductVariantCreatorStep,
data: ProductVariantCreateFormData data: ProductVariantCreateFormData
): boolean { ): boolean {
switch (step) { switch (step) {
case ProductVariantCreateStep.values: case ProductVariantCreatorStep.values:
return data.attributes.every(attribute => attribute.values.length > 0); return data.attributes.every(attribute => attribute.values.length > 0);
case ProductVariantCreateStep.prices: case ProductVariantCreatorStep.prices:
if (data.price.all) { if (data.price.all) {
if (data.price.value === "") { if (data.price.value === "") {
return false; return false;
@ -70,7 +64,7 @@ function canHitNext(
} }
return true; return true;
case ProductVariantCreateStep.summary: case ProductVariantCreatorStep.summary:
return data.variants.every(variant => variant.sku !== ""); return data.variants.every(variant => variant.sku !== "");
default: default:
@ -78,34 +72,45 @@ function canHitNext(
} }
} }
export interface ProductVariantCreateDialogProps export interface ProductVariantCreatePageProps
extends Omit< extends Omit<
ProductVariantCreateContentProps, ProductVariantCreatorContentProps,
"data" | "dispatchFormDataAction" | "step" | "onStepClick" "data" | "dispatchFormDataAction" | "step" | "onStepClick"
> { > {
defaultPrice: string; defaultPrice: string;
open: boolean;
onClose: () => void;
onSubmit: (data: ProductVariantBulkCreateInput[]) => void; onSubmit: (data: ProductVariantBulkCreateInput[]) => void;
} }
const ProductVariantCreateDialog: React.FC<ProductVariantCreateDialogProps> = props => { function getTitle(step: ProductVariantCreatorStep, intl: IntlShape): string {
const { switch (step) {
attributes, case ProductVariantCreatorStep.values:
defaultPrice, return intl.formatMessage({
errors: apiErrors, defaultMessage: "Choose Values",
open, description: "product attribute values, page title"
onClose, });
onSubmit, case ProductVariantCreatorStep.prices:
...contentProps return intl.formatMessage({
} = props; defaultMessage: "Price and SKUs",
description: "page title"
});
case ProductVariantCreatorStep.summary:
return intl.formatMessage({
defaultMessage: "Summary",
description: "page title"
});
}
}
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = props => {
const { attributes, defaultPrice, errors, onSubmit, ...contentProps } = props;
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl();
const [step, { next, prev, set: setStep }] = useWizard< const [step, { next, prev, set: setStep }] = useWizard<
ProductVariantCreateStep ProductVariantCreatorStep
>(ProductVariantCreateStep.values, [ >(ProductVariantCreatorStep.values, [
ProductVariantCreateStep.values, ProductVariantCreatorStep.values,
ProductVariantCreateStep.prices, ProductVariantCreatorStep.prices,
ProductVariantCreateStep.summary ProductVariantCreatorStep.summary
]); ]);
const [data, dispatchFormDataAction] = React.useReducer( const [data, dispatchFormDataAction] = React.useReducer(
@ -121,40 +126,11 @@ const ProductVariantCreateDialog: React.FC<ProductVariantCreateDialogProps> = pr
React.useEffect(reloadForm, [attributes.length]); React.useEffect(reloadForm, [attributes.length]);
useModalDialogOpen(open, {
onClose: () => {
reloadForm();
setStep(ProductVariantCreateStep.values);
}
});
const errors = useModalDialogErrors(apiErrors, open);
return ( return (
<Dialog open={open} maxWidth="md"> <Container>
<DialogTitle> <ProductVariantCreateTabs step={step} onStepClick={setStep} />
<FormattedMessage <PageHeader title={getTitle(step, intl)}>
defaultMessage="Assign Attribute" {step !== ProductVariantCreatorStep.values && (
description="dialog header"
/>
</DialogTitle>
<DialogContent className={classes.content}>
<ProductVariantCreateContent
{...contentProps}
attributes={attributes}
data={data}
dispatchFormDataAction={dispatchFormDataAction}
errors={errors}
step={step}
onStepClick={step => setStep(step)}
/>
</DialogContent>
<DialogActions>
<Button className={classes.button} onClick={onClose}>
<FormattedMessage defaultMessage="Cancel" description="button" />
</Button>
<div className={classes.spacer} />
{step !== ProductVariantCreateStep.values && (
<Button className={classes.button} color="primary" onClick={prev}> <Button className={classes.button} color="primary" onClick={prev}>
<FormattedMessage <FormattedMessage
defaultMessage="Previous" defaultMessage="Previous"
@ -162,7 +138,7 @@ const ProductVariantCreateDialog: React.FC<ProductVariantCreateDialogProps> = pr
/> />
</Button> </Button>
)} )}
{step !== ProductVariantCreateStep.summary ? ( {step !== ProductVariantCreatorStep.summary ? (
<Button <Button
className={classes.button} className={classes.button}
color="primary" color="primary"
@ -186,10 +162,18 @@ const ProductVariantCreateDialog: React.FC<ProductVariantCreateDialogProps> = pr
/> />
</Button> </Button>
)} )}
</DialogActions> </PageHeader>
</Dialog> <ProductVariantCreatorContent
{...contentProps}
attributes={attributes}
data={data}
dispatchFormDataAction={dispatchFormDataAction}
errors={errors}
step={step}
/>
</Container>
); );
}; };
ProductVariantCreateDialog.displayName = "ProductVariantCreateDialog"; ProductVariantCreatePage.displayName = "ProductVariantCreatePage";
export default ProductVariantCreateDialog; export default ProductVariantCreatePage;

View file

@ -0,0 +1,331 @@
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
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 CardTitle from "@saleor/components/CardTitle";
import CardSpacer from "@saleor/components/CardSpacer";
import { ProductVariantCreateFormData } from "./form";
const useStyles = makeStyles(
theme => ({
hr: {
marginBottom: theme.spacing(),
marginTop: theme.spacing(0.5)
},
hrAttribute: {
marginTop: theme.spacing(2)
},
label: {
alignSelf: "center"
},
shortInput: {
width: "33%"
}
}),
{ name: "ProductVariantCreatorPrices" }
);
export type PriceOrStock = "price" | "stock";
export interface ProductVariantCreatorPricesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
currencySymbol: string;
data: ProductVariantCreateFormData;
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 ProductVariantCreatorPrices: React.FC<ProductVariantCreatorPricesProps> = props => {
const {
attributes,
currencySymbol,
data,
onApplyPriceOrStockChange,
onApplyToAllChange,
onAttributeSelect,
onAttributeValueChange
} = props;
const classes = useStyles(props);
const intl = useIntl();
const attributeChoices = attributes.map(attribute => ({
label: attribute.name,
value: attribute.id
}));
const priceAttributeValues = data.price.all
? null
: data.price.attribute
? 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.filter(value =>
data.attributes
.find(attribute => attribute.id === data.stock.attribute)
.values.includes(value.slug)
)
: [];
return (
<>
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Price",
description: "variant price, header"
})}
/>
<CardContent>
<RadioGroup
value={data.price.all ? "applyToAll" : "applyToAttribute"}
>
<FormControlLabel
value="applyToAll"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply single price to all SKUs"
})}
onChange={() => onApplyPriceOrStockChange(true, "price")}
/>
<FormSpacer />
<TextField
className={classes.shortInput}
inputProps={{
min: 0,
type: "number"
}}
InputProps={{
endAdornment: currencySymbol
}}
label={intl.formatMessage({
defaultMessage: "Price",
id: "productVariantCreatePricesPriceInputLabel"
})}
value={data.price.value}
onChange={event =>
onApplyToAllChange(event.target.value, "price")
}
/>
<FormSpacer />
<FormControlLabel
value="applyToAttribute"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply unique prices by attribute to each SKU"
})}
onChange={() => onApplyPriceOrStockChange(false, "price")}
/>
</RadioGroup>
{!data.price.all && (
<>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>
<FormattedMessage
defaultMessage="Choose attribute"
description="variant attribute"
/>
</Typography>
</div>
<div>
<SingleSelectField
choices={attributeChoices}
label={intl.formatMessage({
defaultMessage: "Attribute",
description: "variant attribute"
})}
value={data.price.attribute}
onChange={event =>
onAttributeSelect(event.target.value, "price")
}
/>
</div>
</Grid>
{priceAttributeValues &&
priceAttributeValues.map(attributeValue => (
<React.Fragment key={attributeValue.id}>
<Hr className={classes.hrAttribute} />
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
label={intl.formatMessage({
defaultMessage: "Price",
description: "variant price",
id: "productVariantCreatePricesSetPricePlaceholder"
})}
inputProps={{
min: 0,
type: "number"
}}
InputProps={{
endAdornment: currencySymbol
}}
fullWidth
value={
data.price.values.find(
value => value.slug === attributeValue.slug
).value
}
onChange={event =>
onAttributeValueChange(
attributeValue.slug,
event.target.value,
"price"
)
}
/>
</div>
</Grid>
</React.Fragment>
))}
</>
)}
</CardContent>
</Card>
<CardSpacer />
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Stock",
description: "variant stock, header"
})}
/>
<CardContent>
<RadioGroup
value={data.stock.all ? "applyToAll" : "applyToAttribute"}
>
<FormControlLabel
value="applyToAll"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply single stock to all SKUs"
})}
onChange={() => onApplyPriceOrStockChange(true, "stock")}
/>
<FormSpacer />
<TextField
className={classes.shortInput}
inputProps={{
min: 0,
type: "number"
}}
label={intl.formatMessage({
defaultMessage: "Stock",
id: "productVariantCreatePricesStockInputLabel"
})}
value={data.stock.value}
onChange={event =>
onApplyToAllChange(event.target.value, "stock")
}
/>
<FormSpacer />
<FormControlLabel
value="applyToAttribute"
control={<Radio color="primary" />}
label={intl.formatMessage({
defaultMessage: "Apply unique stock by attribute to each SKU"
})}
onChange={() => onApplyPriceOrStockChange(false, "stock")}
/>
</RadioGroup>
{!data.stock.all && (
<>
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>
<FormattedMessage
defaultMessage="Choose attribute"
description="variant attribute"
/>
</Typography>
</div>
<div>
<SingleSelectField
choices={attributeChoices}
label={intl.formatMessage({
defaultMessage: "Attribute",
description: "variant attribute"
})}
value={data.stock.attribute}
onChange={event =>
onAttributeSelect(event.target.value, "stock")
}
/>
</div>
</Grid>
{stockAttributeValues &&
stockAttributeValues.map(attributeValue => (
<React.Fragment key={attributeValue.id}>
<Hr className={classes.hrAttribute} />
<FormSpacer />
<Grid variant="uniform">
<div className={classes.label}>
<Typography>{attributeValue.name}</Typography>
</div>
<div>
<TextField
label={intl.formatMessage({
defaultMessage: "Stock",
description: "variant stock",
id: "productVariantCreatePricesSetStockPlaceholder"
})}
fullWidth
value={
data.stock.values.find(
value => value.slug === attributeValue.slug
).value
}
onChange={event =>
onAttributeValueChange(
attributeValue.slug,
event.target.value,
"stock"
)
}
/>
</div>
</Grid>
</React.Fragment>
))}
</>
)}
</CardContent>
</Card>
</>
);
};
ProductVariantCreatorPrices.displayName = "ProductVariantCreatorPrices";
export default ProductVariantCreatorPrices;

View file

@ -3,25 +3,26 @@ import cyan from "@material-ui/core/colors/cyan";
import green from "@material-ui/core/colors/green"; import green from "@material-ui/core/colors/green";
import purple from "@material-ui/core/colors/purple"; import purple from "@material-ui/core/colors/purple";
import yellow from "@material-ui/core/colors/yellow"; import yellow from "@material-ui/core/colors/yellow";
import Card from "@material-ui/core/Card";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Hr from "@saleor/components/Hr";
import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors"; import { getFormErrors } from "@saleor/utils/errors";
import { getBulkProductErrorMessage } from "@saleor/utils/errors/product"; import { getBulkProductErrorMessage } from "@saleor/utils/errors/product";
import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl";
import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails";
import { ProductVariantCreateFormData } from "./form"; import { ProductVariantCreateFormData } from "./form";
import { VariantField } from "./reducer"; import { VariantField } from "./reducer";
export interface ProductVariantCreateSummaryProps { export interface ProductVariantCreatorSummaryProps {
attributes: ProductDetails_product_productType_variantAttributes[]; attributes: ProductDetails_product_productType_variantAttributes[];
currencySymbol: string; currencySymbol: string;
data: ProductVariantCreateFormData; data: ProductVariantCreateFormData;
@ -81,11 +82,11 @@ const useStyles = makeStyles(
borderBottom: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`,
display: "grid", display: "grid",
gridTemplateColumns: "1fr 180px 120px 180px 64px", gridTemplateColumns: "1fr 180px 120px 180px 64px",
padding: theme.spacing(1, 0) padding: theme.spacing(1, 1, 1, 3)
} }
}), }),
{ {
name: "ProductVariantCreateSummary" name: "ProductVariantCreatorSummary"
} }
); );
@ -108,7 +109,7 @@ function getVariantName(
); );
} }
const ProductVariantCreateSummary: React.FC<ProductVariantCreateSummaryProps> = props => { const ProductVariantCreatorSummary: React.FC<ProductVariantCreatorSummaryProps> = props => {
const { const {
attributes, attributes,
currencySymbol, currencySymbol,
@ -121,14 +122,8 @@ const ProductVariantCreateSummary: React.FC<ProductVariantCreateSummaryProps> =
const intl = useIntl(); const intl = useIntl();
return ( return (
<> <Card>
<Typography color="textSecondary" variant="h5"> <CardTitle title={intl.formatMessage(commonMessages.summary)} />
<FormattedMessage
defaultMessage="You will create variants below"
description="header"
/>
</Typography>
<Hr className={classes.hr} />
<div> <div>
<div className={classes.row}> <div className={classes.row}>
<div <div
@ -287,9 +282,9 @@ const ProductVariantCreateSummary: React.FC<ProductVariantCreateSummaryProps> =
); );
})} })}
</div> </div>
</> </Card>
); );
}; };
ProductVariantCreateSummary.displayName = "ProductVariantCreateSummary"; ProductVariantCreatorSummary.displayName = "ProductVariantCreatorSummary";
export default ProductVariantCreateSummary; export default ProductVariantCreatorSummary;

View file

@ -4,11 +4,11 @@ import classNames from "classnames";
import React from "react"; import React from "react";
import { IntlShape, useIntl } from "react-intl"; import { IntlShape, useIntl } from "react-intl";
import { ProductVariantCreateStep } from "./types"; import { ProductVariantCreatorStep } from "./types";
interface Step { interface Step {
label: string; label: string;
value: ProductVariantCreateStep; value: ProductVariantCreatorStep;
} }
function getSteps(intl: IntlShape): Step[] { function getSteps(intl: IntlShape): Step[] {
return [ return [
@ -17,21 +17,21 @@ function getSteps(intl: IntlShape): Step[] {
defaultMessage: "Select Values", defaultMessage: "Select Values",
description: "attribute values, variant creation step" description: "attribute values, variant creation step"
}), }),
value: ProductVariantCreateStep.values value: ProductVariantCreatorStep.values
}, },
{ {
label: intl.formatMessage({ label: intl.formatMessage({
defaultMessage: "Prices and SKU", defaultMessage: "Prices and SKU",
description: "variant creation step" description: "variant creation step"
}), }),
value: ProductVariantCreateStep.prices value: ProductVariantCreatorStep.prices
}, },
{ {
label: intl.formatMessage({ label: intl.formatMessage({
defaultMessage: "Summary", defaultMessage: "Summary",
description: "variant creation step" description: "variant creation step"
}), }),
value: ProductVariantCreateStep.summary value: ProductVariantCreatorStep.summary
} }
]; ];
} }
@ -62,16 +62,16 @@ const useStyles = makeStyles(
} }
}), }),
{ {
name: "ProductVariantCreateTabs" name: "ProductVariantCreatorTabs"
} }
); );
export interface ProductVariantCreateTabsProps { export interface ProductVariantCreatorTabsProps {
step: ProductVariantCreateStep; step: ProductVariantCreatorStep;
onStepClick: (step: ProductVariantCreateStep) => void; onStepClick: (step: ProductVariantCreatorStep) => void;
} }
const ProductVariantCreateTabs: React.FC<ProductVariantCreateTabsProps> = props => { const ProductVariantCreatorTabs: React.FC<ProductVariantCreatorTabsProps> = props => {
const { step: currentStep, onStepClick } = props; const { step: currentStep, onStepClick } = props;
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
@ -102,5 +102,5 @@ const ProductVariantCreateTabs: React.FC<ProductVariantCreateTabsProps> = props
); );
}; };
ProductVariantCreateTabs.displayName = "ProductVariantCreateTabs"; ProductVariantCreatorTabs.displayName = "ProductVariantCreatorTabs";
export default ProductVariantCreateTabs; export default ProductVariantCreatorTabs;

View file

@ -0,0 +1,75 @@
import makeStyles from "@material-ui/core/styles/makeStyles";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import React from "react";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Debounce from "@saleor/components/Debounce";
import Skeleton from "@saleor/components/Skeleton";
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
import { isSelected } from "@saleor/utils/lists";
import CardTitle from "@saleor/components/CardTitle";
import CardSpacer from "@saleor/components/CardSpacer";
import { ProductVariantCreateFormData } from "./form";
export interface ProductVariantCreatorValuesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
data: ProductVariantCreateFormData;
onValueClick: (attributeId: string, valueId: string) => void;
}
const useStyles = makeStyles(
theme => ({
valueContainer: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "repeat(5, 1fr)"
}
}),
{ name: "ProductVariantCreatorValues" }
);
const ProductVariantCreatorValues: React.FC<ProductVariantCreatorValuesProps> = props => {
const { attributes, data, onValueClick } = props;
const classes = useStyles(props);
return (
<>
{attributes.map(attribute => (
<>
<Card key={attribute.id}>
<CardTitle title={attribute?.name || <Skeleton />} />
<CardContent className={classes.valueContainer}>
{attribute.values.map(value => (
<Debounce
debounceFn={() => onValueClick(attribute.id, value.slug)}
time={100}
key={value.slug}
>
{change => (
<ControlledCheckbox
checked={isSelected(
value.slug,
data.attributes.find(
dataAttribute => attribute.id === dataAttribute.id
).values,
(a, b) => a === b
)}
name={`value:${value.slug}`}
label={value.name}
onChange={change}
/>
)}
</Debounce>
))}
</CardContent>
</Card>
<CardSpacer />
</>
))}
</>
);
};
ProductVariantCreatorValues.displayName = "ProductVariantCreatorValues";
export default ProductVariantCreatorValues;

View file

@ -97,9 +97,10 @@ export function createVariants(
) { ) {
return []; return [];
} }
const variants = createVariantFlatMatrixDimension([[]], data.attributes).map( const variants = createVariantFlatMatrixDimension(
variant => createVariant(data, variant) [[]],
); data.attributes
).map(variant => createVariant(data, variant));
return variants; return variants;
} }

View file

@ -0,0 +1,2 @@
export * from "./ProductVariantCreatorPage";
export { default } from "./ProductVariantCreatorPage";

View file

@ -0,0 +1,5 @@
export enum ProductVariantCreatorStep {
values,
prices,
summary
}

View file

@ -7,7 +7,6 @@ import {
TypedProductImageCreateMutation, TypedProductImageCreateMutation,
TypedProductImageDeleteMutation, TypedProductImageDeleteMutation,
TypedProductUpdateMutation, TypedProductUpdateMutation,
TypedProductVariantBulkCreateMutation,
TypedProductVariantBulkDeleteMutation, TypedProductVariantBulkDeleteMutation,
TypedSimpleProductUpdateMutation TypedSimpleProductUpdateMutation
} from "../mutations"; } from "../mutations";
@ -26,10 +25,6 @@ import {
ProductImageReorderVariables ProductImageReorderVariables
} from "../types/ProductImageReorder"; } from "../types/ProductImageReorder";
import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate"; import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate";
import {
ProductVariantBulkCreate,
ProductVariantBulkCreateVariables
} from "../types/ProductVariantBulkCreate";
import { import {
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
@ -43,10 +38,6 @@ import ProductImagesReorderProvider from "./ProductImagesReorder";
interface ProductUpdateOperationsProps { interface ProductUpdateOperationsProps {
product: ProductDetails_product; product: ProductDetails_product;
children: (props: { children: (props: {
bulkProductVariantCreate: PartialMutationProviderOutput<
ProductVariantBulkCreate,
ProductVariantBulkCreateVariables
>;
bulkProductVariantDelete: PartialMutationProviderOutput< bulkProductVariantDelete: PartialMutationProviderOutput<
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
@ -76,7 +67,6 @@ interface ProductUpdateOperationsProps {
SimpleProductUpdateVariables SimpleProductUpdateVariables
>; >;
}) => React.ReactNode; }) => React.ReactNode;
onBulkProductVariantCreate?: (data: ProductVariantBulkCreate) => void;
onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void; onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void;
onDelete?: (data: ProductDelete) => void; onDelete?: (data: ProductDelete) => void;
onImageCreate?: (data: ProductImageCreate) => void; onImageCreate?: (data: ProductImageCreate) => void;
@ -88,7 +78,6 @@ interface ProductUpdateOperationsProps {
const ProductUpdateOperations: React.FC<ProductUpdateOperationsProps> = ({ const ProductUpdateOperations: React.FC<ProductUpdateOperationsProps> = ({
product, product,
children, children,
onBulkProductVariantCreate,
onBulkProductVariantDelete, onBulkProductVariantDelete,
onDelete, onDelete,
onImageDelete, onImageDelete,
@ -121,40 +110,31 @@ const ProductUpdateOperations: React.FC<ProductUpdateOperationsProps> = ({
<TypedProductVariantBulkDeleteMutation <TypedProductVariantBulkDeleteMutation
onCompleted={onBulkProductVariantDelete} onCompleted={onBulkProductVariantDelete}
> >
{(...bulkProductVariantDelete) => ( {(...bulkProductVariantDelete) =>
<TypedProductVariantBulkCreateMutation children({
onCompleted={onBulkProductVariantCreate} bulkProductVariantDelete: getMutationProviderData(
> ...bulkProductVariantDelete
{(...bulkProductVariantCreate) => ),
children({ createProductImage: getMutationProviderData(
bulkProductVariantCreate: getMutationProviderData( ...createProductImage
...bulkProductVariantCreate ),
), deleteProduct: getMutationProviderData(
bulkProductVariantDelete: getMutationProviderData( ...deleteProduct
...bulkProductVariantDelete ),
), deleteProductImage: getMutationProviderData(
createProductImage: getMutationProviderData( ...deleteProductImage
...createProductImage ),
), reorderProductImages: getMutationProviderData(
deleteProduct: getMutationProviderData( ...reorderProductImages
...deleteProduct ),
), updateProduct: getMutationProviderData(
deleteProductImage: getMutationProviderData( ...updateProduct
...deleteProductImage ),
), updateSimpleProduct: getMutationProviderData(
reorderProductImages: getMutationProviderData( ...updateSimpleProduct
...reorderProductImages )
), })
updateProduct: getMutationProviderData( }
...updateProduct
),
updateSimpleProduct: getMutationProviderData(
...updateSimpleProduct
)
})
}
</TypedProductVariantBulkCreateMutation>
)}
</TypedProductVariantBulkDeleteMutation> </TypedProductVariantBulkDeleteMutation>
)} )}
</TypedSimpleProductUpdateMutation> </TypedSimpleProductUpdateMutation>

View file

@ -20,7 +20,8 @@ import {
productVariantEditPath, productVariantEditPath,
ProductVariantEditUrlQueryParams, ProductVariantEditUrlQueryParams,
ProductAddUrlQueryParams, ProductAddUrlQueryParams,
ProductVariantAddUrlQueryParams ProductVariantAddUrlQueryParams,
productVariantCreatorPath
} from "./urls"; } from "./urls";
import ProductCreateComponent from "./views/ProductCreate"; import ProductCreateComponent from "./views/ProductCreate";
import ProductImageComponent from "./views/ProductImage"; import ProductImageComponent from "./views/ProductImage";
@ -28,6 +29,7 @@ import ProductListComponent from "./views/ProductList";
import ProductUpdateComponent from "./views/ProductUpdate"; import ProductUpdateComponent from "./views/ProductUpdate";
import ProductVariantComponent from "./views/ProductVariant"; import ProductVariantComponent from "./views/ProductVariant";
import ProductVariantCreateComponent from "./views/ProductVariantCreate"; import ProductVariantCreateComponent from "./views/ProductVariantCreate";
import ProductVariantCreatorComponent from "./views/ProductVariantCreator";
const ProductList: React.FC<RouteComponentProps<any>> = ({ location }) => { const ProductList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
@ -100,6 +102,12 @@ const ProductVariantCreate: React.FC<RouteComponentProps<any>> = ({
); );
}; };
const ProductVariantCreator: React.FC<RouteComponentProps<{
id: string;
}>> = ({ match }) => (
<ProductVariantCreatorComponent id={decodeURIComponent(match.params.id)} />
);
const ProductCreate: React.FC<RouteComponentProps> = ({ location }) => { const ProductCreate: React.FC<RouteComponentProps> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: ProductAddUrlQueryParams = qs; const params: ProductAddUrlQueryParams = qs;
@ -116,6 +124,10 @@ const Component = () => {
<Switch> <Switch>
<Route exact path={productListPath} component={ProductList} /> <Route exact path={productListPath} component={ProductList} />
<Route exact path={productAddPath} component={ProductCreate} /> <Route exact path={productAddPath} component={ProductCreate} />
<Route
path={productVariantCreatorPath(":id")}
component={ProductVariantCreator}
/>
<Route <Route
exact exact
path={productVariantAddPath(":id")} path={productVariantAddPath(":id")}

View file

@ -543,7 +543,7 @@ export const ProductVariantBulkCreateMutation = gql`
} }
} }
`; `;
export const TypedProductVariantBulkCreateMutation = TypedMutation< export const useProductVariantBulkCreateMutation = makeMutation<
ProductVariantBulkCreate, ProductVariantBulkCreate,
ProductVariantBulkCreateVariables ProductVariantBulkCreateVariables
>(ProductVariantBulkCreateMutation); >(ProductVariantBulkCreateMutation);

View file

@ -27,6 +27,10 @@ import {
InitialProductFilterData, InitialProductFilterData,
InitialProductFilterDataVariables InitialProductFilterDataVariables
} from "./types/InitialProductFilterData"; } from "./types/InitialProductFilterData";
import {
CreateMultipleVariantsData,
CreateMultipleVariantsDataVariables
} from "./types/CreateMultipleVariantsData";
export const stockFragment = gql` export const stockFragment = gql`
fragment StockFragment on Stock { fragment StockFragment on Stock {
@ -74,12 +78,50 @@ export const productFragment = gql`
} }
} }
`; `;
const productVariantAttributesFragment = gql`
fragment ProductVariantAttributesFragment on Product {
id
attributes {
attribute {
id
slug
name
inputType
valueRequired
values {
id
name
slug
}
}
values {
id
name
slug
}
}
productType {
variantAttributes {
id
name
values {
id
name
slug
}
}
}
}
`;
export const productFragmentDetails = gql` export const productFragmentDetails = gql`
${fragmentProductImage} ${fragmentProductImage}
${fragmentMoney} ${fragmentMoney}
${productVariantAttributesFragment}
${stockFragment} ${stockFragment}
fragment Product on Product { fragment Product on Product {
id ...ProductVariantAttributesFragment
name name
descriptionJson descriptionJson
seoTitle seoTitle
@ -111,25 +153,6 @@ export const productFragmentDetails = gql`
isPublished isPublished
chargeTaxes chargeTaxes
publicationDate publicationDate
attributes {
attribute {
id
slug
name
inputType
valueRequired
values {
id
name
slug
}
}
values {
id
name
slug
}
}
pricing { pricing {
priceRange { priceRange {
start { start {
@ -332,17 +355,6 @@ const productDetailsQuery = gql`
query ProductDetails($id: ID!) { query ProductDetails($id: ID!) {
product(id: $id) { product(id: $id) {
...Product ...Product
productType {
variantAttributes {
id
name
values {
id
name
slug
}
}
}
} }
} }
`; `;
@ -464,3 +476,20 @@ export const AvailableInGridAttributesQuery = TypedQuery<
AvailableInGridAttributes, AvailableInGridAttributes,
AvailableInGridAttributesVariables AvailableInGridAttributesVariables
>(availableInGridAttributes); >(availableInGridAttributes);
const createMultipleVariantsData = gql`
${fragmentMoney}
${productVariantAttributesFragment}
query CreateMultipleVariantsData($id: ID!) {
product(id: $id) {
...ProductVariantAttributesFragment
basePrice {
...Money
}
}
}
`;
export const useCreateMultipleVariantsData = makeQuery<
CreateMultipleVariantsData,
CreateMultipleVariantsDataVariables
>(createMultipleVariantsData);

View file

@ -0,0 +1,80 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AttributeInputTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: CreateMultipleVariantsData
// ====================================================
export interface CreateMultipleVariantsData_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface CreateMultipleVariantsData_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (CreateMultipleVariantsData_product_attributes_attribute_values | null)[] | null;
}
export interface CreateMultipleVariantsData_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface CreateMultipleVariantsData_product_attributes {
__typename: "SelectedAttribute";
attribute: CreateMultipleVariantsData_product_attributes_attribute;
values: (CreateMultipleVariantsData_product_attributes_values | null)[];
}
export interface CreateMultipleVariantsData_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface CreateMultipleVariantsData_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (CreateMultipleVariantsData_product_productType_variantAttributes_values | null)[] | null;
}
export interface CreateMultipleVariantsData_product_productType {
__typename: "ProductType";
variantAttributes: (CreateMultipleVariantsData_product_productType_variantAttributes | null)[] | null;
}
export interface CreateMultipleVariantsData_product_basePrice {
__typename: "Money";
amount: number;
currency: string;
}
export interface CreateMultipleVariantsData_product {
__typename: "Product";
id: string;
attributes: CreateMultipleVariantsData_product_attributes[];
productType: CreateMultipleVariantsData_product_productType;
basePrice: CreateMultipleVariantsData_product_basePrice | null;
}
export interface CreateMultipleVariantsData {
product: CreateMultipleVariantsData_product | null;
}
export interface CreateMultipleVariantsDataVariables {
id: string;
}

View file

@ -8,6 +8,58 @@ import { AttributeInputTypeEnum } from "./../../types/globalTypes";
// GraphQL fragment: Product // GraphQL fragment: Product
// ==================================================== // ====================================================
export interface Product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface Product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (Product_attributes_attribute_values | null)[] | null;
}
export interface Product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface Product_attributes {
__typename: "SelectedAttribute";
attribute: Product_attributes_attribute;
values: (Product_attributes_values | null)[];
}
export interface Product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface Product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (Product_productType_variantAttributes_values | null)[] | null;
}
export interface Product_productType {
__typename: "ProductType";
variantAttributes: (Product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface Product_category { export interface Product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -50,36 +102,6 @@ export interface Product_purchaseCost {
stop: Product_purchaseCost_stop | null; stop: Product_purchaseCost_stop | null;
} }
export interface Product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface Product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (Product_attributes_attribute_values | null)[] | null;
}
export interface Product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface Product_attributes {
__typename: "SelectedAttribute";
attribute: Product_attributes_attribute;
values: (Product_attributes_values | null)[];
}
export interface Product_pricing_priceRange_start_net { export interface Product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -151,16 +173,11 @@ export interface Product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface Product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface Product { export interface Product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: Product_attributes[];
productType: Product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -174,9 +191,7 @@ export interface Product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: Product_attributes[];
pricing: Product_pricing | null; pricing: Product_pricing | null;
images: (Product_images | null)[] | null; images: (Product_images | null)[] | null;
variants: (Product_variants | null)[] | null; variants: (Product_variants | null)[] | null;
productType: Product_productType;
} }

View file

@ -14,6 +14,58 @@ export interface ProductCreate_productCreate_errors {
field: string | null; field: string | null;
} }
export interface ProductCreate_productCreate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductCreate_productCreate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductCreate_productCreate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductCreate_productCreate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductCreate_productCreate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductCreate_productCreate_product_attributes_attribute;
values: (ProductCreate_productCreate_product_attributes_values | null)[];
}
export interface ProductCreate_productCreate_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductCreate_productCreate_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductCreate_productCreate_product_productType_variantAttributes_values | null)[] | null;
}
export interface ProductCreate_productCreate_product_productType {
__typename: "ProductType";
variantAttributes: (ProductCreate_productCreate_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductCreate_productCreate_product_category { export interface ProductCreate_productCreate_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -56,36 +108,6 @@ export interface ProductCreate_productCreate_product_purchaseCost {
stop: ProductCreate_productCreate_product_purchaseCost_stop | null; stop: ProductCreate_productCreate_product_purchaseCost_stop | null;
} }
export interface ProductCreate_productCreate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductCreate_productCreate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductCreate_productCreate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductCreate_productCreate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductCreate_productCreate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductCreate_productCreate_product_attributes_attribute;
values: (ProductCreate_productCreate_product_attributes_values | null)[];
}
export interface ProductCreate_productCreate_product_pricing_priceRange_start_net { export interface ProductCreate_productCreate_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -157,16 +179,11 @@ export interface ProductCreate_productCreate_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface ProductCreate_productCreate_product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductCreate_productCreate_product { export interface ProductCreate_productCreate_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: ProductCreate_productCreate_product_attributes[];
productType: ProductCreate_productCreate_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -180,11 +197,9 @@ export interface ProductCreate_productCreate_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: ProductCreate_productCreate_product_attributes[];
pricing: ProductCreate_productCreate_product_pricing | null; pricing: ProductCreate_productCreate_product_pricing | null;
images: (ProductCreate_productCreate_product_images | null)[] | null; images: (ProductCreate_productCreate_product_images | null)[] | null;
variants: (ProductCreate_productCreate_product_variants | null)[] | null; variants: (ProductCreate_productCreate_product_variants | null)[] | null;
productType: ProductCreate_productCreate_product_productType;
} }
export interface ProductCreate_productCreate { export interface ProductCreate_productCreate {

View file

@ -8,6 +8,58 @@ import { AttributeInputTypeEnum } from "./../../types/globalTypes";
// GraphQL query operation: ProductDetails // GraphQL query operation: ProductDetails
// ==================================================== // ====================================================
export interface ProductDetails_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductDetails_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductDetails_product_attributes_attribute_values | null)[] | null;
}
export interface ProductDetails_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductDetails_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductDetails_product_attributes_attribute;
values: (ProductDetails_product_attributes_values | null)[];
}
export interface ProductDetails_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: 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";
variantAttributes: (ProductDetails_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductDetails_product_category { export interface ProductDetails_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -50,36 +102,6 @@ export interface ProductDetails_product_purchaseCost {
stop: ProductDetails_product_purchaseCost_stop | null; stop: ProductDetails_product_purchaseCost_stop | null;
} }
export interface ProductDetails_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductDetails_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductDetails_product_attributes_attribute_values | null)[] | null;
}
export interface ProductDetails_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductDetails_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductDetails_product_attributes_attribute;
values: (ProductDetails_product_attributes_values | null)[];
}
export interface ProductDetails_product_pricing_priceRange_start_net { export interface ProductDetails_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -151,31 +173,11 @@ export interface ProductDetails_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface ProductDetails_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: 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 { export interface ProductDetails_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: ProductDetails_product_attributes[];
productType: ProductDetails_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -189,11 +191,9 @@ export interface ProductDetails_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: ProductDetails_product_attributes[];
pricing: ProductDetails_product_pricing | null; pricing: ProductDetails_product_pricing | null;
images: (ProductDetails_product_images | null)[] | null; images: (ProductDetails_product_images | null)[] | null;
variants: (ProductDetails_product_variants | null)[] | null; variants: (ProductDetails_product_variants | null)[] | null;
productType: ProductDetails_product_productType;
} }
export interface ProductDetails { export interface ProductDetails {

View file

@ -14,6 +14,58 @@ export interface ProductImageCreate_productImageCreate_errors {
field: string | null; field: string | null;
} }
export interface ProductImageCreate_productImageCreate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductImageCreate_productImageCreate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductImageCreate_productImageCreate_product_attributes_attribute;
values: (ProductImageCreate_productImageCreate_product_attributes_values | null)[];
}
export interface ProductImageCreate_productImageCreate_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageCreate_productImageCreate_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductImageCreate_productImageCreate_product_productType_variantAttributes_values | null)[] | null;
}
export interface ProductImageCreate_productImageCreate_product_productType {
__typename: "ProductType";
variantAttributes: (ProductImageCreate_productImageCreate_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductImageCreate_productImageCreate_product_category { export interface ProductImageCreate_productImageCreate_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -56,36 +108,6 @@ export interface ProductImageCreate_productImageCreate_product_purchaseCost {
stop: ProductImageCreate_productImageCreate_product_purchaseCost_stop | null; stop: ProductImageCreate_productImageCreate_product_purchaseCost_stop | null;
} }
export interface ProductImageCreate_productImageCreate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductImageCreate_productImageCreate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageCreate_productImageCreate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductImageCreate_productImageCreate_product_attributes_attribute;
values: (ProductImageCreate_productImageCreate_product_attributes_values | null)[];
}
export interface ProductImageCreate_productImageCreate_product_pricing_priceRange_start_net { export interface ProductImageCreate_productImageCreate_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -157,16 +179,11 @@ export interface ProductImageCreate_productImageCreate_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface ProductImageCreate_productImageCreate_product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductImageCreate_productImageCreate_product { export interface ProductImageCreate_productImageCreate_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: ProductImageCreate_productImageCreate_product_attributes[];
productType: ProductImageCreate_productImageCreate_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -180,11 +197,9 @@ export interface ProductImageCreate_productImageCreate_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: ProductImageCreate_productImageCreate_product_attributes[];
pricing: ProductImageCreate_productImageCreate_product_pricing | null; pricing: ProductImageCreate_productImageCreate_product_pricing | null;
images: (ProductImageCreate_productImageCreate_product_images | null)[] | null; images: (ProductImageCreate_productImageCreate_product_images | null)[] | null;
variants: (ProductImageCreate_productImageCreate_product_variants | null)[] | null; variants: (ProductImageCreate_productImageCreate_product_variants | null)[] | null;
productType: ProductImageCreate_productImageCreate_product_productType;
} }
export interface ProductImageCreate_productImageCreate { export interface ProductImageCreate_productImageCreate {

View file

@ -14,6 +14,58 @@ export interface ProductImageUpdate_productImageUpdate_errors {
field: string | null; field: string | null;
} }
export interface ProductImageUpdate_productImageUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductImageUpdate_productImageUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductImageUpdate_productImageUpdate_product_attributes_attribute;
values: (ProductImageUpdate_productImageUpdate_product_attributes_values | null)[];
}
export interface ProductImageUpdate_productImageUpdate_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductImageUpdate_productImageUpdate_product_productType_variantAttributes_values | null)[] | null;
}
export interface ProductImageUpdate_productImageUpdate_product_productType {
__typename: "ProductType";
variantAttributes: (ProductImageUpdate_productImageUpdate_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductImageUpdate_productImageUpdate_product_category { export interface ProductImageUpdate_productImageUpdate_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -56,36 +108,6 @@ export interface ProductImageUpdate_productImageUpdate_product_purchaseCost {
stop: ProductImageUpdate_productImageUpdate_product_purchaseCost_stop | null; stop: ProductImageUpdate_productImageUpdate_product_purchaseCost_stop | null;
} }
export interface ProductImageUpdate_productImageUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductImageUpdate_productImageUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductImageUpdate_productImageUpdate_product_attributes_attribute;
values: (ProductImageUpdate_productImageUpdate_product_attributes_values | null)[];
}
export interface ProductImageUpdate_productImageUpdate_product_pricing_priceRange_start_net { export interface ProductImageUpdate_productImageUpdate_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -157,16 +179,11 @@ export interface ProductImageUpdate_productImageUpdate_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface ProductImageUpdate_productImageUpdate_product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductImageUpdate_productImageUpdate_product { export interface ProductImageUpdate_productImageUpdate_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: ProductImageUpdate_productImageUpdate_product_attributes[];
productType: ProductImageUpdate_productImageUpdate_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -180,11 +197,9 @@ export interface ProductImageUpdate_productImageUpdate_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: ProductImageUpdate_productImageUpdate_product_attributes[];
pricing: ProductImageUpdate_productImageUpdate_product_pricing | null; pricing: ProductImageUpdate_productImageUpdate_product_pricing | null;
images: (ProductImageUpdate_productImageUpdate_product_images | null)[] | null; images: (ProductImageUpdate_productImageUpdate_product_images | null)[] | null;
variants: (ProductImageUpdate_productImageUpdate_product_variants | null)[] | null; variants: (ProductImageUpdate_productImageUpdate_product_variants | null)[] | null;
productType: ProductImageUpdate_productImageUpdate_product_productType;
} }
export interface ProductImageUpdate_productImageUpdate { export interface ProductImageUpdate_productImageUpdate {

View file

@ -14,6 +14,58 @@ export interface ProductUpdate_productUpdate_errors {
field: string | null; field: string | null;
} }
export interface ProductUpdate_productUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductUpdate_productUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductUpdate_productUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductUpdate_productUpdate_product_attributes_attribute;
values: (ProductUpdate_productUpdate_product_attributes_values | null)[];
}
export interface ProductUpdate_productUpdate_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductUpdate_productUpdate_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductUpdate_productUpdate_product_productType_variantAttributes_values | null)[] | null;
}
export interface ProductUpdate_productUpdate_product_productType {
__typename: "ProductType";
variantAttributes: (ProductUpdate_productUpdate_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductUpdate_productUpdate_product_category { export interface ProductUpdate_productUpdate_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -56,36 +108,6 @@ export interface ProductUpdate_productUpdate_product_purchaseCost {
stop: ProductUpdate_productUpdate_product_purchaseCost_stop | null; stop: ProductUpdate_productUpdate_product_purchaseCost_stop | null;
} }
export interface ProductUpdate_productUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductUpdate_productUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface ProductUpdate_productUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductUpdate_productUpdate_product_attributes_attribute;
values: (ProductUpdate_productUpdate_product_attributes_values | null)[];
}
export interface ProductUpdate_productUpdate_product_pricing_priceRange_start_net { export interface ProductUpdate_productUpdate_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -157,16 +179,11 @@ export interface ProductUpdate_productUpdate_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface ProductUpdate_productUpdate_product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface ProductUpdate_productUpdate_product { export interface ProductUpdate_productUpdate_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: ProductUpdate_productUpdate_product_attributes[];
productType: ProductUpdate_productUpdate_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -180,11 +197,9 @@ export interface ProductUpdate_productUpdate_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: ProductUpdate_productUpdate_product_attributes[];
pricing: ProductUpdate_productUpdate_product_pricing | null; pricing: ProductUpdate_productUpdate_product_pricing | null;
images: (ProductUpdate_productUpdate_product_images | null)[] | null; images: (ProductUpdate_productUpdate_product_images | null)[] | null;
variants: (ProductUpdate_productUpdate_product_variants | null)[] | null; variants: (ProductUpdate_productUpdate_product_variants | null)[] | null;
productType: ProductUpdate_productUpdate_product_productType;
} }
export interface ProductUpdate_productUpdate { export interface ProductUpdate_productUpdate {

View file

@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AttributeInputTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: ProductVariantAttributesFragment
// ====================================================
export interface ProductVariantAttributesFragment_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantAttributesFragment_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductVariantAttributesFragment_attributes_attribute_values | null)[] | null;
}
export interface ProductVariantAttributesFragment_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantAttributesFragment_attributes {
__typename: "SelectedAttribute";
attribute: ProductVariantAttributesFragment_attributes_attribute;
values: (ProductVariantAttributesFragment_attributes_values | null)[];
}
export interface ProductVariantAttributesFragment_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantAttributesFragment_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductVariantAttributesFragment_productType_variantAttributes_values | null)[] | null;
}
export interface ProductVariantAttributesFragment_productType {
__typename: "ProductType";
variantAttributes: (ProductVariantAttributesFragment_productType_variantAttributes | null)[] | null;
}
export interface ProductVariantAttributesFragment {
__typename: "Product";
id: string;
attributes: ProductVariantAttributesFragment_attributes[];
productType: ProductVariantAttributesFragment_productType;
}

View file

@ -14,6 +14,58 @@ export interface SimpleProductUpdate_productUpdate_errors {
field: string | null; field: string | null;
} }
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (SimpleProductUpdate_productUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: SimpleProductUpdate_productUpdate_product_attributes_attribute;
values: (SimpleProductUpdate_productUpdate_product_attributes_values | null)[];
}
export interface SimpleProductUpdate_productUpdate_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (SimpleProductUpdate_productUpdate_product_productType_variantAttributes_values | null)[] | null;
}
export interface SimpleProductUpdate_productUpdate_product_productType {
__typename: "ProductType";
variantAttributes: (SimpleProductUpdate_productUpdate_product_productType_variantAttributes | null)[] | null;
id: string;
name: string;
hasVariants: boolean;
}
export interface SimpleProductUpdate_productUpdate_product_category { export interface SimpleProductUpdate_productUpdate_product_category {
__typename: "Category"; __typename: "Category";
id: string; id: string;
@ -56,36 +108,6 @@ export interface SimpleProductUpdate_productUpdate_product_purchaseCost {
stop: SimpleProductUpdate_productUpdate_product_purchaseCost_stop | null; stop: SimpleProductUpdate_productUpdate_product_purchaseCost_stop | null;
} }
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (SimpleProductUpdate_productUpdate_product_attributes_attribute_values | null)[] | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes {
__typename: "SelectedAttribute";
attribute: SimpleProductUpdate_productUpdate_product_attributes_attribute;
values: (SimpleProductUpdate_productUpdate_product_attributes_values | null)[];
}
export interface SimpleProductUpdate_productUpdate_product_pricing_priceRange_start_net { export interface SimpleProductUpdate_productUpdate_product_pricing_priceRange_start_net {
__typename: "Money"; __typename: "Money";
amount: number; amount: number;
@ -157,16 +179,11 @@ export interface SimpleProductUpdate_productUpdate_product_variants {
trackInventory: boolean; trackInventory: boolean;
} }
export interface SimpleProductUpdate_productUpdate_product_productType {
__typename: "ProductType";
id: string;
name: string;
hasVariants: boolean;
}
export interface SimpleProductUpdate_productUpdate_product { export interface SimpleProductUpdate_productUpdate_product {
__typename: "Product"; __typename: "Product";
id: string; id: string;
attributes: SimpleProductUpdate_productUpdate_product_attributes[];
productType: SimpleProductUpdate_productUpdate_product_productType;
name: string; name: string;
descriptionJson: any; descriptionJson: any;
seoTitle: string | null; seoTitle: string | null;
@ -180,11 +197,9 @@ export interface SimpleProductUpdate_productUpdate_product {
isPublished: boolean; isPublished: boolean;
chargeTaxes: boolean; chargeTaxes: boolean;
publicationDate: any | null; publicationDate: any | null;
attributes: SimpleProductUpdate_productUpdate_product_attributes[];
pricing: SimpleProductUpdate_productUpdate_product_pricing | null; pricing: SimpleProductUpdate_productUpdate_product_pricing | null;
images: (SimpleProductUpdate_productUpdate_product_images | null)[] | null; images: (SimpleProductUpdate_productUpdate_product_images | null)[] | null;
variants: (SimpleProductUpdate_productUpdate_product_variants | null)[] | null; variants: (SimpleProductUpdate_productUpdate_product_variants | null)[] | null;
productType: SimpleProductUpdate_productUpdate_product_productType;
} }
export interface SimpleProductUpdate_productUpdate { export interface SimpleProductUpdate_productUpdate {

View file

@ -69,11 +69,7 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string =>
productListPath + "?" + stringifyQs(params); productListPath + "?" + stringifyQs(params);
export const productPath = (id: string) => urlJoin(productSection + id); export const productPath = (id: string) => urlJoin(productSection + id);
export type ProductUrlDialog = export type ProductUrlDialog = "edit-stocks" | "remove" | "remove-variants";
| "create-variants"
| "edit-stocks"
| "remove"
| "remove-variants";
export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>; export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>;
export const productUrl = (id: string, params?: ProductUrlQueryParams) => export const productUrl = (id: string, params?: ProductUrlQueryParams) =>
productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); productPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
@ -96,6 +92,11 @@ export const productVariantEditUrl = (
"?" + "?" +
stringifyQs(params); stringifyQs(params);
export const productVariantCreatorPath = (productId: string) =>
urlJoin(productSection, productId, "variant-creator");
export const productVariantCreatorUrl = (productId: string) =>
productVariantCreatorPath(encodeURIComponent(productId));
export const productVariantAddPath = (productId: string) => export const productVariantAddPath = (productId: string) =>
urlJoin(productSection, productId, "variant/add"); urlJoin(productSection, productId, "variant/add");
export type ProductVariantAddUrlDialog = "edit-stocks"; export type ProductVariantAddUrlDialog = "edit-stocks";

View file

@ -11,10 +11,7 @@ import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import ProductVariantCreateDialog from "@saleor/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog";
import { ProductVariantBulkCreate } from "@saleor/products/types/ProductVariantBulkCreate";
import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
@ -39,7 +36,8 @@ import {
ProductUrlQueryParams, ProductUrlQueryParams,
productVariantAddUrl, productVariantAddUrl,
productVariantEditUrl, productVariantEditUrl,
ProductUrlDialog ProductUrlDialog,
productVariantCreatorUrl
} from "../../urls"; } from "../../urls";
import { import {
createImageReorderHandler, createImageReorderHandler,
@ -59,7 +57,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
params.ids params.ids
); );
const intl = useIntl(); const intl = useIntl();
const shop = useShop();
const { const {
loadMore: loadMoreCategories, loadMore: loadMoreCategories,
search: searchCategories, search: searchCategories,
@ -144,15 +141,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
}); });
const handleVariantAdd = () => navigate(productVariantAddUrl(id)); const handleVariantAdd = () => navigate(productVariantAddUrl(id));
const handleBulkProductVariantCreate = (
data: ProductVariantBulkCreate
) => {
if (data.productVariantBulkCreate.errors.length === 0) {
closeModal();
refetch();
}
};
const handleBulkProductVariantDelete = ( const handleBulkProductVariantDelete = (
data: ProductVariantBulkDelete data: ProductVariantBulkDelete
) => { ) => {
@ -166,7 +154,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
return ( return (
<ProductUpdateOperations <ProductUpdateOperations
product={product} product={product}
onBulkProductVariantCreate={handleBulkProductVariantCreate}
onBulkProductVariantDelete={handleBulkProductVariantDelete} onBulkProductVariantDelete={handleBulkProductVariantDelete}
onDelete={handleDelete} onDelete={handleDelete}
onImageCreate={handleImageCreate} onImageCreate={handleImageCreate}
@ -174,7 +161,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
onUpdate={handleUpdate} onUpdate={handleUpdate}
> >
{({ {({
bulkProductVariantCreate,
bulkProductVariantDelete, bulkProductVariantDelete,
createProductImage, createProductImage,
deleteProduct, deleteProduct,
@ -258,7 +244,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
onImageReorder={handleImageReorder} onImageReorder={handleImageReorder}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onVariantAdd={handleVariantAdd} onVariantAdd={handleVariantAdd}
onVariantsAdd={() => openModal("create-variants")} onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
onVariantShow={variantId => () => onVariantShow={variantId => () =>
navigate(productVariantEditUrl(product.id, variantId))} navigate(productVariantEditUrl(product.id, variantId))}
onImageUpload={handleImageUpload} onImageUpload={handleImageUpload}
@ -347,28 +333,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<ProductVariantCreateDialog
defaultPrice={maybe(() =>
data.product.basePrice.amount.toFixed(2)
)}
errors={
bulkProductVariantCreate.opts.data
?.productVariantBulkCreate.errors || []
}
open={params.action === "create-variants"}
attributes={maybe(
() => data.product.productType.variantAttributes,
[]
)}
currencySymbol={maybe(() => shop.defaultCurrency)}
onClose={closeModal}
onSubmit={inputs =>
bulkProductVariantCreate.mutate({
id,
inputs
})
}
/>
{!product?.productType?.hasVariants && ( {!product?.productType?.hasVariants && (
<ProductWarehousesDialog <ProductWarehousesDialog
confirmButtonState={addOrRemoveStocksOpts.status} confirmButtonState={addOrRemoveStocksOpts.status}

View file

@ -0,0 +1,71 @@
import React from "react";
import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { useCreateMultipleVariantsData } from "@saleor/products/queries";
import { useProductVariantBulkCreateMutation } from "@saleor/products/mutations";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import { productUrl } from "@saleor/products/urls";
import useShop from "@saleor/hooks/useShop";
import ProductVariantCreatorPage from "../../components/ProductVariantCreatorPage";
interface ProductVariantCreatorProps {
id: string;
}
const ProductVariantCreator: React.FC<ProductVariantCreatorProps> = ({
id
}) => {
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
const { data } = useCreateMultipleVariantsData({
displayLoader: true,
variables: { id }
});
const [
bulkProductVariantCreate,
bulkProductVariantCreateOpts
] = useProductVariantBulkCreateMutation({
onCompleted: data => {
if (data.productVariantBulkCreate.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Successfully created variants",
description: "success message"
})
});
navigate(productUrl(id));
}
}
});
const shop = useShop();
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Variants",
description: "window title"
})}
/>
<ProductVariantCreatorPage
defaultPrice={data?.product?.basePrice.amount.toString()}
errors={
bulkProductVariantCreateOpts.data?.productVariantBulkCreate.errors ||
[]
}
attributes={data?.product?.productType?.variantAttributes || []}
currencySymbol={shop?.defaultCurrency}
onSubmit={inputs =>
bulkProductVariantCreate({
variables: { id, inputs }
})
}
/>
</>
);
};
ProductVariantCreator.displayName = "ProductVariantCreator";
export default ProductVariantCreator;

View file

@ -0,0 +1,2 @@
export * from "./ProductVariantCreator";
export { default } from "./ProductVariantCreator";