Merge branch 'master' into feat/add-slug-to-seo-form
This commit is contained in:
commit
c7124ffc41
30 changed files with 1764 additions and 446 deletions
|
@ -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
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.22165 29.7949H0.58465C0.463483 29.7757 0.339087 29.7544 0.241002 29.6832C0.093454 29.576 0.0294675 29.3967 0 29.2102V0.584649C0.03929 0.33635 0.171052 0.104259 0.403986 0.0286256C0.461938 0.00975233 0.524452 0.00954185 0.58465 0H29.2105C29.2702 0.00954185 29.3305 0.0190837 29.3908 0.0286256C29.5592 0.114292 29.7101 0.230548 29.7662 0.403985C29.7851 0.461938 29.7851 0.524451 29.795 0.584649V6.21595L35.4394 6.20683C35.4997 6.21631 35.5601 6.22571 35.6204 6.23518C35.7888 6.32077 35.9397 6.43703 35.9965 6.61061C36.0154 6.6687 36.0154 6.73121 36.0253 6.79148V35.4147C35.986 35.6629 35.8541 35.8951 35.6211 35.9707C35.5629 35.9896 35.5004 35.9898 35.4401 35.9994L6.80637 36C6.68513 35.9808 6.56074 35.9597 6.46265 35.8884C6.3151 35.7812 6.25119 35.6018 6.22165 35.4154V29.7949ZM34.8557 30.2975H7.39102V34.8307L34.8557 34.8301V30.2975ZM28.6633 15.4452L23.063 20.8494L31.1722 28.9086C31.2424 29.0051 31.2852 29.0454 31.3133 29.1281H34.8557V21.229L28.6633 15.4452ZM8.27743 29.0783C8.28087 29.0948 8.28353 29.1114 8.28557 29.1281H29.7339L19.241 18.6996L8.27743 29.0783ZM28.6254 6.21785V1.1693H1.1693V28.6256H6.22165V6.83772C6.2408 6.71669 6.26199 6.59244 6.33307 6.49442C6.44006 6.34694 6.61918 6.28282 6.80539 6.25307L28.6254 6.21785ZM34.8557 19.6289V7.37711C25.7011 7.39192 16.5459 7.40665 7.39102 7.42146V28.3073L18.8489 17.4606C18.996 17.3592 18.9973 17.3585 19.1681 17.3064C19.228 17.3072 19.2878 17.3079 19.3475 17.3086C19.5171 17.3648 19.5185 17.3655 19.663 17.4705L22.233 20.0249L28.2507 14.2183C28.4444 14.0476 28.4507 14.0362 28.7306 14.059C28.9264 14.0927 28.9629 14.1322 29.0562 14.2117L34.8557 19.6289ZM12.9322 9.93019C14.3966 9.94408 15.7827 11.0726 16.0626 12.5321C16.2614 13.5688 15.9096 14.6949 15.1526 15.4341C14.2682 16.2976 12.862 16.5852 11.6988 16.1097C10.6719 15.6898 9.89786 14.7049 9.73691 13.6065C9.53829 12.2509 10.3114 10.8116 11.5556 10.2303C11.9847 10.0299 12.4574 9.92865 12.9322 9.93019ZM12.8982 11.0995C11.9719 11.1083 11.1014 11.8117 10.9137 12.7275C10.653 14 11.8357 15.4123 13.2853 15.1437C14.1119 14.9905 14.8064 14.274 14.929 13.437C15.0953 12.3025 14.1666 11.1191 12.938 11.0996C12.9247 11.0995 12.9115 11.0995 12.8982 11.0995Z" fill="url(#paint0_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="0" y1="0" x2="39.5989" y2="41.1706" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#13BEBB"/>
|
||||
<stop offset="1" stop-color="#3EE7CD"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M6.22165 29.7949H0.58465C0.463483 29.7757 0.339087 29.7544 0.241002 29.6832C0.093454 29.576 0.0294675 29.3967 0 29.2102V0.584649C0.03929 0.33635 0.171052 0.104259 0.403986 0.0286256C0.461938 0.00975233 0.524452 0.00954185 0.58465 0H29.2105C29.2702 0.00954185 29.3305 0.0190837 29.3908 0.0286256C29.5592 0.114292 29.7101 0.230548 29.7662 0.403985C29.7851 0.461938 29.7851 0.524451 29.795 0.584649V6.21595L35.4394 6.20683C35.4997 6.21631 35.5601 6.22571 35.6204 6.23518C35.7888 6.32077 35.9397 6.43703 35.9965 6.61061C36.0154 6.6687 36.0154 6.73121 36.0253 6.79148V35.4147C35.986 35.6629 35.8541 35.8951 35.6211 35.9707C35.5629 35.9896 35.5004 35.9898 35.4401 35.9994L6.80637 36C6.68513 35.9808 6.56074 35.9597 6.46265 35.8884C6.3151 35.7812 6.25119 35.6018 6.22165 35.4154V29.7949ZM34.8557 30.2975H7.39102V34.8307L34.8557 34.8301V30.2975ZM28.6633 15.4452L23.063 20.8494L31.1722 28.9086C31.2424 29.0051 31.2852 29.0454 31.3133 29.1281H34.8557V21.229L28.6633 15.4452ZM8.27743 29.0783C8.28087 29.0948 8.28353 29.1114 8.28557 29.1281H29.7339L19.241 18.6996L8.27743 29.0783ZM28.6254 6.21785V1.1693H1.1693V28.6256H6.22165V6.83772C6.2408 6.71669 6.26199 6.59244 6.33307 6.49442C6.44006 6.34694 6.61918 6.28282 6.80539 6.25307L28.6254 6.21785ZM34.8557 19.6289V7.37711C25.7011 7.39192 16.5459 7.40665 7.39102 7.42146V28.3073L18.8489 17.4606C18.996 17.3592 18.9973 17.3585 19.1681 17.3064C19.228 17.3072 19.2878 17.3079 19.3475 17.3086C19.5171 17.3648 19.5185 17.3655 19.663 17.4705L22.233 20.0249L28.2507 14.2183C28.4444 14.0476 28.4507 14.0362 28.7306 14.059C28.9264 14.0927 28.9629 14.1322 29.0562 14.2117L34.8557 19.6289ZM12.9322 9.93019C14.3966 9.94408 15.7827 11.0726 16.0626 12.5321C16.2614 13.5688 15.9096 14.6949 15.1526 15.4341C14.2682 16.2976 12.862 16.5852 11.6988 16.1097C10.6719 15.6898 9.89786 14.7049 9.73691 13.6065C9.53829 12.2509 10.3114 10.8116 11.5556 10.2303C11.9847 10.0299 12.4574 9.92865 12.9322 9.93019ZM12.8982 11.0995C11.9719 11.1083 11.1014 11.8117 10.9137 12.7275C10.653 14 11.8357 15.4123 13.2853 15.1437C14.1119 14.9905 14.8064 14.274 14.929 13.437C15.0953 12.3025 14.1666 11.1191 12.938 11.0996C12.9247 11.0995 12.9115 11.0995 12.8982 11.0995Z" fill="white"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<AppLayoutProps> = ({ children }) => {
|
|||
<div>
|
||||
<Container>
|
||||
<div className={classes.header}>
|
||||
{isMdUp && <div ref={appHeaderAnchor} />}
|
||||
{!isMdUp && (
|
||||
<SideBarDrawer
|
||||
menuItems={menuStructure}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={renderConfigure}
|
||||
onMenuItemClick={navigate}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
<div className={classes.userBar}>
|
||||
<ThemeSwitch
|
||||
className={classes.darkThemeSwitch}
|
||||
checked={isDark}
|
||||
onClick={toggleTheme}
|
||||
/>
|
||||
<NavigatorButton
|
||||
isMac={navigator.platform
|
||||
.toLowerCase()
|
||||
.includes("mac")}
|
||||
onClick={() => setNavigatorVisibility(true)}
|
||||
/>
|
||||
<UserChip
|
||||
onLogout={logout}
|
||||
onProfileClick={() =>
|
||||
navigate(staffMemberDetailsUrl(user.id))
|
||||
}
|
||||
user={user}
|
||||
/>
|
||||
<div
|
||||
className={classes.headerAnchor}
|
||||
ref={appHeaderAnchor}
|
||||
/>
|
||||
<div className={classes.headerToolbar}>
|
||||
{!isMdUp && (
|
||||
<SideBarDrawer
|
||||
menuItems={menuStructure}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={renderConfigure}
|
||||
onMenuItemClick={navigate}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
<div className={classes.userBar}>
|
||||
<ThemeSwitch
|
||||
className={classes.darkThemeSwitch}
|
||||
checked={isDark}
|
||||
onClick={toggleTheme}
|
||||
/>
|
||||
<NavigatorButton
|
||||
isMac={navigator.platform
|
||||
.toLowerCase()
|
||||
.includes("mac")}
|
||||
onClick={() => setNavigatorVisibility(true)}
|
||||
/>
|
||||
<UserChip
|
||||
onLogout={logout}
|
||||
onProfileClick={() =>
|
||||
navigate(staffMemberDetailsUrl(user.id))
|
||||
}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!isMdUp && <div ref={appHeaderAnchor} />}
|
||||
</Container>
|
||||
</div>
|
||||
<main className={classes.view}>
|
||||
|
|
|
@ -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<ImageUploadProps> = props => {
|
|||
iconContainerActiveClassName,
|
||||
iconContainerClassName,
|
||||
isActiveClassName,
|
||||
hideUploadIcon,
|
||||
onImageUpload
|
||||
} = props;
|
||||
|
||||
|
@ -82,24 +84,26 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
|
|||
[isActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(iconContainerClassName, {
|
||||
[iconContainerActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<input
|
||||
{...getInputProps()}
|
||||
className={classes.fileField}
|
||||
accept="image/*"
|
||||
/>
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drop here to upload"
|
||||
description="image upload"
|
||||
{!hideUploadIcon && (
|
||||
<div
|
||||
className={classNames(iconContainerClassName, {
|
||||
[iconContainerActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<input
|
||||
{...getInputProps()}
|
||||
className={classes.fileField}
|
||||
accept="image/*"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drop here to upload"
|
||||
description="image upload"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{children && children({ isDragActive })}
|
||||
</>
|
||||
|
|
|
@ -241,7 +241,10 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
|
|||
const { value } = e.target;
|
||||
if (!value) {
|
||||
onChange({
|
||||
target: { name: "availableForPurchase", value: null }
|
||||
target: {
|
||||
name: "availableForPurchase",
|
||||
value: null
|
||||
}
|
||||
});
|
||||
}
|
||||
return onChange(e);
|
||||
|
|
|
@ -83,9 +83,6 @@ const useStyles = makeStyles(
|
|||
imageUploadActive: {
|
||||
zIndex: 1
|
||||
},
|
||||
imageUploadIcon: {
|
||||
display: "none"
|
||||
},
|
||||
imageUploadIconActive: {
|
||||
display: "block"
|
||||
},
|
||||
|
@ -253,7 +250,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
className={classes.imageUpload}
|
||||
isActiveClassName={classes.imageUploadActive}
|
||||
disableClick={true}
|
||||
iconContainerClassName={classes.imageUploadIcon}
|
||||
hideUploadIcon={true}
|
||||
iconContainerActiveClassName={classes.imageUploadIconActive}
|
||||
onImageUpload={handleImageUpload}
|
||||
>
|
||||
|
|
|
@ -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<ProductUpdatePageProps> = ({
|
|||
onVariantAdd,
|
||||
onVariantsAdd,
|
||||
onVariantShow,
|
||||
onVariantReorder,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
onRowClick={onVariantShow}
|
||||
onVariantAdd={onVariantAdd}
|
||||
onVariantsAdd={onVariantsAdd}
|
||||
onVariantReorder={onVariantReorder}
|
||||
toolbar={toolbar}
|
||||
isChecked={isChecked}
|
||||
selected={selected}
|
||||
|
|
|
@ -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<ProductVariantCreatePageProps> = ({
|
||||
|
@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
weightUnit,
|
||||
onBack,
|
||||
onSubmit,
|
||||
onVariantClick
|
||||
onVariantClick,
|
||||
onVariantReorder
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const attributeInput = React.useMemo(
|
||||
|
@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
return onVariantClick(variantId);
|
||||
}
|
||||
}}
|
||||
onReorder={onVariantReorder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -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<ProductVariantNavigationProps> = 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<ProductVariantNavigationProps> = props
|
|||
})}
|
||||
/>
|
||||
<ResponsiveTable>
|
||||
<TableBody>
|
||||
{renderCollection(variants, variant => (
|
||||
<TableRow
|
||||
<SortableTableBody onSortEnd={onReorder}>
|
||||
{renderCollection(variants, (variant, variantIndex) => (
|
||||
<SortableTableRow
|
||||
hover={!!variant}
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
className={classes.link}
|
||||
index={variantIndex}
|
||||
className={classNames(classes.link, {
|
||||
[classes.tabActive]: variant && variant.id === current
|
||||
})}
|
||||
onClick={variant ? () => onRowClick(variant.id) : undefined}
|
||||
>
|
||||
<TableCellAvatar
|
||||
className={classNames({
|
||||
[classes.tabActive]: variant && variant.id === current
|
||||
})}
|
||||
thumbnail={maybe(
|
||||
() => variant.images[0].url,
|
||||
fallbackThumbnail
|
||||
|
@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
<TableCell className={classes.colName}>
|
||||
{variant ? variant.name || variant.sku : <Skeleton />}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</SortableTableRow>
|
||||
))}
|
||||
{onAdd ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
<TableCell colSpan={3}>
|
||||
<Button color="primary" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add variant"
|
||||
|
@ -102,7 +116,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
) : (
|
||||
<TableRow>
|
||||
<TableCellAvatar className={classes.tabActive} thumbnail={null} />
|
||||
<TableCell className={classes.colName}>
|
||||
<TableCell className={classes.colName} colSpan={2}>
|
||||
<FormattedMessage
|
||||
defaultMessage="New Variant"
|
||||
description="variant name"
|
||||
|
@ -110,7 +124,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</SortableTableBody>
|
||||
</ResponsiveTable>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
getAttributeInputFromVariant,
|
||||
getStockInputFromVariant
|
||||
} from "@saleor/products/utils/data";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { diff } from "fast-array-diff";
|
||||
|
@ -60,6 +61,7 @@ interface ProductVariantPageProps {
|
|||
placeholderImage?: string;
|
||||
header: string;
|
||||
warehouses: WarehouseFragment[];
|
||||
onVariantReorder: ReorderAction;
|
||||
onAdd();
|
||||
onBack();
|
||||
onDelete();
|
||||
|
@ -82,7 +84,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
onDelete,
|
||||
onImageSelect,
|
||||
onSubmit,
|
||||
onVariantClick
|
||||
onVariantClick,
|
||||
onVariantReorder
|
||||
}) => {
|
||||
const attributeInput = React.useMemo(
|
||||
() => getAttributeInputFromVariant(variant),
|
||||
|
@ -188,6 +191,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
return onVariantClick(variantId);
|
||||
}
|
||||
}}
|
||||
onReorder={onVariantReorder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -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<ProductVariantsProps> = props => {
|
||||
const {
|
||||
|
@ -186,6 +189,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
onRowClick,
|
||||
onVariantAdd,
|
||||
onVariantsAdd,
|
||||
onVariantReorder,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -266,6 +270,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
items={variants}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
dragRows
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage
|
||||
|
@ -291,8 +296,8 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(variants, variant => {
|
||||
<SortableTableBody onSortEnd={onVariantReorder}>
|
||||
{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<ProductVariantsProps> = props => {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
<SortableTableRow
|
||||
selected={isSelected}
|
||||
hover={!!variant}
|
||||
onClick={onRowClick(variant.id)}
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
index={variantIndex || 0}
|
||||
className={classes.link}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
|
@ -354,10 +360,10 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</SortableTableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</SortableTableBody>
|
||||
</ResponsiveTable>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
||||
|
|
236
src/products/types/ProductVariantReorder.ts
Normal file
236
src/products/types/ProductVariantReorder.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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<ProductUpdateProps> = ({ 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<ProductUpdateProps> = ({ id, params }) => {
|
|||
|
||||
const handleBack = () => navigate(productListUrl());
|
||||
|
||||
const product = data?.product;
|
||||
const [product, setProduct] = useStateFromProps(data?.product);
|
||||
|
||||
if (product === null) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
|
@ -225,12 +234,22 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ 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<ProductUpdateProps> = ({ id, params }) => {
|
|||
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
|
||||
onVariantShow={variantId => () =>
|
||||
navigate(productVariantEditUrl(product.id, variantId))}
|
||||
onVariantReorder={handleVariantReorder}
|
||||
onImageUpload={handleImageUpload}
|
||||
onImageEdit={handleImageEdit}
|
||||
onImageDelete={handleImageDelete}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<ProductUpdateProps> = ({
|
|||
return <NotFoundPage onBack={handleBack} />;
|
||||
}
|
||||
|
||||
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<ProductUpdateProps> = ({
|
|||
onVariantClick={variantId => {
|
||||
navigate(productVariantEditUrl(productId, variantId));
|
||||
}}
|
||||
onVariantReorder={handleVariantReorder}
|
||||
/>
|
||||
<ProductVariantDeleteDialog
|
||||
confirmButtonState={deleteVariantOpts.status}
|
||||
|
|
|
@ -17,9 +17,13 @@ import { decimal, weight } from "../../misc";
|
|||
import ProductVariantCreatePage, {
|
||||
ProductVariantCreatePageSubmitData
|
||||
} from "../components/ProductVariantCreatePage";
|
||||
import { useVariantCreateMutation } from "../mutations";
|
||||
import {
|
||||
useProductVariantReorderMutation,
|
||||
useVariantCreateMutation
|
||||
} from "../mutations";
|
||||
import { useProductVariantCreateQuery } from "../queries";
|
||||
import { productListUrl, productUrl, productVariantEditUrl } from "../urls";
|
||||
import { createVariantReorderHandler } from "./ProductUpdate/handlers";
|
||||
|
||||
interface ProductVariantCreateProps {
|
||||
productId: string;
|
||||
|
@ -69,6 +73,15 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
|||
return <NotFoundPage onBack={() => 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<ProductVariantCreateProps> = ({
|
|||
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<ProductVariantCreateProps> = ({
|
|||
onBack={handleBack}
|
||||
onSubmit={handleSubmit}
|
||||
onVariantClick={handleVariantClick}
|
||||
onVariantReorder={handleVariantReorder}
|
||||
saveButtonBarState={variantCreateResult.status}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||
|
|
|
@ -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<StaffPropertiesProps> = props => {
|
|||
{canEditAvatar && (
|
||||
<div className={classes.avatarHover}>
|
||||
<SVG src={photoIcon} />
|
||||
<Typography onClick={clickImgInput}>
|
||||
<Typography
|
||||
onClick={clickImgInput}
|
||||
className={classes.avatarActionText}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Change photo"
|
||||
description="button"
|
||||
/>
|
||||
</Typography>
|
||||
<Typography onClick={onImageDelete}>
|
||||
<Typography
|
||||
onClick={onImageDelete}
|
||||
className={classes.avatarActionText}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete photo"
|
||||
description="button"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = {
|
|||
onImageUpload: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onVariantAdd: () => undefined,
|
||||
onVariantReorder: () => undefined,
|
||||
onVariantShow: () => undefined,
|
||||
onVariantsAdd: () => undefined,
|
||||
placeholderImage,
|
||||
|
|
|
@ -23,6 +23,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -54,6 +55,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -69,6 +71,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -87,6 +90,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
|
|
@ -24,6 +24,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -41,6 +42,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -56,6 +58,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
errors={[
|
||||
{
|
||||
|
|
|
@ -548,6 +548,7 @@ export enum OrderErrorCode {
|
|||
REQUIRED = "REQUIRED",
|
||||
SHIPPING_METHOD_NOT_APPLICABLE = "SHIPPING_METHOD_NOT_APPLICABLE",
|
||||
SHIPPING_METHOD_REQUIRED = "SHIPPING_METHOD_REQUIRED",
|
||||
TAX_ERROR = "TAX_ERROR",
|
||||
UNIQUE = "UNIQUE",
|
||||
VOID_INACTIVE_PAYMENT = "VOID_INACTIVE_PAYMENT",
|
||||
ZERO_QUANTITY = "ZERO_QUANTITY",
|
||||
|
@ -905,6 +906,7 @@ export enum WebhookEventTypeEnum {
|
|||
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
|
||||
ORDER_UPDATED = "ORDER_UPDATED",
|
||||
PRODUCT_CREATED = "PRODUCT_CREATED",
|
||||
PRODUCT_UPDATED = "PRODUCT_UPDATED",
|
||||
}
|
||||
|
||||
export enum WebhookSortField {
|
||||
|
|
|
@ -68,6 +68,10 @@ const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
|||
defaultMessage: "Product created",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.PRODUCT_UPDATED]: intl.formatMessage({
|
||||
defaultMessage: "Product updated",
|
||||
description: "event"
|
||||
}),
|
||||
[WebhookEventTypeEnum.CHECKOUT_QUANTITY_CHANGED]: intl.formatMessage({
|
||||
defaultMessage: "Changed quantity in checkout",
|
||||
description: "event"
|
||||
|
|
Loading…
Reference in a new issue