From 4cecf238d6a04f11eb9a9fd6376cf10f81c035b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Szyma=C5=84ski?= Date: Fri, 25 Sep 2020 14:33:01 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + locale/defaultMessages.json | 11 + schema.graphql | 8 + src/fragments/products.ts | 3 + src/fragments/types/Product.ts | 6 + src/hooks/useOnSetDefaultVariant.ts | 55 +++ .../ProductUpdatePage/ProductUpdatePage.tsx | 4 + .../ProductVariantPage/ProductVariantPage.tsx | 9 +- .../ProductVariantSetDefault.tsx | 32 ++ .../ProductVariantSetDefault/index.ts | 2 + .../ProductVariants/ProductVariants.tsx | 39 +- src/products/fixtures.ts | 1 + src/products/mutations.ts | 24 ++ src/products/types/ProductCreate.ts | 6 + src/products/types/ProductDetails.ts | 6 + src/products/types/ProductImageCreate.ts | 6 + src/products/types/ProductImageUpdate.ts | 6 + src/products/types/ProductUpdate.ts | 6 + src/products/types/ProductVariantReorder.ts | 6 + .../types/ProductVariantSetDefault.ts | 242 +++++++++++ src/products/types/SimpleProductUpdate.ts | 6 + .../views/ProductUpdate/ProductUpdate.tsx | 6 + src/products/views/ProductVariant.tsx | 4 + .../__snapshots__/Stories.test.ts.snap | 382 +++++++++++++++++- .../stories/products/ProductUpdatePage.tsx | 1 + .../stories/products/ProductVariantPage.tsx | 3 + 26 files changed, 868 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useOnSetDefaultVariant.ts create mode 100644 src/products/components/ProductVariantSetDefault/ProductVariantSetDefault.tsx create mode 100644 src/products/components/ProductVariantSetDefault/index.ts create mode 100644 src/products/types/ProductVariantSetDefault.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2baa06eda..b4fbc234b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 95db1b070..6a8bf9090 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -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" diff --git a/schema.graphql b/schema.graphql index abe05671b..bb01cb372 100644 --- a/schema.graphql +++ b/schema.graphql @@ -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 diff --git a/src/fragments/products.ts b/src/fragments/products.ts index 1b651dfdd..45724da0c 100644 --- a/src/fragments/products.ts +++ b/src/fragments/products.ts @@ -114,6 +114,9 @@ export const productFragmentDetails = gql` descriptionJson seoTitle seoDescription + defaultVariant { + id + } category { id name diff --git a/src/fragments/types/Product.ts b/src/fragments/types/Product.ts index 6921ecc67..69c7c3488 100644 --- a/src/fragments/types/Product.ts +++ b/src/fragments/types/Product.ts @@ -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; diff --git a/src/hooks/useOnSetDefaultVariant.ts b/src/hooks/useOnSetDefaultVariant.ts new file mode 100644 index 000000000..d11e16894 --- /dev/null +++ b/src/hooks/useOnSetDefaultVariant.ts @@ -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; diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index ad8f27270..0cf4ebb04 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -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 = ({ onSubmit, onVariantAdd, onVariantsAdd, + onSetDefaultVariant, onVariantShow, onVariantReorder, isChecked, @@ -296,6 +298,7 @@ export const ProductUpdatePage: React.FC = ({ = ({ onVariantAdd={onVariantAdd} onVariantsAdd={onVariantsAdd} onVariantReorder={onVariantReorder} + onSetDefaultVariant={onSetDefaultVariant} toolbar={toolbar} isChecked={isChecked} selected={selected} diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index eecb0227d..4d7643521 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -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 = ({ @@ -85,7 +87,8 @@ const ProductVariantPage: React.FC = ({ onImageSelect, onSubmit, onVariantClick, - onVariantReorder + onVariantReorder, + onSetDefaultVariant }) => { const attributeInput = React.useMemo( () => getAttributeInputFromVariant(variant), @@ -165,7 +168,9 @@ const ProductVariantPage: React.FC = ({ {maybe(() => variant.product.name)} - + + +
{({ change, data, hasChanged, submit, triggerChange }) => { const handleAttributeChange: FormsetChange = (id, value) => { diff --git a/src/products/components/ProductVariantSetDefault/ProductVariantSetDefault.tsx b/src/products/components/ProductVariantSetDefault/ProductVariantSetDefault.tsx new file mode 100644 index 000000000..3cd48ee09 --- /dev/null +++ b/src/products/components/ProductVariantSetDefault/ProductVariantSetDefault.tsx @@ -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 = ({ + onSetDefaultVariant +}) => { + const intl = useIntl(); + + return ( + + ); +}; + +ProductVariantSetDefault.displayName = "ProductVariantSetDefault"; +export default ProductVariantSetDefault; diff --git a/src/products/components/ProductVariantSetDefault/index.ts b/src/products/components/ProductVariantSetDefault/index.ts new file mode 100644 index 000000000..2e3a6d961 --- /dev/null +++ b/src/products/components/ProductVariantSetDefault/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ProductVariantSetDefault"; +export * from "./ProductVariantSetDefault"; diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index 90c798629..3c16477a5 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -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 = props => { const { disabled, variants, + product, fallbackPrice, onRowClick, onVariantAdd, onVariantsAdd, onVariantReorder, + onSetDefaultVariant, isChecked, selected, toggle, @@ -295,10 +310,13 @@ export const ProductVariants: React.FC = props => { description="product variant inventory status" /> + {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 = props => { {variant ? variant.name || variant.sku : } + {isDefault && ( + + {intl.formatMessage({ + defaultMessage: "default", + description: "default variant label" + })} + + )} {variant ? variant.sku : } @@ -360,6 +386,15 @@ export const ProductVariants: React.FC = props => { ) )} + e.stopPropagation()} + > + onSetDefaultVariant(variant)} + /> + ); })} diff --git a/src/products/fixtures.ts b/src/products/fixtures.ts index 5432485a6..801fe4d22 100644 --- a/src/products/fixtures.ts +++ b/src/products/fixtures.ts @@ -115,6 +115,7 @@ export const product: ( name: "Winter sale" } ], + defaultVariant: { __typename: "ProductVariant", id: "pv75934" }, descriptionJson: JSON.stringify(content), id: "p10171", images: [ diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 56f86ce2f..0e9eb086c 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -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} diff --git a/src/products/types/ProductCreate.ts b/src/products/types/ProductCreate.ts index 68c154d91..7d5340b98 100644 --- a/src/products/types/ProductCreate.ts +++ b/src/products/types/ProductCreate.ts @@ -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; diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index 06e579bae..b4c2acbb5 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -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; diff --git a/src/products/types/ProductImageCreate.ts b/src/products/types/ProductImageCreate.ts index 9c472c556..f92d20c0d 100644 --- a/src/products/types/ProductImageCreate.ts +++ b/src/products/types/ProductImageCreate.ts @@ -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; diff --git a/src/products/types/ProductImageUpdate.ts b/src/products/types/ProductImageUpdate.ts index e85899725..5362628f4 100644 --- a/src/products/types/ProductImageUpdate.ts +++ b/src/products/types/ProductImageUpdate.ts @@ -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; diff --git a/src/products/types/ProductUpdate.ts b/src/products/types/ProductUpdate.ts index f03604480..5a40d3bc8 100644 --- a/src/products/types/ProductUpdate.ts +++ b/src/products/types/ProductUpdate.ts @@ -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; diff --git a/src/products/types/ProductVariantReorder.ts b/src/products/types/ProductVariantReorder.ts index 8c5f1741f..4173f109d 100644 --- a/src/products/types/ProductVariantReorder.ts +++ b/src/products/types/ProductVariantReorder.ts @@ -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; diff --git a/src/products/types/ProductVariantSetDefault.ts b/src/products/types/ProductVariantSetDefault.ts new file mode 100644 index 000000000..2fb7dcb8a --- /dev/null +++ b/src/products/types/ProductVariantSetDefault.ts @@ -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; +} diff --git a/src/products/types/SimpleProductUpdate.ts b/src/products/types/SimpleProductUpdate.ts index e023f09b1..930925389 100644 --- a/src/products/types/SimpleProductUpdate.ts +++ b/src/products/types/SimpleProductUpdate.ts @@ -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; diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index 8aeef201c..83c12371c 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -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 = ({ 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 = ({ id, params }) => { collections={collections} defaultWeightUnit={shop?.defaultWeightUnit} disabled={disableFormSave} + onSetDefaultVariant={onSetDefaultVariant} errors={errors} fetchCategories={searchCategories} fetchCollections={searchCollections} diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index d090a2f70..845a92246 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -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 = ({ reorderProductVariantsOpts ] = useProductVariantReorderMutation({}); + const onSetDefaultVariant = useOnSetDefaultVariant(productId, variant); + const handleVariantReorder = createVariantReorderHandler( variant?.product, variables => reorderProductVariants({ variables }) @@ -199,6 +202,7 @@ export const ProductVariant: React.FC = ({ Inventory + Cordoba Oro + + default + 3 available at 2 locations + +
+ +
+ 11 available at 1 location + +
+ +
+ @@ -146926,6 +146999,10 @@ Ctrl + K" > Inventory + Cordoba Oro + + default + 3 available at 2 locations + +
+ +
+ 11 available at 1 location + +
+ +
+ @@ -154092,6 +154238,10 @@ Ctrl + K" > Inventory + Cordoba Oro + + default + 3 available at 2 locations + +
+ +
+ 11 available at 1 location + +
+ +
+ @@ -157644,6 +157863,10 @@ Ctrl + K" > Inventory + Cordoba Oro + + default + 3 available at 2 locations + +
+ +
+ 11 available at 1 location + +
+ +
+ @@ -167837,7 +168129,35 @@ exports[`Storyshots Views / Products / Product variant details attribute errors >
+ > +
+ +
+
@@ -169271,7 +169591,35 @@ exports[`Storyshots Views / Products / Product variant details when loaded data >
+ > +
+ +
+
@@ -170692,7 +171040,35 @@ exports[`Storyshots Views / Products / Product variant details when loading data >
+ > +
+ +
+
diff --git a/src/storybook/stories/products/ProductUpdatePage.tsx b/src/storybook/stories/products/ProductUpdatePage.tsx index 7682f8369..4e8ce5ba6 100644 --- a/src/storybook/stories/products/ProductUpdatePage.tsx +++ b/src/storybook/stories/products/ProductUpdatePage.tsx @@ -32,6 +32,7 @@ const props: ProductUpdatePageProps = { onDelete: () => undefined, onImageDelete: () => undefined, onImageUpload: () => undefined, + onSetDefaultVariant: () => undefined, onSubmit: () => undefined, onVariantAdd: () => undefined, onVariantReorder: () => undefined, diff --git a/src/storybook/stories/products/ProductVariantPage.tsx b/src/storybook/stories/products/ProductVariantPage.tsx index cca477a73..038c03372 100644 --- a/src/storybook/stories/products/ProductVariantPage.tsx +++ b/src/storybook/stories/products/ProductVariantPage.tsx @@ -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}