diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fceb0db..2c06a97a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,9 @@ All notable, unreleased changes to this project will be documented in this file. - Update schema with PositiveDecimal type - #695 by @AlicjaSzu - Restyle side menu - #697 by @dominik-zeglen - Add error info when fetching taxes - #701 by @dominik-zeglen +- 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 - Add slug field to product, collection, category & page details (update and create) - #720 by @mmarkusik ## 2.10.1 diff --git a/assets/images/photo-icon.svg b/assets/images/photo-icon.svg index 57ee59835..d9662bedc 100644 --- a/assets/images/photo-icon.svg +++ b/assets/images/photo-icon.svg @@ -1,9 +1,3 @@ - - - - - - - + diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 5ce46df6b..1745f22fd 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5890,6 +5890,10 @@ "context": "webhook events", "string": "Expand or restrict webhooks permissions to register certain events in Saleor system." }, + "src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3316426878": { + "context": "event", + "string": "Product updated" + }, "src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3345061702": { "context": "event", "string": "Order fully paid" diff --git a/schema.graphql b/schema.graphql index e2953571b..abe05671b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2640,6 +2640,7 @@ type Mutation { productClearPrivateMetadata(id: ID!, input: MetaPath!): ProductClearPrivateMeta @deprecated(reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31.") productSetAvailabilityForPurchase(isAvailable: Boolean!, productId: ID!, startDate: Date): ProductSetAvailabilityForPurchase productImageCreate(input: ProductImageCreateInput!): ProductImageCreate + productVariantReorder(moves: [ReorderInput]!, productId: ID!): ProductVariantReorder productImageDelete(id: ID!): ProductImageDelete productImageBulkDelete(ids: [ID]!): ProductImageBulkDelete productImageReorder(imagesIds: [ID]!, productId: ID!): ProductImageReorder @@ -2999,6 +3000,7 @@ enum OrderErrorCode { REQUIRED SHIPPING_METHOD_NOT_APPLICABLE SHIPPING_METHOD_REQUIRED + TAX_ERROR UNIQUE VOID_INACTIVE_PAYMENT ZERO_QUANTITY @@ -4174,6 +4176,12 @@ input ProductVariantInput { weight: WeightScalar } +type ProductVariantReorder { + 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 @@ -5600,6 +5608,7 @@ enum WebhookEventTypeEnum { INVOICE_SENT CUSTOMER_CREATED PRODUCT_CREATED + PRODUCT_UPDATED CHECKOUT_QUANTITY_CHANGED CHECKOUT_CREATED CHECKOUT_UPDATED @@ -5622,6 +5631,7 @@ enum WebhookSampleEventTypeEnum { INVOICE_SENT CUSTOMER_CREATED PRODUCT_CREATED + PRODUCT_UPDATED CHECKOUT_QUANTITY_CHANGED CHECKOUT_CREATED CHECKOUT_UPDATED diff --git a/src/components/AppHeader/AppHeader.tsx b/src/components/AppHeader/AppHeader.tsx index 7f85264dd..d38731269 100644 --- a/src/components/AppHeader/AppHeader.tsx +++ b/src/components/AppHeader/AppHeader.tsx @@ -34,7 +34,7 @@ const useStyles = makeStyles( marginTop: theme.spacing(0.5), transition: theme.transitions.duration.standard + "ms", [theme.breakpoints.down("sm")]: { - margin: theme.spacing(4, 0, 3, 0) + margin: theme.spacing(4, 0, 0, 0) } }, skeleton: { diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 891ef3f09..703a33a2c 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -56,12 +56,24 @@ const useStyles = makeStyles( marginRight: theme.spacing(2) }, header: { + display: "grid", + gridTemplateAreas: `"headerAnchor headerToolbar"`, + [theme.breakpoints.down("sm")]: { + gridTemplateAreas: `"headerToolbar" + "headerAnchor"` + }, + marginBottom: theme.spacing(3) + }, + headerAnchor: { + gridArea: "headerAnchor" + }, + headerToolbar: { + display: "flex", + gridArea: "headerToolbar", + height: 40, [theme.breakpoints.down("sm")]: { height: "auto" - }, - display: "flex", - height: 40, - marginBottom: theme.spacing(3) + } }, root: { @@ -162,39 +174,43 @@ const AppLayout: React.FC = ({ children }) => {
- {isMdUp &&
} - {!isMdUp && ( - - )} -
-
- - setNavigatorVisibility(true)} - /> - - navigate(staffMemberDetailsUrl(user.id)) - } - user={user} - /> +
+
+ {!isMdUp && ( + + )} +
+
+ + setNavigatorVisibility(true)} + /> + + navigate(staffMemberDetailsUrl(user.id)) + } + user={user} + /> +
- {!isMdUp &&
}
diff --git a/src/components/ImageUpload/ImageUpload.tsx b/src/components/ImageUpload/ImageUpload.tsx index f0b1d18a2..65530d2fe 100644 --- a/src/components/ImageUpload/ImageUpload.tsx +++ b/src/components/ImageUpload/ImageUpload.tsx @@ -15,6 +15,7 @@ interface ImageUploadProps { isActiveClassName?: string; iconContainerClassName?: string; iconContainerActiveClassName?: string; + hideUploadIcon?: boolean; onImageUpload: (file: FileList) => void; } @@ -66,6 +67,7 @@ export const ImageUpload: React.FC = props => { iconContainerActiveClassName, iconContainerClassName, isActiveClassName, + hideUploadIcon, onImageUpload } = props; @@ -82,24 +84,26 @@ export const ImageUpload: React.FC = props => { [isActiveClassName]: isDragActive })} > -
- - - - + - -
+ + + + +
+ )}
{children && children({ isDragActive })} diff --git a/src/components/VisibilityCard/VisibilityCard.tsx b/src/components/VisibilityCard/VisibilityCard.tsx index 4207baff5..a97754e83 100644 --- a/src/components/VisibilityCard/VisibilityCard.tsx +++ b/src/components/VisibilityCard/VisibilityCard.tsx @@ -241,7 +241,10 @@ export const VisibilityCard: React.FC = props => { const { value } = e.target; if (!value) { onChange({ - target: { name: "availableForPurchase", value: null } + target: { + name: "availableForPurchase", + value: null + } }); } return onChange(e); diff --git a/src/products/components/ProductImages/ProductImages.tsx b/src/products/components/ProductImages/ProductImages.tsx index bc3c750ca..2890342cd 100644 --- a/src/products/components/ProductImages/ProductImages.tsx +++ b/src/products/components/ProductImages/ProductImages.tsx @@ -83,9 +83,6 @@ const useStyles = makeStyles( imageUploadActive: { zIndex: 1 }, - imageUploadIcon: { - display: "none" - }, imageUploadIconActive: { display: "block" }, @@ -253,7 +250,7 @@ const ProductImages: React.FC = props => { className={classes.imageUpload} isActiveClassName={classes.imageUploadActive} disableClick={true} - iconContainerClassName={classes.imageUploadIcon} + hideUploadIcon={true} iconContainerActiveClassName={classes.imageUploadIconActive} onImageUpload={handleImageUpload} > diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index 3c5cb9ded..83d574663 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -18,7 +18,7 @@ import { sectionNames } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; -import { FetchMoreProps, ListActions } from "@saleor/types"; +import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; @@ -73,6 +73,7 @@ export interface ProductUpdatePageProps extends ListActions { fetchCollections: (query: string) => void; onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; + onVariantReorder: ReorderAction; onImageDelete: (id: string) => () => void; onBack?(); onDelete(); @@ -120,6 +121,7 @@ export const ProductUpdatePage: React.FC = ({ onVariantAdd, onVariantsAdd, onVariantShow, + onVariantReorder, isChecked, selected, toggle, @@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC = ({ onRowClick={onVariantShow} onVariantAdd={onVariantAdd} onVariantsAdd={onVariantsAdd} + onVariantReorder={onVariantReorder} toolbar={toolbar} isChecked={isChecked} selected={selected} diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index ac79fad42..1237d8b98 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -14,6 +14,7 @@ import useFormset, { } from "@saleor/hooks/useFormset"; import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import { ReorderAction } from "@saleor/types"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; import React from "react"; import { useIntl } from "react-intl"; @@ -56,6 +57,7 @@ interface ProductVariantCreatePageProps { onBack: () => void; onSubmit: (data: ProductVariantCreatePageSubmitData) => void; onVariantClick: (variantId: string) => void; + onVariantReorder: ReorderAction; } const ProductVariantCreatePage: React.FC = ({ @@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC = ({ weightUnit, onBack, onSubmit, - onVariantClick + onVariantClick, + onVariantReorder }) => { const intl = useIntl(); const attributeInput = React.useMemo( @@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC = ({ return onVariantClick(variantId); } }} + onReorder={onVariantReorder} />
diff --git a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx index 89f0e7d55..39ac8e6b2 100644 --- a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx +++ b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx @@ -1,13 +1,17 @@ import Button from "@material-ui/core/Button"; import Card from "@material-ui/core/Card"; import { makeStyles } from "@material-ui/core/styles"; -import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; import TableRow from "@material-ui/core/TableRow"; import CardTitle from "@saleor/components/CardTitle"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; +import { + SortableTableBody, + SortableTableRow +} from "@saleor/components/SortableTable"; import TableCellAvatar from "@saleor/components/TableCellAvatar"; +import { ReorderAction } from "@saleor/types"; import classNames from "classnames"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -26,16 +30,18 @@ const useStyles = makeStyles( cursor: "pointer" }, tabActive: { - "&:before": { - background: theme.palette.primary.main, - content: '""', - height: "100%", - left: 0, - position: "absolute", - top: 0, - width: 2 - }, - position: "relative" + "& > td:first-child": { + "&:before": { + background: theme.palette.primary.main, + content: '""', + height: "100%", + left: 0, + position: "absolute", + top: 0, + width: 2 + }, + position: "relative" + } } }), { name: "ProductVariantNavigation" } @@ -49,10 +55,18 @@ interface ProductVariantNavigationProps { | ProductVariantCreateData_product_variants[]; onAdd?: () => void; onRowClick: (variantId: string) => void; + onReorder: ReorderAction; } const ProductVariantNavigation: React.FC = props => { - const { current, fallbackThumbnail, variants, onAdd, onRowClick } = props; + const { + current, + fallbackThumbnail, + variants, + onAdd, + onRowClick, + onReorder + } = props; const classes = useStyles(props); const intl = useIntl(); @@ -66,18 +80,18 @@ const ProductVariantNavigation: React.FC = props })} /> - - {renderCollection(variants, variant => ( - + {renderCollection(variants, (variant, variantIndex) => ( + onRowClick(variant.id) : undefined} > variant.images[0].url, fallbackThumbnail @@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC = props {variant ? variant.name || variant.sku : } - + ))} {onAdd ? ( - +
diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index 86ee81aa4..90c798629 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -3,9 +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 TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; -import TableRow from "@material-ui/core/TableRow"; import Typography from "@material-ui/core/Typography"; import CardTitle from "@saleor/components/CardTitle"; import Checkbox from "@saleor/components/Checkbox"; @@ -14,13 +12,17 @@ import Money from "@saleor/components/Money"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import Skeleton from "@saleor/components/Skeleton"; +import { + SortableTableBody, + SortableTableRow +} from "@saleor/components/SortableTable"; import TableHead from "@saleor/components/TableHead"; import { ProductVariant_costPrice } from "@saleor/fragments/types/ProductVariant"; import React from "react"; import { FormattedMessage, IntlShape, useIntl } from "react-intl"; import { maybe, renderCollection } from "../../../misc"; -import { ListActions } from "../../../types"; +import { ListActions, ReorderAction } from "../../../types"; import { ProductDetails_product_variants, ProductDetails_product_variants_stocks_warehouse @@ -171,12 +173,13 @@ interface ProductVariantsProps extends ListActions { disabled: boolean; variants: ProductDetails_product_variants[]; fallbackPrice?: ProductVariant_costPrice; + onVariantReorder: ReorderAction; onRowClick: (id: string) => () => void; onVariantAdd?(); onVariantsAdd?(); } -const numberOfColumns = 5; +const numberOfColumns = 6; export const ProductVariants: React.FC = props => { const { @@ -186,6 +189,7 @@ export const ProductVariants: React.FC = props => { onRowClick, onVariantAdd, onVariantsAdd, + onVariantReorder, isChecked, selected, toggle, @@ -266,6 +270,7 @@ export const ProductVariants: React.FC = props => { items={variants} toggleAll={toggleAll} toolbar={toolbar} + dragRows > = props => { /> - - {renderCollection(variants, variant => { + + {renderCollection(variants, (variant, variantIndex) => { const isSelected = variant ? isChecked(variant.id) : false; const numAvailable = variant && variant.stocks @@ -303,11 +308,12 @@ export const ProductVariants: React.FC = props => { : null; return ( - @@ -354,10 +360,10 @@ export const ProductVariants: React.FC = props => { ) )} - + ); })} - + )} diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 822613639..d90a7af80 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -53,6 +53,10 @@ import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables } from "./types/ProductVariantBulkDelete"; +import { + ProductVariantReorder, + ProductVariantReorderVariables +} from "./types/ProductVariantReorder"; import { SimpleProductUpdate, SimpleProductUpdateVariables @@ -615,6 +619,11 @@ const productSetAvailabilityForPurchase = gql` productId: $productId startDate: $startDate ) { + product { + id + availableForPurchase + isAvailableForPurchase + } errors: productErrors { ...ProductErrorFragment message @@ -627,3 +636,22 @@ export const useProductSetAvailabilityForPurchase = makeMutation< ProductSetAvailabilityForPurchase, ProductSetAvailabilityForPurchaseVariables >(productSetAvailabilityForPurchase); + +const productVariantReorder = gql` + ${productErrorFragment} + ${productFragmentDetails} + mutation ProductVariantReorder($move: ReorderInput!, $productId: ID!) { + productVariantReorder(moves: [$move], productId: $productId) { + errors: productErrors { + ...ProductErrorFragment + } + product { + ...Product + } + } + } +`; +export const useProductVariantReorderMutation = makeMutation< + ProductVariantReorder, + ProductVariantReorderVariables +>(productVariantReorder); diff --git a/src/products/types/ProductSetAvailabilityForPurchase.ts b/src/products/types/ProductSetAvailabilityForPurchase.ts index cc8cb6557..8a5fdeebb 100644 --- a/src/products/types/ProductSetAvailabilityForPurchase.ts +++ b/src/products/types/ProductSetAvailabilityForPurchase.ts @@ -8,6 +8,13 @@ import { ProductErrorCode } from "./../../types/globalTypes"; // GraphQL mutation operation: ProductSetAvailabilityForPurchase // ==================================================== +export interface ProductSetAvailabilityForPurchase_productSetAvailabilityForPurchase_product { + __typename: "Product"; + id: string; + availableForPurchase: any | null; + isAvailableForPurchase: boolean | null; +} + export interface ProductSetAvailabilityForPurchase_productSetAvailabilityForPurchase_errors { __typename: "ProductError"; code: ProductErrorCode; @@ -17,6 +24,7 @@ export interface ProductSetAvailabilityForPurchase_productSetAvailabilityForPurc export interface ProductSetAvailabilityForPurchase_productSetAvailabilityForPurchase { __typename: "ProductSetAvailabilityForPurchase"; + product: ProductSetAvailabilityForPurchase_productSetAvailabilityForPurchase_product | null; errors: ProductSetAvailabilityForPurchase_productSetAvailabilityForPurchase_errors[]; } diff --git a/src/products/types/ProductVariantReorder.ts b/src/products/types/ProductVariantReorder.ts new file mode 100644 index 000000000..8c5f1741f --- /dev/null +++ b/src/products/types/ProductVariantReorder.ts @@ -0,0 +1,236 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ReorderInput, ProductErrorCode, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ProductVariantReorder +// ==================================================== + +export interface ProductVariantReorder_productVariantReorder_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_attribute_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_attribute { + __typename: "Attribute"; + id: string; + slug: string | null; + name: string | null; + inputType: AttributeInputTypeEnum | null; + valueRequired: boolean; + values: (ProductVariantReorder_productVariantReorder_product_attributes_attribute_values | null)[] | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes { + __typename: "SelectedAttribute"; + attribute: ProductVariantReorder_productVariantReorder_product_attributes_attribute; + values: (ProductVariantReorder_productVariantReorder_product_attributes_values | null)[]; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType_variantAttributes_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType_variantAttributes { + __typename: "Attribute"; + id: string; + name: string | null; + values: (ProductVariantReorder_productVariantReorder_product_productType_variantAttributes_values | null)[] | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType { + __typename: "ProductType"; + id: string; + variantAttributes: (ProductVariantReorder_productVariantReorder_product_productType_variantAttributes | null)[] | null; + name: string; + hasVariants: boolean; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start_gross { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start { + __typename: "TaxedMoney"; + gross: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start_gross; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop_gross { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop { + __typename: "TaxedMoney"; + gross: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop_gross; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted { + __typename: "TaxedMoneyRange"; + start: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start | null; + stop: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing { + __typename: "ProductPricingInfo"; + priceRangeUndiscounted: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_metadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_privateMetadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_category { + __typename: "Category"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_collections { + __typename: "Collection"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_margin { + __typename: "Margin"; + start: number | null; + stop: number | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost_start { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost_stop { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost { + __typename: "MoneyRange"; + start: ProductVariantReorder_productVariantReorder_product_purchaseCost_start | null; + stop: ProductVariantReorder_productVariantReorder_product_purchaseCost_stop | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_images { + __typename: "ProductImage"; + id: string; + alt: string; + sortOrder: number | null; + url: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_price { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_stocks_warehouse { + __typename: "Warehouse"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_stocks { + __typename: "Stock"; + id: string; + quantity: number; + quantityAllocated: number; + warehouse: ProductVariantReorder_productVariantReorder_product_variants_stocks_warehouse; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants { + __typename: "ProductVariant"; + id: string; + sku: string; + name: string; + price: ProductVariantReorder_productVariantReorder_product_variants_price | null; + margin: number | null; + stocks: (ProductVariantReorder_productVariantReorder_product_variants_stocks | null)[] | null; + trackInventory: boolean; +} + +export interface ProductVariantReorder_productVariantReorder_product_weight { + __typename: "Weight"; + unit: WeightUnitsEnum; + value: number; +} + +export interface ProductVariantReorder_productVariantReorder_product { + __typename: "Product"; + id: string; + attributes: ProductVariantReorder_productVariantReorder_product_attributes[]; + productType: ProductVariantReorder_productVariantReorder_product_productType; + pricing: ProductVariantReorder_productVariantReorder_product_pricing | null; + metadata: (ProductVariantReorder_productVariantReorder_product_metadata | null)[]; + privateMetadata: (ProductVariantReorder_productVariantReorder_product_privateMetadata | null)[]; + name: string; + descriptionJson: any; + seoTitle: string | null; + seoDescription: string | null; + category: ProductVariantReorder_productVariantReorder_product_category | null; + collections: (ProductVariantReorder_productVariantReorder_product_collections | null)[] | null; + margin: ProductVariantReorder_productVariantReorder_product_margin | null; + purchaseCost: ProductVariantReorder_productVariantReorder_product_purchaseCost | null; + isAvailableForPurchase: boolean | null; + isAvailable: boolean | null; + isPublished: boolean; + chargeTaxes: boolean; + publicationDate: any | null; + images: (ProductVariantReorder_productVariantReorder_product_images | null)[] | null; + variants: (ProductVariantReorder_productVariantReorder_product_variants | null)[] | null; + weight: ProductVariantReorder_productVariantReorder_product_weight | null; + availableForPurchase: any | null; + visibleInListings: boolean; +} + +export interface ProductVariantReorder_productVariantReorder { + __typename: "ProductVariantReorder"; + errors: ProductVariantReorder_productVariantReorder_errors[]; + product: ProductVariantReorder_productVariantReorder_product | null; +} + +export interface ProductVariantReorder { + productVariantReorder: ProductVariantReorder_productVariantReorder | null; +} + +export interface ProductVariantReorderVariables { + move: ReorderInput; + productId: string; +} diff --git a/src/products/utils/handlers.ts b/src/products/utils/handlers.ts index cba4b3018..e6ae599b5 100644 --- a/src/products/utils/handlers.ts +++ b/src/products/utils/handlers.ts @@ -116,3 +116,30 @@ export function createProductTypeSelectHandler( ); }; } + +interface ProductAvailabilityArgs { + availableForPurchase: string | null; + isAvailableForPurchase: boolean; + productId: string; +} + +export function getProductAvailabilityVariables({ + isAvailableForPurchase, + availableForPurchase, + productId +}: ProductAvailabilityArgs) { + const isAvailable = + availableForPurchase && !isAvailableForPurchase + ? true + : isAvailableForPurchase; + + return { + isAvailable, + productId, + startDate: isAvailableForPurchase + ? null + : availableForPurchase !== "" + ? availableForPurchase + : null + }; +} diff --git a/src/products/views/ProductCreate.tsx b/src/products/views/ProductCreate.tsx index 2ed4172ef..30627ab5f 100644 --- a/src/products/views/ProductCreate.tsx +++ b/src/products/views/ProductCreate.tsx @@ -3,6 +3,7 @@ import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; +import { getProductAvailabilityVariables } from "@saleor/products/utils/handlers"; import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useProductTypeSearch from "@saleor/searches/useProductTypeSearch"; @@ -19,7 +20,10 @@ import { decimal, weight } from "../../misc"; import ProductCreatePage, { ProductCreatePageSubmitData } from "../components/ProductCreatePage"; -import { useProductCreateMutation } from "../mutations"; +import { + useProductCreateMutation, + useProductSetAvailabilityForPurchase +} from "../mutations"; import { productListUrl, productUrl } from "../urls"; export const ProductCreateView: React.FC = () => { @@ -59,6 +63,18 @@ export const ProductCreateView: React.FC = () => { const handleBack = () => navigate(productListUrl()); + const [ + setProductAvailability, + productAvailabilityOpts + ] = useProductSetAvailabilityForPurchase({ + onCompleted: data => { + const errors = data?.productSetAvailabilityForPurchase?.errors; + if (errors?.length === 0) { + navigate(productUrl(data.productSetAvailabilityForPurchase.product.id)); + } + } + }); + const [productCreate, productCreateOpts] = useProductCreateMutation({ onCompleted: data => { if (data.productCreate.errors.length === 0) { @@ -68,7 +84,6 @@ export const ProductCreateView: React.FC = () => { defaultMessage: "Product created" }) }); - navigate(productUrl(data.productCreate.product.id)); } } }); @@ -101,11 +116,28 @@ export const ProductCreateView: React.FC = () => { warehouse: stock.id })), trackInventory: formData.trackInventory, + visibleInListings: formData.visibleInListings, weight: weight(formData.weight) } }); - return result.data.productCreate?.product?.id || null; + const productId = result.data.productCreate?.product?.id; + + if (productId) { + const { isAvailableForPurchase, availableForPurchase } = formData; + + const variables = getProductAvailabilityVariables({ + availableForPurchase, + isAvailableForPurchase, + productId + }); + + setProductAvailability({ + variables + }); + } + + return productId || null; }; const handleSubmit = createMetadataCreateHandler( handleCreate, @@ -129,7 +161,7 @@ export const ProductCreateView: React.FC = () => { collections={(searchCollectionOpts.data?.search.edges || []).map( edge => edge.node )} - disabled={productCreateOpts.loading} + disabled={productCreateOpts.loading || productAvailabilityOpts.loading} errors={productCreateOpts.data?.productCreate.errors || []} fetchCategories={searchCategory} fetchCollections={searchCollection} diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index d3915ceab..8aeef201c 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -10,6 +10,7 @@ import useBulkActions from "@saleor/hooks/useBulkActions"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; import { commonMessages } from "@saleor/intl"; import { useProductDeleteMutation, @@ -19,6 +20,7 @@ import { useProductSetAvailabilityForPurchase, useProductUpdateMutation, useProductVariantBulkDeleteMutation, + useProductVariantReorderMutation, useSimpleProductUpdateMutation } from "@saleor/products/mutations"; import useCategorySearch from "@saleor/searches/useCategorySearch"; @@ -51,7 +53,8 @@ import { import { createImageReorderHandler, createImageUploadHandler, - createUpdateHandler + createUpdateHandler, + createVariantReorderHandler } from "./handlers"; interface ProductUpdateProps { @@ -177,6 +180,12 @@ export const ProductUpdate: React.FC = ({ id, params }) => { onCompleted: data => { const errors = data?.productSetAvailabilityForPurchase?.errors; if (errors?.length === 0) { + const updatedProduct = data?.productSetAvailabilityForPurchase?.product; + setProduct(product => ({ + ...product, + availableForPurchase: updatedProduct.availableForPurchase, + isAvailableForPurchase: updatedProduct.isAvailableForPurchase + })); notify({ status: "success", text: intl.formatMessage({ @@ -195,7 +204,7 @@ export const ProductUpdate: React.FC = ({ id, params }) => { const handleBack = () => navigate(productListUrl()); - const product = data?.product; + const [product, setProduct] = useStateFromProps(data?.product); if (product === null) { return ; @@ -225,12 +234,22 @@ export const ProductUpdate: React.FC = ({ id, params }) => { reorderProductImages({ variables }) ); + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler(product, variables => + reorderProductVariants({ variables }) + ); + const disableFormSave = createProductImageOpts.loading || deleteProductOpts.loading || reorderProductImagesOpts.loading || updateProductOpts.loading || productAvailabilityOpts.loading || + reorderProductVariantsOpts.loading || loading; const formTransitionState = getMutationState( @@ -282,6 +301,7 @@ export const ProductUpdate: React.FC = ({ id, params }) => { onVariantsAdd={() => navigate(productVariantCreatorUrl(id))} onVariantShow={variantId => () => navigate(productVariantEditUrl(product.id, variantId))} + onVariantReorder={handleVariantReorder} onImageUpload={handleImageUpload} onImageEdit={handleImageEdit} onImageDelete={handleImageDelete} diff --git a/src/products/views/ProductUpdate/handlers.ts b/src/products/views/ProductUpdate/handlers.ts index b64e657b2..2c0756e0c 100644 --- a/src/products/views/ProductUpdate/handlers.ts +++ b/src/products/views/ProductUpdate/handlers.ts @@ -14,11 +14,15 @@ import { ProductUpdate, ProductUpdateVariables } from "@saleor/products/types/ProductUpdate"; +import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData"; +import { ProductVariantDetails_productVariant_product } from "@saleor/products/types/ProductVariantDetails"; +import { ProductVariantReorderVariables } from "@saleor/products/types/ProductVariantReorder"; import { SimpleProductUpdate, SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate"; import { mapFormsetStockToStockInput } from "@saleor/products/utils/data"; +import { getProductAvailabilityVariables } from "@saleor/products/utils/handlers"; import { ReorderEvent } from "@saleor/types"; import { MutationFetchResult } from "react-apollo"; import { arrayMove } from "react-sortable-hoc"; @@ -92,20 +96,13 @@ export function createUpdateHandler( isAvailableForPurchase !== product.isAvailableForPurchase || availableForPurchase !== product.availableForPurchase ) { - const isAvailable = - availableForPurchase && !isAvailableForPurchase - ? true - : isAvailableForPurchase; - - const availabilityResult = await setProductAvailability({ - isAvailable, - productId: product.id, - startDate: isAvailableForPurchase - ? null - : availableForPurchase !== "" - ? availableForPurchase - : null + const variables = getProductAvailabilityVariables({ + availableForPurchase, + isAvailableForPurchase, + productId: product.id }); + + const availabilityResult = await setProductAvailability(variables); errors = [ ...errors, ...availabilityResult.data.productSetAvailabilityForPurchase.errors @@ -141,3 +138,21 @@ export function createImageReorderHandler( }); }; } + +export function createVariantReorderHandler( + product: + | ProductDetails_product + | ProductVariantDetails_productVariant_product + | ProductVariantCreateData_product, + reorderProductVariants: (variables: ProductVariantReorderVariables) => void +) { + return ({ newIndex, oldIndex }: ReorderEvent) => { + reorderProductVariants({ + move: { + id: product.variants[oldIndex].id, + sortOrder: newIndex - oldIndex + }, + productId: product.id + }); + }; +} diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index bce7b327b..d090a2f70 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -21,6 +21,7 @@ import ProductVariantPage, { ProductVariantPageSubmitData } from "../components/ProductVariantPage"; import { + useProductVariantReorderMutation, useVariantDeleteMutation, useVariantImageAssignMutation, useVariantImageUnassignMutation, @@ -36,6 +37,7 @@ import { ProductVariantEditUrlQueryParams } from "../urls"; import { mapFormsetStockToStockInput } from "../utils/data"; +import { createVariantReorderHandler } from "./ProductUpdate/handlers"; interface ProductUpdateProps { variantId: string; @@ -120,12 +122,23 @@ export const ProductVariant: React.FC = ({ return ; } + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler( + variant?.product, + variables => reorderProductVariants({ variables }) + ); + const disableFormSave = loading || deleteVariantOpts.loading || updateVariantOpts.loading || assignImageOpts.loading || - unassignImageOpts.loading; + unassignImageOpts.loading || + reorderProductVariantsOpts.loading; const handleImageSelect = (id: string) => () => { if (variant) { @@ -202,6 +215,7 @@ export const ProductVariant: React.FC = ({ onVariantClick={variantId => { navigate(productVariantEditUrl(productId, variantId)); }} + onVariantReorder={handleVariantReorder} /> = ({ return navigate(productListUrl())} />; } + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler(product, variables => + reorderProductVariants({ variables }) + ); + const handleBack = () => navigate(productUrl(productId)); const handleCreate = async (formData: ProductVariantCreatePageSubmitData) => { const result = await variantCreate({ @@ -104,7 +117,10 @@ export const ProductVariant: React.FC = ({ const handleVariantClick = (id: string) => navigate(productVariantEditUrl(productId, id)); - const disableForm = productLoading || variantCreateResult.loading; + const disableForm = + productLoading || + variantCreateResult.loading || + reorderProductVariantsOpts.loading; return ( <> @@ -126,6 +142,7 @@ export const ProductVariant: React.FC = ({ onBack={handleBack} onSubmit={handleSubmit} onVariantClick={handleVariantClick} + onVariantReorder={handleVariantReorder} saveButtonBarState={variantCreateResult.status} warehouses={ warehouses.data?.warehouses.edges.map(edge => edge.node) || [] diff --git a/src/staff/components/StaffProperties/StaffProperties.tsx b/src/staff/components/StaffProperties/StaffProperties.tsx index 1a2b10f63..3cdc2f628 100644 --- a/src/staff/components/StaffProperties/StaffProperties.tsx +++ b/src/staff/components/StaffProperties/StaffProperties.tsx @@ -34,6 +34,14 @@ const useStyles = makeStyles( position: "relative", width: 120 }, + avatarActionText: { + "&:hover": { + textDecoration: "underline" + }, + color: "#fff", + cursor: "pointer", + fontSize: 12 + }, avatarDefault: { "& div": { color: "#fff", @@ -47,17 +55,10 @@ const useStyles = makeStyles( width: 120 }, avatarHover: { - "& p": { - "&:hover": { - textDecoration: "underline" - }, - color: theme.palette.primary.main, - cursor: "pointer", - fontSize: 12, - fontWeight: 500 - }, background: "#00000080", borderRadius: "100%", + fontSize: 12, + fontWeight: 500, height: 120, opacity: 0, padding: theme.spacing(2.5, 0), @@ -155,13 +156,19 @@ const StaffProperties: React.FC = props => { {canEditAvatar && (
- + - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
New Variant @@ -140676,6 +140975,43 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` + + + @@ -140704,6 +141040,43 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` + + + @@ -140779,6 +141152,7 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` New Variant @@ -141496,6 +141870,43 @@ exports[`Storyshots Views / Products / Create product variant when loading data + + + @@ -141582,6 +141993,7 @@ exports[`Storyshots Views / Products / Create product variant when loading data New Variant @@ -142244,6 +142656,43 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = + + + @@ -142272,6 +142721,43 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = + + + @@ -142347,6 +142833,7 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = New Variant @@ -143697,56 +144184,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -144188,7 +144626,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -144304,6 +144783,43 @@ Ctrl + K" + + + @@ -145969,56 +146485,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -146201,7 +146668,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -146317,6 +146825,43 @@ Ctrl + K" + + + @@ -147971,56 +148516,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -150391,56 +150887,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -152945,56 +153392,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -153436,7 +153834,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -153552,6 +153991,43 @@ Ctrl + K" + + + @@ -156910,7 +157386,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -157026,6 +157543,43 @@ Ctrl + K" + + + @@ -158680,56 +159234,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -167173,10 +167678,47 @@ exports[`Storyshots Views / Products / Product variant details attribute errors class="MuiTableBody-root-id" > + + +
+ + + @@ -167231,6 +167810,43 @@ exports[`Storyshots Views / Products / Product variant details attribute errors + + + @@ -167259,6 +167875,43 @@ exports[`Storyshots Views / Products / Product variant details attribute errors + + + @@ -167289,7 +167942,7 @@ exports[`Storyshots Views / Products / Product variant details attribute errors >