Allow product variant to be set as default (#721)

* Add drag-and-drop to allow variants reordering

* Handle variants reordering

* Allow product variant to be set as default

* Display default variant and perform refresing after set

* Move ProductVariantSetDefault to separate component

* Changes to new schema


Co-authored-by: Dawid Tarasiuk <tarasiukdawid@gmail.com>
This commit is contained in:
Tomasz Szymański 2020-09-25 14:33:01 +02:00 committed by GitHub
parent 4da205bb24
commit 4cecf238d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 868 additions and 7 deletions

View file

@ -43,6 +43,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Fix return to previous page on screen size change - #710 by @orzechdev
- Add variants reordering possibility - #716 by @orzechdev
- Fix avatar change button - #719 by @orzechdev
- Allow product variant to be set as default - #721 by @tomaszszymanski129
- Change plural form of "informations" to "information" strings across the app #722 by @mmarkusik
- Fix misaligned rich text draft controls - #725 by @orzechdev

View file

@ -2733,6 +2733,9 @@
"src_dot_home_dot_components_dot_HomeActivityCard_dot_placed": {
"string": "Order #{orderId} was placed"
},
"src_dot_hooks_dot_3382262667": {
"string": "Variant {name} has been set as default."
},
"src_dot_lastName": {
"string": "Last Name"
},
@ -4521,6 +4524,10 @@
"src_dot_products_dot_components_dot_ProductVariantPrice_dot_819659341": {
"string": "Cost price"
},
"src_dot_products_dot_components_dot_ProductVariantSetDefault_dot_3683859003": {
"context": "set variant as default, button",
"string": "Set as default"
},
"src_dot_products_dot_components_dot_ProductVariants_dot_1001303107": {
"context": "product variant inventory",
"string": "Unavailable in all locations"
@ -4529,6 +4536,10 @@
"context": "product variant inventory",
"string": "Unavailable"
},
"src_dot_products_dot_components_dot_ProductVariants_dot_1120495519": {
"context": "default variant label",
"string": "default"
},
"src_dot_products_dot_components_dot_ProductVariants_dot_1134347598": {
"context": "product variant price",
"string": "Price"

View file

@ -2666,6 +2666,7 @@ type Mutation {
productVariantStocksDelete(variantId: ID!, warehouseIds: [ID!]): ProductVariantStocksDelete
productVariantStocksUpdate(stocks: [StockInput!]!, variantId: ID!): ProductVariantStocksUpdate
productVariantUpdate(id: ID!, input: ProductVariantInput!): ProductVariantUpdate
productVariantSetDefault(productId: ID!, variantId: ID!): ProductVariantSetDefault
productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate
productVariantUpdateMetadata(id: ID!, input: MetaInput!): ProductVariantUpdateMeta @deprecated(reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31.")
productVariantClearMetadata(id: ID!, input: MetaPath!): ProductVariantClearMeta @deprecated(reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31.")
@ -3641,6 +3642,7 @@ type Product implements Node & ObjectWithMetadata {
weight: Weight
availableForPurchase: Date
visibleInListings: Boolean!
defaultVariant: ProductVariant
privateMetadata: [MetadataItem]!
metadata: [MetadataItem]!
privateMeta: [MetaStore]! @deprecated(reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31.")
@ -4182,6 +4184,12 @@ type ProductVariantReorder {
productErrors: [ProductError!]!
}
type ProductVariantSetDefault {
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
product: Product
productErrors: [ProductError!]!
}
type ProductVariantStocksCreate {
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
productVariant: ProductVariant

View file

@ -114,6 +114,9 @@ export const productFragmentDetails = gql`
descriptionJson
seoTitle
seoDescription
defaultVariant {
id
}
category {
id
name

View file

@ -105,6 +105,11 @@ export interface Product_privateMetadata {
value: string;
}
export interface Product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface Product_category {
__typename: "Category";
id: string;
@ -198,6 +203,7 @@ export interface Product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: Product_defaultVariant | null;
category: Product_category | null;
collections: (Product_collections | null)[] | null;
margin: Product_margin | null;

View file

@ -0,0 +1,55 @@
import useNotifier from "@saleor/hooks/useNotifier";
import { useProductVariantSetDefaultMutation } from "@saleor/products/mutations";
import { getProductErrorMessage } from "@saleor/utils/errors";
import { useIntl } from "react-intl";
import { ProductDetails_product_variants } from "../products/types/ProductDetails";
import { VariantUpdate_productVariantUpdate_productVariant } from "../products/types/VariantUpdate";
function useOnSetDefaultVariant(
productId: string,
variant:
| ProductDetails_product_variants
| VariantUpdate_productVariantUpdate_productVariant
) {
const notify = useNotifier();
const intl = useIntl();
const [productVariantSetDefault] = useProductVariantSetDefaultMutation({
onCompleted: data => {
const errors = data.productVariantSetDefault.errors;
if (errors.length) {
errors.map(error =>
notify({
status: "error",
text: getProductErrorMessage(error, intl)
})
);
} else {
if (variant) {
notify({
status: "success",
text: intl.formatMessage(
{
defaultMessage: "Variant {name} has been set as default."
},
{ name: variant.name }
)
});
}
}
}
});
const onSetDefaultVariant = (selectedVariant = null) => {
productVariantSetDefault({
variables: {
productId,
variantId: variant ? variant.id : selectedVariant.id
}
});
};
return onSetDefaultVariant;
}
export default useOnSetDefaultVariant;

View file

@ -83,6 +83,7 @@ export interface ProductUpdatePageProps extends ListActions {
onSeoClick?();
onSubmit?(data: ProductUpdatePageSubmitData);
onVariantAdd?();
onSetDefaultVariant();
}
export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData {
@ -120,6 +121,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onSubmit,
onVariantAdd,
onVariantsAdd,
onSetDefaultVariant,
onVariantShow,
onVariantReorder,
isChecked,
@ -296,6 +298,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
<ProductVariants
disabled={disabled}
variants={variants}
product={product}
fallbackPrice={
product?.variants?.length
? product.variants[0].price
@ -305,6 +308,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onVariantAdd={onVariantAdd}
onVariantsAdd={onVariantsAdd}
onVariantReorder={onVariantReorder}
onSetDefaultVariant={onSetDefaultVariant}
toolbar={toolbar}
isChecked={isChecked}
selected={selected}

View file

@ -35,6 +35,7 @@ import ProductVariantImages from "../ProductVariantImages";
import ProductVariantImageSelectDialog from "../ProductVariantImageSelectDialog";
import ProductVariantNavigation from "../ProductVariantNavigation";
import ProductVariantPrice from "../ProductVariantPrice";
import ProductVariantSetDefault from "../ProductVariantSetDefault";
export interface ProductVariantPageFormData extends MetadataFormData {
costPrice: string;
@ -68,6 +69,7 @@ interface ProductVariantPageProps {
onSubmit(data: ProductVariantPageSubmitData);
onImageSelect(id: string);
onVariantClick(variantId: string);
onSetDefaultVariant();
}
const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
@ -85,7 +87,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onImageSelect,
onSubmit,
onVariantClick,
onVariantReorder
onVariantReorder,
onSetDefaultVariant
}) => {
const attributeInput = React.useMemo(
() => getAttributeInputFromVariant(variant),
@ -165,7 +168,9 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
<AppHeader onBack={onBack}>
{maybe(() => variant.product.name)}
</AppHeader>
<PageHeader title={header} />
<PageHeader title={header}>
<ProductVariantSetDefault onSetDefaultVariant={onSetDefaultVariant} />
</PageHeader>
<Form initial={initialForm} onSubmit={handleSubmit} confirmLeave>
{({ change, data, hasChanged, submit, triggerChange }) => {
const handleAttributeChange: FormsetChange = (id, value) => {

View file

@ -0,0 +1,32 @@
import CardMenu from "@saleor/components/CardMenu";
import React from "react";
import { useIntl } from "react-intl";
interface ProductVariantSetDefaultProps {
onSetDefaultVariant: () => void;
}
const ProductVariantSetDefault: React.FC<ProductVariantSetDefaultProps> = ({
onSetDefaultVariant
}) => {
const intl = useIntl();
return (
<CardMenu
menuItems={[
{
label: intl.formatMessage({
defaultMessage: "Set as default",
description: "set variant as default, button"
}),
onSelect: onSetDefaultVariant,
testId: "setDefault"
}
]}
data-test="menu"
/>
);
};
ProductVariantSetDefault.displayName = "ProductVariantSetDefault";
export default ProductVariantSetDefault;

View file

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

View file

@ -3,6 +3,7 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Hidden from "@material-ui/core/Hidden";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import TableCell from "@material-ui/core/TableCell";
import Typography from "@material-ui/core/Typography";
import CardTitle from "@saleor/components/CardTitle";
@ -24,9 +25,11 @@ import { FormattedMessage, IntlShape, useIntl } from "react-intl";
import { maybe, renderCollection } from "../../../misc";
import { ListActions, ReorderAction } from "../../../types";
import {
ProductDetails_product,
ProductDetails_product_variants,
ProductDetails_product_variants_stocks_warehouse
} from "../../types/ProductDetails";
import ProductVariantSetDefault from "../ProductVariantSetDefault";
function getWarehouseChoices(
variants: ProductDetails_product_variants[],
@ -66,8 +69,11 @@ function getWarehouseChoices(
const useStyles = makeStyles(
theme => ({
[theme.breakpoints.up("lg")]: {
colActions: {
width: 30
},
colInventory: {
width: 300
width: 270
},
colName: {},
colPrice: {
@ -86,10 +92,15 @@ const useStyles = makeStyles(
},
colSku: {},
colStatus: {},
defaultVariant: {
color: fade(theme.palette.text.secondary, 0.6),
display: "block"
},
denseTable: {
"& td, & th": {
paddingRight: theme.spacing(3)
}
},
width: "auto"
},
link: {
cursor: "pointer"
@ -171,10 +182,12 @@ function getAvailabilityLabel(
interface ProductVariantsProps extends ListActions {
disabled: boolean;
product: ProductDetails_product;
variants: ProductDetails_product_variants[];
fallbackPrice?: ProductVariant_costPrice;
onVariantReorder: ReorderAction;
onRowClick: (id: string) => () => void;
onSetDefaultVariant(variant: ProductDetails_product_variants);
onVariantAdd?();
onVariantsAdd?();
}
@ -185,11 +198,13 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
const {
disabled,
variants,
product,
fallbackPrice,
onRowClick,
onVariantAdd,
onVariantsAdd,
onVariantReorder,
onSetDefaultVariant,
isChecked,
selected,
toggle,
@ -295,10 +310,13 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
description="product variant inventory status"
/>
</TableCell>
<TableCell className={classes.colActions}></TableCell>
</TableHead>
<SortableTableBody onSortEnd={onVariantReorder}>
{renderCollection(variants, (variant, variantIndex) => {
const isSelected = variant ? isChecked(variant.id) : false;
const isDefault =
variant && product?.defaultVariant?.id === variant?.id;
const numAvailable =
variant && variant.stocks
? variant.stocks.reduce(
@ -326,6 +344,14 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
</TableCell>
<TableCell className={classes.colName} data-test="name">
{variant ? variant.name || variant.sku : <Skeleton />}
{isDefault && (
<span className={classes.defaultVariant}>
{intl.formatMessage({
defaultMessage: "default",
description: "default variant label"
})}
</span>
)}
</TableCell>
<TableCell className={classes.colSku} data-test="sku">
{variant ? variant.sku : <Skeleton />}
@ -360,6 +386,15 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
)
)}
</TableCell>
<TableCell
className={classes.colActions}
data-test="actions"
onClick={e => e.stopPropagation()}
>
<ProductVariantSetDefault
onSetDefaultVariant={() => onSetDefaultVariant(variant)}
/>
</TableCell>
</SortableTableRow>
);
})}

View file

@ -115,6 +115,7 @@ export const product: (
name: "Winter sale"
}
],
defaultVariant: { __typename: "ProductVariant", id: "pv75934" },
descriptionJson: JSON.stringify(content),
id: "p10171",
images: [

View file

@ -57,6 +57,10 @@ import {
ProductVariantReorder,
ProductVariantReorderVariables
} from "./types/ProductVariantReorder";
import {
ProductVariantSetDefault,
ProductVariantSetDefaultVariables
} from "./types/ProductVariantSetDefault";
import {
SimpleProductUpdate,
SimpleProductUpdateVariables
@ -134,6 +138,26 @@ export const useProductImagesReorder = makeMutation<
ProductImageReorderVariables
>(productImagesReorder);
const productVariantSetDefault = gql`
${productErrorFragment}
${productFragmentDetails}
mutation ProductVariantSetDefault($productId: ID!, $variantId: ID!) {
productVariantSetDefault(productId: $productId, variantId: $variantId) {
errors: productErrors {
...ProductErrorFragment
}
product {
...Product
}
}
}
`;
export const useProductVariantSetDefaultMutation = makeMutation<
ProductVariantSetDefault,
ProductVariantSetDefaultVariables
>(productVariantSetDefault);
export const productUpdateMutation = gql`
${productErrorFragment}
${productFragmentDetails}

View file

@ -111,6 +111,11 @@ export interface ProductCreate_productCreate_product_privateMetadata {
value: string;
}
export interface ProductCreate_productCreate_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductCreate_productCreate_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface ProductCreate_productCreate_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductCreate_productCreate_product_defaultVariant | null;
category: ProductCreate_productCreate_product_category | null;
collections: (ProductCreate_productCreate_product_collections | null)[] | null;
margin: ProductCreate_productCreate_product_margin | null;

View file

@ -105,6 +105,11 @@ export interface ProductDetails_product_privateMetadata {
value: string;
}
export interface ProductDetails_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductDetails_product_category {
__typename: "Category";
id: string;
@ -198,6 +203,7 @@ export interface ProductDetails_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductDetails_product_defaultVariant | null;
category: ProductDetails_product_category | null;
collections: (ProductDetails_product_collections | null)[] | null;
margin: ProductDetails_product_margin | null;

View file

@ -111,6 +111,11 @@ export interface ProductImageCreate_productImageCreate_product_privateMetadata {
value: string;
}
export interface ProductImageCreate_productImageCreate_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductImageCreate_productImageCreate_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface ProductImageCreate_productImageCreate_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductImageCreate_productImageCreate_product_defaultVariant | null;
category: ProductImageCreate_productImageCreate_product_category | null;
collections: (ProductImageCreate_productImageCreate_product_collections | null)[] | null;
margin: ProductImageCreate_productImageCreate_product_margin | null;

View file

@ -111,6 +111,11 @@ export interface ProductImageUpdate_productImageUpdate_product_privateMetadata {
value: string;
}
export interface ProductImageUpdate_productImageUpdate_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductImageUpdate_productImageUpdate_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface ProductImageUpdate_productImageUpdate_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductImageUpdate_productImageUpdate_product_defaultVariant | null;
category: ProductImageUpdate_productImageUpdate_product_category | null;
collections: (ProductImageUpdate_productImageUpdate_product_collections | null)[] | null;
margin: ProductImageUpdate_productImageUpdate_product_margin | null;

View file

@ -111,6 +111,11 @@ export interface ProductUpdate_productUpdate_product_privateMetadata {
value: string;
}
export interface ProductUpdate_productUpdate_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductUpdate_productUpdate_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface ProductUpdate_productUpdate_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductUpdate_productUpdate_product_defaultVariant | null;
category: ProductUpdate_productUpdate_product_category | null;
collections: (ProductUpdate_productUpdate_product_collections | null)[] | null;
margin: ProductUpdate_productUpdate_product_margin | null;

View file

@ -111,6 +111,11 @@ export interface ProductVariantReorder_productVariantReorder_product_privateMeta
value: string;
}
export interface ProductVariantReorder_productVariantReorder_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductVariantReorder_productVariantReorder_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface ProductVariantReorder_productVariantReorder_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductVariantReorder_productVariantReorder_product_defaultVariant | null;
category: ProductVariantReorder_productVariantReorder_product_category | null;
collections: (ProductVariantReorder_productVariantReorder_product_collections | null)[] | null;
margin: ProductVariantReorder_productVariantReorder_product_margin | null;

View file

@ -0,0 +1,242 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { ProductErrorCode, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: ProductVariantSetDefault
// ====================================================
export interface ProductVariantSetDefault_productVariantSetDefault_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_attributes_attribute_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_attributes_attribute {
__typename: "Attribute";
id: string;
slug: string | null;
name: string | null;
inputType: AttributeInputTypeEnum | null;
valueRequired: boolean;
values: (ProductVariantSetDefault_productVariantSetDefault_product_attributes_attribute_values | null)[] | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_attributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_attributes {
__typename: "SelectedAttribute";
attribute: ProductVariantSetDefault_productVariantSetDefault_product_attributes_attribute;
values: (ProductVariantSetDefault_productVariantSetDefault_product_attributes_values | null)[];
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes_values {
__typename: "AttributeValue";
id: string;
name: string | null;
slug: string | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes {
__typename: "Attribute";
id: string;
name: string | null;
values: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes_values | null)[] | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType {
__typename: "ProductType";
id: string;
variantAttributes: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes | null)[] | null;
name: string;
hasVariants: boolean;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start_gross {
__typename: "Money";
amount: number;
currency: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start {
__typename: "TaxedMoney";
gross: ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start_gross;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_stop_gross {
__typename: "Money";
amount: number;
currency: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_stop {
__typename: "TaxedMoney";
gross: ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_stop_gross;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted {
__typename: "TaxedMoneyRange";
start: ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start | null;
stop: ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_stop | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing {
__typename: "ProductPricingInfo";
priceRangeUndiscounted: ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_category {
__typename: "Category";
id: string;
name: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_collections {
__typename: "Collection";
id: string;
name: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_margin {
__typename: "Margin";
start: number | null;
stop: number | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost_start {
__typename: "Money";
amount: number;
currency: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost_stop {
__typename: "Money";
amount: number;
currency: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost {
__typename: "MoneyRange";
start: ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost_start | null;
stop: ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost_stop | null;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_images {
__typename: "ProductImage";
id: string;
alt: string;
sortOrder: number | null;
url: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_variants_price {
__typename: "Money";
amount: number;
currency: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_variants_stocks_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_variants_stocks {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
warehouse: ProductVariantSetDefault_productVariantSetDefault_product_variants_stocks_warehouse;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_variants {
__typename: "ProductVariant";
id: string;
sku: string;
name: string;
price: ProductVariantSetDefault_productVariantSetDefault_product_variants_price | null;
margin: number | null;
stocks: (ProductVariantSetDefault_productVariantSetDefault_product_variants_stocks | null)[] | null;
trackInventory: boolean;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product_weight {
__typename: "Weight";
unit: WeightUnitsEnum;
value: number;
}
export interface ProductVariantSetDefault_productVariantSetDefault_product {
__typename: "Product";
id: string;
attributes: ProductVariantSetDefault_productVariantSetDefault_product_attributes[];
productType: ProductVariantSetDefault_productVariantSetDefault_product_productType;
pricing: ProductVariantSetDefault_productVariantSetDefault_product_pricing | null;
metadata: (ProductVariantSetDefault_productVariantSetDefault_product_metadata | null)[];
privateMetadata: (ProductVariantSetDefault_productVariantSetDefault_product_privateMetadata | null)[];
name: string;
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: ProductVariantSetDefault_productVariantSetDefault_product_defaultVariant | null;
category: ProductVariantSetDefault_productVariantSetDefault_product_category | null;
collections: (ProductVariantSetDefault_productVariantSetDefault_product_collections | null)[] | null;
margin: ProductVariantSetDefault_productVariantSetDefault_product_margin | null;
purchaseCost: ProductVariantSetDefault_productVariantSetDefault_product_purchaseCost | null;
isAvailableForPurchase: boolean | null;
isAvailable: boolean | null;
isPublished: boolean;
chargeTaxes: boolean;
publicationDate: any | null;
images: (ProductVariantSetDefault_productVariantSetDefault_product_images | null)[] | null;
variants: (ProductVariantSetDefault_productVariantSetDefault_product_variants | null)[] | null;
weight: ProductVariantSetDefault_productVariantSetDefault_product_weight | null;
availableForPurchase: any | null;
visibleInListings: boolean;
}
export interface ProductVariantSetDefault_productVariantSetDefault {
__typename: "ProductVariantSetDefault";
errors: ProductVariantSetDefault_productVariantSetDefault_errors[];
product: ProductVariantSetDefault_productVariantSetDefault_product | null;
}
export interface ProductVariantSetDefault {
productVariantSetDefault: ProductVariantSetDefault_productVariantSetDefault | null;
}
export interface ProductVariantSetDefaultVariables {
productId: string;
variantId: string;
}

View file

@ -111,6 +111,11 @@ export interface SimpleProductUpdate_productUpdate_product_privateMetadata {
value: string;
}
export interface SimpleProductUpdate_productUpdate_product_defaultVariant {
__typename: "ProductVariant";
id: string;
}
export interface SimpleProductUpdate_productUpdate_product_category {
__typename: "Category";
id: string;
@ -204,6 +209,7 @@ export interface SimpleProductUpdate_productUpdate_product {
descriptionJson: any;
seoTitle: string | null;
seoDescription: string | null;
defaultVariant: SimpleProductUpdate_productUpdate_product_defaultVariant | null;
category: SimpleProductUpdate_productUpdate_product_category | null;
collections: (SimpleProductUpdate_productUpdate_product_collections | null)[] | null;
margin: SimpleProductUpdate_productUpdate_product_margin | null;

View file

@ -9,6 +9,7 @@ import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useOnSetDefaultVariant from "@saleor/hooks/useOnSetDefaultVariant";
import useShop from "@saleor/hooks/useShop";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { commonMessages } from "@saleor/intl";
@ -272,6 +273,10 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
...maybe(() => updateProductOpts.data.productUpdate.errors, []),
...maybe(() => updateSimpleProductOpts.data.productUpdate.errors, [])
];
const onSetDefaultVariant = useOnSetDefaultVariant(
product ? product.id : null,
null
);
return (
<>
@ -281,6 +286,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
collections={collections}
defaultWeightUnit={shop?.defaultWeightUnit}
disabled={disableFormSave}
onSetDefaultVariant={onSetDefaultVariant}
errors={errors}
fetchCategories={searchCategories}
fetchCollections={searchCollections}

View file

@ -3,6 +3,7 @@ import NotFoundPage from "@saleor/components/NotFoundPage";
import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useOnSetDefaultVariant from "@saleor/hooks/useOnSetDefaultVariant";
import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
@ -127,6 +128,8 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
reorderProductVariantsOpts
] = useProductVariantReorderMutation({});
const onSetDefaultVariant = useOnSetDefaultVariant(productId, variant);
const handleVariantReorder = createVariantReorderHandler(
variant?.product,
variables => reorderProductVariants({ variables })
@ -199,6 +202,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
<ProductVariantPage
defaultWeightUnit={shop?.defaultWeightUnit}
errors={errors}
onSetDefaultVariant={onSetDefaultVariant}
saveButtonBarState={updateVariantOpts.status}
loading={disableFormSave}
placeholderImage={placeholderImg}

View file

@ -144884,6 +144884,10 @@ Ctrl + K"
>
Inventory
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id ProductVariants-colActions-id"
scope="col"
/>
</tr>
</thead>
<tbody
@ -144968,6 +144972,11 @@ Ctrl + K"
data-test="name"
>
Cordoba Oro
<span
class="ProductVariants-defaultVariant-id"
>
default
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colSku-id"
@ -144981,6 +144990,38 @@ Ctrl + K"
>
3 available at 2 locations
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
<tr
class="MuiTableRow-root-id ProductVariants-link-id MuiTableRow-hover-id"
@ -145074,6 +145115,38 @@ Ctrl + K"
>
11 available at 1 location
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
@ -146926,6 +146999,10 @@ Ctrl + K"
>
Inventory
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id ProductVariants-colActions-id"
scope="col"
/>
</tr>
</thead>
<tbody
@ -147010,6 +147087,11 @@ Ctrl + K"
data-test="name"
>
Cordoba Oro
<span
class="ProductVariants-defaultVariant-id"
>
default
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colSku-id"
@ -147023,6 +147105,38 @@ Ctrl + K"
>
3 available at 2 locations
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
<tr
class="MuiTableRow-root-id ProductVariants-link-id MuiTableRow-hover-id"
@ -147116,6 +147230,38 @@ Ctrl + K"
>
11 available at 1 location
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
@ -154092,6 +154238,10 @@ Ctrl + K"
>
Inventory
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id ProductVariants-colActions-id"
scope="col"
/>
</tr>
</thead>
<tbody
@ -154176,6 +154326,11 @@ Ctrl + K"
data-test="name"
>
Cordoba Oro
<span
class="ProductVariants-defaultVariant-id"
>
default
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colSku-id"
@ -154189,6 +154344,38 @@ Ctrl + K"
>
3 available at 2 locations
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
<tr
class="MuiTableRow-root-id ProductVariants-link-id MuiTableRow-hover-id"
@ -154282,6 +154469,38 @@ Ctrl + K"
>
11 available at 1 location
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
@ -157644,6 +157863,10 @@ Ctrl + K"
>
Inventory
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id ProductVariants-colActions-id"
scope="col"
/>
</tr>
</thead>
<tbody
@ -157728,6 +157951,11 @@ Ctrl + K"
data-test="name"
>
Cordoba Oro
<span
class="ProductVariants-defaultVariant-id"
>
default
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colSku-id"
@ -157741,6 +157969,38 @@ Ctrl + K"
>
3 available at 2 locations
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
<tr
class="MuiTableRow-root-id ProductVariants-link-id MuiTableRow-hover-id"
@ -157834,6 +158094,38 @@ Ctrl + K"
>
11 available at 1 location
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id ProductVariants-colActions-id"
data-test="actions"
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
@ -167837,7 +168129,35 @@ exports[`Storyshots Views / Products / Product variant details attribute errors
>
<div
class="PageHeader-root-id"
/>
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
<form>
@ -169271,7 +169591,35 @@ exports[`Storyshots Views / Products / Product variant details when loaded data
>
<div
class="PageHeader-root-id"
/>
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
<form>
@ -170692,7 +171040,35 @@ exports[`Storyshots Views / Products / Product variant details when loading data
>
<div
class="PageHeader-root-id"
/>
>
<div
data-test="menu"
>
<button
aria-haspopup="true"
aria-label="More"
class="MuiButtonBase-root-id MuiIconButton-root-id CardMenu-iconButton-id MuiIconButton-colorPrimary-id"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
<form>

View file

@ -32,6 +32,7 @@ const props: ProductUpdatePageProps = {
onDelete: () => undefined,
onImageDelete: () => undefined,
onImageUpload: () => undefined,
onSetDefaultVariant: () => undefined,
onSubmit: () => undefined,
onVariantAdd: () => undefined,
onVariantReorder: () => undefined,

View file

@ -21,6 +21,7 @@ storiesOf("Views / Products / Product variant details", module)
onAdd={() => undefined}
onBack={() => undefined}
onDelete={undefined}
onSetDefaultVariant={() => undefined}
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}
@ -39,6 +40,7 @@ storiesOf("Views / Products / Product variant details", module)
placeholderImage={placeholderImage}
onAdd={() => undefined}
onDelete={undefined}
onSetDefaultVariant={() => undefined}
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}
@ -55,6 +57,7 @@ storiesOf("Views / Products / Product variant details", module)
onAdd={() => undefined}
onBack={() => undefined}
onDelete={undefined}
onSetDefaultVariant={() => undefined}
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}