Merge branch 'master' into lush-collection-load-more

This commit is contained in:
Dominik Żegleń 2020-10-30 15:38:37 +01:00 committed by GitHub
commit e39f24cc0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1102 additions and 526 deletions

View file

@ -54,6 +54,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Fix order draft back button redirect - #753 by @orzechdev - Fix order draft back button redirect - #753 by @orzechdev
- Add manage product types and attributes permission - #768 by @orzechdev - Add manage product types and attributes permission - #768 by @orzechdev
- Fix isPublished and isAvailable behaviour for products, collections and pages - #780 by @mmarkusik - Fix isPublished and isAvailable behaviour for products, collections and pages - #780 by @mmarkusik
- Add metadata editor to page views - #782 by @dominik-zeglen
- Add missing infinite scroll to searches - #793 by @dominik-zeglen - Add missing infinite scroll to searches - #793 by @dominik-zeglen
## 2.10.1 ## 2.10.1

View file

@ -2901,39 +2901,6 @@
"context": "button", "context": "button",
"string": "Add products" "string": "Add products"
}, },
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_115822814": {
"string": "No billing address"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_1161061962": {
"context": "dialog header",
"string": "Finalize Draft Order"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_1297434244": {
"string": "No user information"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_1472924390": {
"string": "There are missing or incorrect informations about this order:"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_2430492151": {
"string": "No shipping address"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_2725265632": {
"context": "button",
"string": "Finalize"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_2824936338": {
"string": "Shipping method provided, but no product requires it"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_2968256006": {
"string": "Some products require shipping, but no method provided"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_3358029330": {
"string": "Are you sure you want to finalize draft #{orderNumber}?"
},
"src_dot_orders_dot_components_dot_OrderDraftFinalizeDialog_dot_678764806": {
"context": "button",
"string": "Finalize anyway"
},
"src_dot_orders_dot_components_dot_OrderDraftListPage_dot_2826235371": { "src_dot_orders_dot_components_dot_OrderDraftListPage_dot_2826235371": {
"context": "button", "context": "button",
"string": "Create order" "string": "Create order"

View file

@ -1184,6 +1184,7 @@ enum CollectionSortField {
NAME NAME
AVAILABILITY AVAILABILITY
PRODUCT_COUNT PRODUCT_COUNT
PUBLICATION_DATE
} }
input CollectionSortingInput { input CollectionSortingInput {
@ -2306,6 +2307,7 @@ type Margin {
type Menu implements Node { type Menu implements Node {
id: ID! id: ID!
name: String! name: String!
slug: String!
items: [MenuItem] items: [MenuItem]
} }
@ -2334,6 +2336,7 @@ type MenuCreate {
input MenuCreateInput { input MenuCreateInput {
name: String! name: String!
slug: String
items: [MenuItemInput] items: [MenuItemInput]
} }
@ -2363,10 +2366,12 @@ enum MenuErrorCode {
input MenuFilterInput { input MenuFilterInput {
search: String search: String
slug: [String]
} }
input MenuInput { input MenuInput {
name: String name: String
slug: String
} }
type MenuItem implements Node { type MenuItem implements Node {
@ -3229,7 +3234,7 @@ type OrderVoid {
orderErrors: [OrderError!]! orderErrors: [OrderError!]!
} }
type Page implements Node { type Page implements Node & ObjectWithMetadata {
seoTitle: String seoTitle: String
seoDescription: String seoDescription: String
id: ID! id: ID!
@ -3240,6 +3245,10 @@ type Page implements Node {
isPublished: Boolean! isPublished: Boolean!
slug: String! slug: String!
created: DateTime! created: DateTime!
privateMetadata: [MetadataItem]!
metadata: [MetadataItem]!
privateMeta: [MetaStore]! @deprecated(reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31.")
meta: [MetaStore]! @deprecated(reason: "Use the `metadata` field. This field will be removed after 2020-07-31.")
translation(languageCode: LanguageCodeEnum!): PageTranslation translation(languageCode: LanguageCodeEnum!): PageTranslation
} }
@ -3792,6 +3801,7 @@ input ProductFilterInput {
price: PriceRangeInput price: PriceRangeInput
minimalPrice: PriceRangeInput minimalPrice: PriceRangeInput
productTypes: [ID] productTypes: [ID]
ids: [ID]
} }
type ProductImage implements Node { type ProductImage implements Node {
@ -3878,6 +3888,7 @@ enum ProductOrderField {
DATE DATE
TYPE TYPE
PUBLISHED PUBLISHED
PUBLICATION_DATE
} }
type ProductPricingInfo { type ProductPricingInfo {
@ -4288,7 +4299,7 @@ type Query {
draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection
ordersTotal(period: ReportingPeriod): TaxedMoney ordersTotal(period: ReportingPeriod): TaxedMoney
orderByToken(token: UUID!): Order orderByToken(token: UUID!): Order
menu(id: ID, name: String): Menu menu(id: ID, name: String, slug: String): Menu
menus(sortBy: MenuSortingInput, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection menus(sortBy: MenuSortingInput, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection
menuItem(id: ID!): MenuItem menuItem(id: ID!): MenuItem
menuItems(sortBy: MenuItemSortingInput, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection menuItems(sortBy: MenuItemSortingInput, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection
@ -4755,10 +4766,10 @@ type Shop {
defaultMailSenderAddress: String defaultMailSenderAddress: String
description: String description: String
domain: Domain! domain: Domain!
homepageCollection: Collection homepageCollection: Collection @deprecated(reason: "Use the `collection` query with the `slug` parameter. This field will be removed in Saleor 3.0")
languages: [LanguageDisplay]! languages: [LanguageDisplay]!
name: String! name: String!
navigation: Navigation navigation: Navigation @deprecated(reason: "Fetch menus using the `menu` query with `slug` parameter.")
permissions: [Permission]! permissions: [Permission]!
phonePrefixes: [String]! phonePrefixes: [String]!
headerText: String headerText: String

View file

@ -150,7 +150,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
}; };
// fix for bug where input value is returned from debounce as id instead of label // fix for bug where input value is returned from debounce as id instead of label
if (value === inputValue && inputValue !== "") { if (value === inputValue && !!inputValue) {
ensureProperValues(); ensureProperValues();
} }

View file

@ -1,5 +1,7 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { metadataFragment } from "./metadata";
export const pageFragment = gql` export const pageFragment = gql`
fragment PageFragment on Page { fragment PageFragment on Page {
id id
@ -11,8 +13,10 @@ export const pageFragment = gql`
export const pageDetailsFragment = gql` export const pageDetailsFragment = gql`
${pageFragment} ${pageFragment}
${metadataFragment}
fragment PageDetailsFragment on Page { fragment PageDetailsFragment on Page {
...PageFragment ...PageFragment
...MetadataFragment
contentJson contentJson
seoTitle seoTitle
seoDescription seoDescription

View file

@ -19,7 +19,7 @@ export interface MetadataFragment_privateMetadata {
} }
export interface MetadataFragment { export interface MetadataFragment {
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice"; __typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "Page" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
metadata: (MetadataFragment_metadata | null)[]; metadata: (MetadataFragment_metadata | null)[];
privateMetadata: (MetadataFragment_privateMetadata | null)[]; privateMetadata: (MetadataFragment_privateMetadata | null)[];
} }

View file

@ -6,12 +6,26 @@
// GraphQL fragment: PageDetailsFragment // GraphQL fragment: PageDetailsFragment
// ==================================================== // ====================================================
export interface PageDetailsFragment_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageDetailsFragment_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageDetailsFragment { export interface PageDetailsFragment {
__typename: "Page"; __typename: "Page";
id: string; id: string;
title: string; title: string;
slug: string; slug: string;
isPublished: boolean; isPublished: boolean;
metadata: (PageDetailsFragment_metadata | null)[];
privateMetadata: (PageDetailsFragment_privateMetadata | null)[];
contentJson: any; contentJson: any;
seoTitle: string | null; seoTitle: string | null;
seoDescription: string | null; seoDescription: string | null;

View file

@ -49,9 +49,9 @@ const OrderAddressEditDialog: React.FC<OrderAddressEditDialogProps> = props => {
address, address,
confirmButtonState, confirmButtonState,
open, open,
errors, errors = [],
variant, variant,
countries, countries = [],
onClose, onClose,
onConfirm onConfirm
} = props; } = props;

View file

@ -1,124 +0,0 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import ActionDialog from "@saleor/components/ActionDialog";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer";
import { OrderErrorFragment } from "@saleor/fragments/types/OrderErrorFragment";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import getOrderErrorMessage from "@saleor/utils/errors/order";
import React from "react";
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
export enum OrderDraftFinalizeWarning {
NO_SHIPPING,
NO_BILLING,
NO_USER,
NO_SHIPPING_METHOD,
UNNECESSARY_SHIPPING_METHOD
}
export interface OrderDraftFinalizeDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
errors: OrderErrorFragment[];
open: boolean;
orderNumber: string;
warnings: OrderDraftFinalizeWarning[];
onClose: () => void;
onConfirm: () => void;
}
function translateWarnings(
intl: IntlShape
): Record<OrderDraftFinalizeWarning, string> {
return {
[OrderDraftFinalizeWarning.NO_BILLING]: intl.formatMessage({
defaultMessage: "No billing address"
}),
[OrderDraftFinalizeWarning.NO_SHIPPING]: intl.formatMessage({
defaultMessage: "No shipping address"
}),
[OrderDraftFinalizeWarning.NO_SHIPPING_METHOD]: intl.formatMessage({
defaultMessage: "Some products require shipping, but no method provided"
}),
[OrderDraftFinalizeWarning.NO_USER]: intl.formatMessage({
defaultMessage: "No user information"
}),
[OrderDraftFinalizeWarning.UNNECESSARY_SHIPPING_METHOD]: intl.formatMessage(
{
defaultMessage: "Shipping method provided, but no product requires it"
}
)
};
}
const OrderDraftFinalizeDialog: React.FC<OrderDraftFinalizeDialogProps> = ({
confirmButtonState,
errors: apiErrors,
open,
warnings,
onClose,
onConfirm,
orderNumber
}) => {
const intl = useIntl();
const errors = useModalDialogErrors(apiErrors, open);
const translatedWarnings = translateWarnings(intl);
return (
<ActionDialog
onClose={onClose}
onConfirm={onConfirm}
open={open}
title={intl.formatMessage({
defaultMessage: "Finalize Draft Order",
description: "dialog header"
})}
confirmButtonLabel={
warnings.length > 0
? intl.formatMessage({
defaultMessage: "Finalize anyway",
description: "button"
})
: intl.formatMessage({
defaultMessage: "Finalize",
description: "button"
})
}
confirmButtonState={confirmButtonState}
variant={warnings.length > 0 ? "delete" : "default"}
>
<DialogContentText component="div">
{warnings.length > 0 && (
<>
<p>
<FormattedMessage defaultMessage="There are missing or incorrect informations about this order:" />
</p>
<ul>
{warnings.map(warning => (
<li key={warning}>{translatedWarnings[warning]}</li>
))}
</ul>
</>
)}
<FormattedMessage
defaultMessage="Are you sure you want to finalize draft #{orderNumber}?"
values={{
orderNumber
}}
/>
{errors.length > 0 && (
<>
<FormSpacer />
{errors.map((err, index) => (
<DialogContentText color="error" key={index}>
{getOrderErrorMessage(err, intl)}
</DialogContentText>
))}
</>
)}
</DialogContentText>
</ActionDialog>
);
};
OrderDraftFinalizeDialog.displayName = "OrderDraftFinalize";
export default OrderDraftFinalizeDialog;

View file

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

View file

@ -186,13 +186,16 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
onClose: () => setVariants([]) onClose: () => setVariants([])
}); });
const selectedVariantsToProductsMap = products const productChoices = products.filter(
? products.map(product => product => product.variants?.length > 0
);
const selectedVariantsToProductsMap = productChoices
? productChoices.map(product =>
product.variants.map(variant => isVariantSelected(variant, variants)) product.variants.map(variant => isVariantSelected(variant, variants))
) )
: []; : [];
const productsWithAllVariantsSelected = products const productsWithAllVariantsSelected = productChoices
? products.map(product => ? productChoices.map(product =>
hasAllVariantsSelected(product.variants, variants) hasAllVariantsSelected(product.variants, variants)
) )
: []; : [];
@ -248,7 +251,7 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
<ResponsiveTable key="table"> <ResponsiveTable key="table">
<TableBody> <TableBody>
{renderCollection( {renderCollection(
products, productChoices,
(product, productIndex) => ( (product, productIndex) => (
<React.Fragment key={product ? product.id : "skeleton"}> <React.Fragment key={product ? product.id : "skeleton"}>
<TableRow> <TableRow>

View file

@ -0,0 +1,90 @@
import { transformAddressToForm } from "@saleor/misc";
import OrderAddressEditDialog from "@saleor/orders/components/OrderAddressEditDialog";
import { OrderDetails } from "@saleor/orders/types/OrderDetails";
import {
OrderDraftUpdate,
OrderDraftUpdateVariables
} from "@saleor/orders/types/OrderDraftUpdate";
import {
OrderUpdate,
OrderUpdateVariables
} from "@saleor/orders/types/OrderUpdate";
import { PartialMutationProviderOutput } from "@saleor/types";
import { AddressInput } from "@saleor/types/globalTypes";
import React from "react";
enum FieldType {
shipping = "shippingAddress",
billing = "billingAddress"
}
interface Props {
action: string;
id: string;
isDraft: boolean;
data: OrderDetails;
onClose: () => void;
orderUpdate: PartialMutationProviderOutput<OrderUpdate, OrderUpdateVariables>;
orderDraftUpdate: PartialMutationProviderOutput<
OrderDraftUpdate,
OrderDraftUpdateVariables
>;
}
const OrderAddressFields = ({
action,
isDraft,
id,
onClose,
orderUpdate,
orderDraftUpdate,
data
}: Props) => {
const order = data?.order;
const handleConfirm = (type: FieldType) => (value: AddressInput) => {
const updateMutation = isDraft ? orderDraftUpdate : orderUpdate;
updateMutation.mutate({
id,
input: {
[type]: value
}
});
};
const addressFieldCommonProps = {
confirmButtonState: isDraft
? orderDraftUpdate.opts.status
: orderUpdate.opts.status,
countries: data?.shop?.countries.map(country => ({
code: country.code,
label: country.country
})),
errors: isDraft
? orderDraftUpdate.opts.data?.draftOrderUpdate.errors
: orderUpdate.opts.data?.orderUpdate.errors,
onClose
};
return (
<>
<OrderAddressEditDialog
{...addressFieldCommonProps}
address={transformAddressToForm(order?.shippingAddress)}
open={action === "edit-shipping-address"}
variant="shipping"
onConfirm={handleConfirm(FieldType.shipping)}
/>
<OrderAddressEditDialog
{...addressFieldCommonProps}
address={transformAddressToForm(order?.billingAddress)}
open={action === "edit-billing-address"}
variant="billing"
onConfirm={handleConfirm(FieldType.billing)}
/>
</>
);
};
export default OrderAddressFields;

View file

@ -173,6 +173,7 @@ export const OrderDetailsMessages: React.FC<OrderDetailsMessages> = ({
}) })
}); });
} }
closeModal();
}; };
const handleShippingMethodUpdate = (data: OrderShippingMethodUpdate) => { const handleShippingMethodUpdate = (data: OrderShippingMethodUpdate) => {
const errs = data.orderUpdateShipping?.errors; const errs = data.orderUpdateShipping?.errors;
@ -255,7 +256,6 @@ export const OrderDetailsMessages: React.FC<OrderDetailsMessages> = ({
defaultMessage: "Draft order successfully finalized" defaultMessage: "Draft order successfully finalized"
}) })
}); });
closeModal();
} }
}; };
const handleInvoiceGeneratePending = (data: InvoiceRequest) => { const handleInvoiceGeneratePending = (data: InvoiceRequest) => {

View file

@ -23,25 +23,16 @@ import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { customerUrl } from "../../../customers/urls"; import { customerUrl } from "../../../customers/urls";
import { import { getMutationState, getStringOrPlaceholder, maybe } from "../../../misc";
getMutationState,
getStringOrPlaceholder,
maybe,
transformAddressToForm
} from "../../../misc";
import { productUrl } from "../../../products/urls"; import { productUrl } from "../../../products/urls";
import { import {
FulfillmentStatus, FulfillmentStatus,
JobStatusEnum, JobStatusEnum,
OrderStatus OrderStatus
} from "../../../types/globalTypes"; } from "../../../types/globalTypes";
import OrderAddressEditDialog from "../../components/OrderAddressEditDialog";
import OrderCancelDialog from "../../components/OrderCancelDialog"; import OrderCancelDialog from "../../components/OrderCancelDialog";
import OrderDetailsPage from "../../components/OrderDetailsPage"; import OrderDetailsPage from "../../components/OrderDetailsPage";
import OrderDraftCancelDialog from "../../components/OrderDraftCancelDialog/OrderDraftCancelDialog"; import OrderDraftCancelDialog from "../../components/OrderDraftCancelDialog/OrderDraftCancelDialog";
import OrderDraftFinalizeDialog, {
OrderDraftFinalizeWarning
} from "../../components/OrderDraftFinalizeDialog";
import OrderDraftPage from "../../components/OrderDraftPage"; import OrderDraftPage from "../../components/OrderDraftPage";
import OrderFulfillmentCancelDialog from "../../components/OrderFulfillmentCancelDialog"; import OrderFulfillmentCancelDialog from "../../components/OrderFulfillmentCancelDialog";
import OrderFulfillmentTrackingDialog from "../../components/OrderFulfillmentTrackingDialog"; import OrderFulfillmentTrackingDialog from "../../components/OrderFulfillmentTrackingDialog";
@ -52,7 +43,6 @@ import OrderProductAddDialog from "../../components/OrderProductAddDialog";
import OrderShippingMethodEditDialog from "../../components/OrderShippingMethodEditDialog"; import OrderShippingMethodEditDialog from "../../components/OrderShippingMethodEditDialog";
import OrderOperations from "../../containers/OrderOperations"; import OrderOperations from "../../containers/OrderOperations";
import { TypedOrderDetailsQuery, useOrderVariantSearch } from "../../queries"; import { TypedOrderDetailsQuery, useOrderVariantSearch } from "../../queries";
import { OrderDetails_order } from "../../types/OrderDetails";
import { import {
orderDraftListUrl, orderDraftListUrl,
orderFulfillUrl, orderFulfillUrl,
@ -61,38 +51,9 @@ import {
OrderUrlDialog, OrderUrlDialog,
OrderUrlQueryParams OrderUrlQueryParams
} from "../../urls"; } from "../../urls";
import OrderAddressFields from "./OrderAddressFields";
import { OrderDetailsMessages } from "./OrderDetailsMessages"; import { OrderDetailsMessages } from "./OrderDetailsMessages";
const orderDraftFinalizeWarnings = (order: OrderDetails_order) => {
const warnings = [] as OrderDraftFinalizeWarning[];
if (!(order && order.shippingAddress)) {
warnings.push(OrderDraftFinalizeWarning.NO_SHIPPING);
}
if (!(order && order.billingAddress)) {
warnings.push(OrderDraftFinalizeWarning.NO_BILLING);
}
if (!(order && (order.user || order.userEmail))) {
warnings.push(OrderDraftFinalizeWarning.NO_USER);
}
if (
order &&
order.lines &&
order.lines.filter(line => line.isShippingRequired).length > 0 &&
order.shippingMethod === null
) {
warnings.push(OrderDraftFinalizeWarning.NO_SHIPPING_METHOD);
}
if (
order &&
order.lines &&
order.lines.filter(line => line.isShippingRequired).length === 0 &&
order.shippingMethod !== null
) {
warnings.push(OrderDraftFinalizeWarning.UNNECESSARY_SHIPPING_METHOD);
}
return warnings;
};
interface OrderDetailsProps { interface OrderDetailsProps {
id: string; id: string;
params: OrderUrlQueryParams; params: OrderUrlQueryParams;
@ -513,7 +474,9 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
input: data input: data
}) })
} }
onDraftFinalize={() => openModal("finalize")} onDraftFinalize={() =>
orderDraftFinalize.mutate({ id })
}
onDraftRemove={() => openModal("cancel")} onDraftRemove={() => openModal("cancel")}
onOrderLineAdd={() => openModal("add-order-line")} onOrderLineAdd={() => openModal("add-order-line")}
onBack={() => navigate(orderDraftListUrl())} onBack={() => navigate(orderDraftListUrl())}
@ -561,18 +524,6 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
open={params.action === "cancel"} open={params.action === "cancel"}
orderNumber={getStringOrPlaceholder(order?.number)} orderNumber={getStringOrPlaceholder(order?.number)}
/> />
<OrderDraftFinalizeDialog
confirmButtonState={orderDraftFinalize.opts.status}
errors={
orderDraftFinalize.opts.data?.draftOrderComplete
.errors || []
}
onClose={closeModal}
onConfirm={() => orderDraftFinalize.mutate({ id })}
open={params.action === "finalize"}
orderNumber={getStringOrPlaceholder(order?.number)}
warnings={orderDraftFinalizeWarnings(order)}
/>
<OrderShippingMethodEditDialog <OrderShippingMethodEditDialog
confirmButtonState={ confirmButtonState={
orderShippingMethodUpdate.opts.status orderShippingMethodUpdate.opts.status
@ -623,49 +574,14 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
/> />
</> </>
)} )}
<OrderAddressEditDialog <OrderAddressFields
confirmButtonState={orderUpdate.opts.status} isDraft={order?.status === OrderStatus.DRAFT}
address={transformAddressToForm(order?.shippingAddress)} orderUpdate={orderUpdate}
countries={ orderDraftUpdate={orderDraftUpdate}
data?.shop?.countries.map(country => ({ data={data}
code: country.code, id={id}
label: country.country
})) || []
}
errors={orderUpdate.opts.data?.orderUpdate.errors || []}
open={params.action === "edit-shipping-address"}
variant="shipping"
onClose={closeModal} onClose={closeModal}
onConfirm={shippingAddress => action={params.action}
orderUpdate.mutate({
id,
input: {
shippingAddress
}
})
}
/>
<OrderAddressEditDialog
confirmButtonState={orderUpdate.opts.status}
address={transformAddressToForm(order?.billingAddress)}
countries={
data?.shop?.countries.map(country => ({
code: country.code,
label: country.country
})) || []
}
errors={orderUpdate.opts.data?.orderUpdate.errors || []}
open={params.action === "edit-billing-address"}
variant="billing"
onClose={closeModal}
onConfirm={billingAddress =>
orderUpdate.mutate({
id,
input: {
billingAddress
}
})
}
/> />
</> </>
)} )}

View file

@ -4,6 +4,7 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
@ -11,6 +12,8 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment"; import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { import {
ContentState, ContentState,
convertFromRaw, convertFromRaw,
@ -24,7 +27,7 @@ import { maybe } from "../../../misc";
import { PageDetails_page } from "../../types/PageDetails"; import { PageDetails_page } from "../../types/PageDetails";
import PageInfo from "../PageInfo"; import PageInfo from "../PageInfo";
export interface FormData { export interface FormData extends MetadataFormData {
content: RawDraftContentState; content: RawDraftContentState;
isPublished: boolean; isPublished: boolean;
publicationDate: string; publicationDate: string;
@ -56,6 +59,12 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const pageExists = page !== null; const pageExists = page !== null;
const initialForm: FormData = { const initialForm: FormData = {
@ -63,109 +72,126 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
() => JSON.parse(page.contentJson), () => JSON.parse(page.contentJson),
convertToRaw(ContentState.createFromText("")) convertToRaw(ContentState.createFromText(""))
), ),
isPublished: maybe(() => page.isPublished, false), isPublished: page?.isPublished,
publicationDate: maybe(() => page.publicationDate, ""), metadata: pageExists ? page?.metadata?.map(mapMetadataItemToInput) : [],
seoDescription: maybe(() => page.seoDescription || "", ""), privateMetadata: pageExists
seoTitle: maybe(() => page.seoTitle || "", ""), ? page?.privateMetadata?.map(mapMetadataItemToInput)
slug: maybe(() => page.slug, ""), : [],
title: maybe(() => page.title, "") publicationDate: page?.publicationDate || "",
seoDescription: page?.seoDescription || "",
seoTitle: page?.seoTitle || "",
slug: page?.slug || "",
title: page?.title || ""
}; };
const handleSubmit = (data: FormData) => onSubmit(getParsedData(data)); const handleSubmit = (data: FormData) => {
const metadata = isMetadataModified ? data.metadata : undefined;
const privateMetadata = isPrivateMetadataModified
? data.privateMetadata
: undefined;
const getParsedData = (data: FormData) => ({ onSubmit({
...data, ...data,
isPublished: data.isPublished || !!data.publicationDate isPublished: data.isPublished || !!data.publicationDate,
}); metadata,
privateMetadata
});
};
return ( return (
<Form initial={initialForm} onSubmit={handleSubmit}> <Form initial={initialForm} onSubmit={handleSubmit}>
{({ change, data, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => {
<Container> const changeMetadata = makeMetadataChangeHandler(change);
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.pages)} return (
</AppHeader> <Container>
<PageHeader <AppHeader onBack={onBack}>
title={ {intl.formatMessage(sectionNames.pages)}
!pageExists </AppHeader>
? intl.formatMessage({ <PageHeader
defaultMessage: "Create Page", title={
description: "page header" !pageExists
}) ? intl.formatMessage({
: maybe(() => page.title) defaultMessage: "Create Page",
} description: "page header"
/> })
<Grid> : maybe(() => page.title)
<div> }
<PageInfo />
data={data} <Grid>
disabled={disabled} <div>
errors={errors} <PageInfo
page={page} data={data}
onChange={change} disabled={disabled}
/> errors={errors}
<CardSpacer /> page={page}
<SeoForm onChange={change}
errors={errors} />
allowEmptySlug={!pageExists} <CardSpacer />
description={data.seoDescription} <SeoForm
disabled={disabled} errors={errors}
descriptionPlaceholder={maybe( allowEmptySlug={!pageExists}
() => description={data.seoDescription}
convertFromRaw(data.content) disabled={disabled}
.getPlainText() descriptionPlaceholder={maybe(
.slice(0, 300), () =>
"" convertFromRaw(data.content)
)} .getPlainText()
onChange={change} .slice(0, 300),
slug={data.slug} ""
slugPlaceholder={data.title} )}
title={data.seoTitle} onChange={change}
titlePlaceholder={data.title} slug={data.slug}
helperText={intl.formatMessage({ slugPlaceholder={data.title}
defaultMessage: title={data.seoTitle}
"Add search engine title and description to make this page easier to find" titlePlaceholder={data.title}
})} helperText={intl.formatMessage({
/> defaultMessage:
</div> "Add search engine title and description to make this page easier to find"
<div> })}
<CardSpacer /> />
<VisibilityCard <CardSpacer />
data={data} <Metadata data={data} onChange={changeMetadata} />
errors={errors} </div>
disabled={disabled} <div>
messages={{ <CardSpacer />
hiddenLabel: intl.formatMessage({ <VisibilityCard
defaultMessage: "Hidden", data={data}
description: "page label" errors={errors}
}), disabled={disabled}
hiddenSecondLabel: intl.formatMessage( messages={{
{ hiddenLabel: intl.formatMessage({
defaultMessage: "will be visible from {date}", defaultMessage: "Hidden",
description: "page" description: "page label"
}, }),
{ hiddenSecondLabel: intl.formatMessage(
date: localizeDate(data.publicationDate, "L") {
} defaultMessage: "will be visible from {date}",
), description: "page"
visibleLabel: intl.formatMessage({ },
defaultMessage: "Visible", {
description: "page label" date: localizeDate(data.publicationDate, "L")
}) }
}} ),
onChange={change} visibleLabel: intl.formatMessage({
/> defaultMessage: "Visible",
</div> description: "page label"
</Grid> })
<SaveButtonBar }}
disabled={disabled || !hasChanged} onChange={change}
state={saveButtonBarState} />
onCancel={onBack} </div>
onDelete={page === null ? undefined : onRemove} </Grid>
onSave={submit} <SaveButtonBar
/> disabled={disabled || !hasChanged}
</Container> state={saveButtonBarState}
)} onCancel={onBack}
onDelete={page === null ? undefined : onRemove}
onSave={submit}
/>
</Container>
);
}}
</Form> </Form>
); );
}; };

View file

@ -37,6 +37,14 @@ export const page: PageDetails_page = {
contentJson: JSON.stringify(content), contentJson: JSON.stringify(content),
id: "Kzx152sEm==", id: "Kzx152sEm==",
isPublished: false, isPublished: false,
metadata: [
{
__typename: "MetadataItem",
key: "integration.id",
value: "100023123"
}
],
privateMetadata: [],
publicationDate: "", publicationDate: "",
seoDescription: "About", seoDescription: "About",
seoTitle: "About", seoTitle: "About",

View file

@ -14,12 +14,26 @@ export interface PageCreate_pageCreate_errors {
field: string | null; field: string | null;
} }
export interface PageCreate_pageCreate_page_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageCreate_pageCreate_page_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageCreate_pageCreate_page { export interface PageCreate_pageCreate_page {
__typename: "Page"; __typename: "Page";
id: string; id: string;
title: string; title: string;
slug: string; slug: string;
isPublished: boolean; isPublished: boolean;
metadata: (PageCreate_pageCreate_page_metadata | null)[];
privateMetadata: (PageCreate_pageCreate_page_privateMetadata | null)[];
contentJson: any; contentJson: any;
seoTitle: string | null; seoTitle: string | null;
seoDescription: string | null; seoDescription: string | null;

View file

@ -6,12 +6,26 @@
// GraphQL query operation: PageDetails // GraphQL query operation: PageDetails
// ==================================================== // ====================================================
export interface PageDetails_page_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageDetails_page_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageDetails_page { export interface PageDetails_page {
__typename: "Page"; __typename: "Page";
id: string; id: string;
title: string; title: string;
slug: string; slug: string;
isPublished: boolean; isPublished: boolean;
metadata: (PageDetails_page_metadata | null)[];
privateMetadata: (PageDetails_page_privateMetadata | null)[];
contentJson: any; contentJson: any;
seoTitle: string | null; seoTitle: string | null;
seoDescription: string | null; seoDescription: string | null;

View file

@ -14,12 +14,26 @@ export interface PageUpdate_pageUpdate_errors {
field: string | null; field: string | null;
} }
export interface PageUpdate_pageUpdate_page_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageUpdate_pageUpdate_page_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface PageUpdate_pageUpdate_page { export interface PageUpdate_pageUpdate_page {
__typename: "Page"; __typename: "Page";
id: string; id: string;
title: string; title: string;
slug: string; slug: string;
isPublished: boolean; isPublished: boolean;
metadata: (PageUpdate_pageUpdate_page_metadata | null)[];
privateMetadata: (PageUpdate_pageUpdate_page_privateMetadata | null)[];
contentJson: any; contentJson: any;
seoTitle: string | null; seoTitle: string | null;
seoDescription: string | null; seoDescription: string | null;

View file

@ -1,10 +1,15 @@
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
import {
useMetadataUpdate,
usePrivateMetadataUpdate
} from "@saleor/utils/metadata/updateMetadata";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import PageDetailsPage from "../components/PageDetailsPage"; import PageDetailsPage, { FormData } from "../components/PageDetailsPage";
import { TypedPageCreate } from "../mutations"; import { TypedPageCreate } from "../mutations";
import { PageCreate as PageCreateData } from "../types/PageCreate"; import { PageCreate as PageCreateData } from "../types/PageCreate";
import { pageListUrl, pageUrl } from "../urls"; import { pageListUrl, pageUrl } from "../urls";
@ -17,6 +22,8 @@ export const PageCreate: React.FC<PageCreateProps> = () => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
const [updateMetadata] = useMetadataUpdate({});
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
const handlePageCreate = (data: PageCreateData) => { const handlePageCreate = (data: PageCreateData) => {
if (data.pageCreate.errors.length === 0) { if (data.pageCreate.errors.length === 0) {
@ -32,41 +39,52 @@ export const PageCreate: React.FC<PageCreateProps> = () => {
return ( return (
<TypedPageCreate onCompleted={handlePageCreate}> <TypedPageCreate onCompleted={handlePageCreate}>
{(pageCreate, pageCreateOpts) => ( {(pageCreate, pageCreateOpts) => {
<> const handleCreate = async (formData: FormData) => {
<WindowTitle const result = await pageCreate({
title={intl.formatMessage({ variables: {
defaultMessage: "Create Page", input: {
description: "header" contentJson: JSON.stringify(formData.content),
})} isPublished: formData.isPublished,
/> publicationDate: formData.publicationDate,
<PageDetailsPage seo: {
disabled={pageCreateOpts.loading} description: formData.seoDescription,
errors={pageCreateOpts.data?.pageCreate.errors || []} title: formData.seoTitle
saveButtonBarState={pageCreateOpts.status} },
page={null} slug: formData.slug === "" ? null : formData.slug,
onBack={() => navigate(pageListUrl())} title: formData.title
onRemove={() => undefined} }
onSubmit={formData =>
pageCreate({
variables: {
input: {
contentJson: JSON.stringify(formData.content),
isPublished: formData.isPublished,
publicationDate: formData.publicationDate,
seo: {
description: formData.seoDescription,
title: formData.seoTitle
},
slug: formData.slug === "" ? null : formData.slug,
title: formData.title
}
}
})
} }
/> });
</>
)} return result.data.pageCreate.page?.id || null;
};
const handleSubmit = createMetadataCreateHandler(
handleCreate,
updateMetadata,
updatePrivateMetadata
);
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Page",
description: "header"
})}
/>
<PageDetailsPage
disabled={pageCreateOpts.loading}
errors={pageCreateOpts.data?.pageCreate.errors || []}
saveButtonBarState={pageCreateOpts.status}
page={null}
onBack={() => navigate(pageListUrl())}
onRemove={() => undefined}
onSubmit={handleSubmit}
/>
</>
);
}}
</TypedPageCreate> </TypedPageCreate>
); );
}; };

View file

@ -4,6 +4,11 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
import {
useMetadataUpdate,
usePrivateMetadataUpdate
} from "@saleor/utils/metadata/updateMetadata";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -36,6 +41,8 @@ export const PageDetails: React.FC<PageDetailsProps> = ({ id, params }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
const [updateMetadata] = useMetadataUpdate({});
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
const handlePageRemove = (data: PageRemove) => { const handlePageRemove = (data: PageRemove) => {
if (data.pageDelete.errors.length === 0) { if (data.pageDelete.errors.length === 0) {
@ -46,68 +53,82 @@ export const PageDetails: React.FC<PageDetailsProps> = ({ id, params }) => {
navigate(pageListUrl()); navigate(pageListUrl());
} }
}; };
return ( return (
<TypedPageRemove variables={{ id }} onCompleted={handlePageRemove}> <TypedPageRemove variables={{ id }} onCompleted={handlePageRemove}>
{(pageRemove, pageRemoveOpts) => ( {(pageRemove, pageRemoveOpts) => (
<TypedPageUpdate> <TypedPageUpdate>
{(pageUpdate, pageUpdateOpts) => ( {(pageUpdate, pageUpdateOpts) => (
<TypedPageDetailsQuery variables={{ id }}> <TypedPageDetailsQuery variables={{ id }}>
{pageDetails => ( {pageDetails => {
<> const handleUpdate = async (data: FormData) => {
<WindowTitle const result = await pageUpdate({
title={maybe(() => pageDetails.data.page.title)} variables: {
/> id,
<PageDetailsPage input: createPageInput(data)
disabled={pageDetails.loading}
errors={pageUpdateOpts.data?.pageUpdate.errors || []}
saveButtonBarState={pageUpdateOpts.status}
page={pageDetails.data?.page}
onBack={() => navigate(pageListUrl())}
onRemove={() =>
navigate(
pageUrl(id, {
action: "remove"
})
)
} }
onSubmit={formData => });
pageUpdate({
variables: { return result.data.pageUpdate.errors;
id, };
input: createPageInput(formData)
} const handleSubmit = createMetadataUpdateHandler(
}) pageDetails.data?.page,
} handleUpdate,
/> variables => updateMetadata({ variables }),
<ActionDialog variables => updatePrivateMetadata({ variables })
open={params.action === "remove"} );
confirmButtonState={pageRemoveOpts.status}
title={intl.formatMessage({ return (
defaultMessage: "Delete Page", <>
description: "dialog header" <WindowTitle
})} title={maybe(() => pageDetails.data.page.title)}
onClose={() => navigate(pageUrl(id))} />
onConfirm={pageRemove} <PageDetailsPage
variant="delete" disabled={pageDetails.loading}
> errors={pageUpdateOpts.data?.pageUpdate.errors || []}
<DialogContentText> saveButtonBarState={pageUpdateOpts.status}
<FormattedMessage page={pageDetails.data?.page}
defaultMessage="Are you sure you want to delete {title}?" onBack={() => navigate(pageListUrl())}
description="delete page" onRemove={() =>
values={{ navigate(
title: ( pageUrl(id, {
<strong> action: "remove"
{getStringOrPlaceholder( })
pageDetails.data?.page?.title )
)} }
</strong> onSubmit={handleSubmit}
) />
}} <ActionDialog
/> open={params.action === "remove"}
</DialogContentText> confirmButtonState={pageRemoveOpts.status}
</ActionDialog> title={intl.formatMessage({
</> defaultMessage: "Delete Page",
)} description: "dialog header"
})}
onClose={() => navigate(pageUrl(id))}
onConfirm={pageRemove}
variant="delete"
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {title}?"
description="delete page"
values={{
title: (
<strong>
{getStringOrPlaceholder(
pageDetails.data?.page?.title
)}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</>
);
}}
</TypedPageDetailsQuery> </TypedPageDetailsQuery>
)} )}
</TypedPageUpdate> </TypedPageUpdate>

View file

@ -199,7 +199,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
const getStocksData = () => { const getStocksData = () => {
if (product.productType.hasVariants) { if (product.productType.hasVariants) {
return { removeStocks: [], updateStocks: [] }; return { addStocks: [], removeStocks: [], updateStocks: [] };
} }
const dataStocks = stocks.map(stock => stock.id); const dataStocks = stocks.map(stock => stock.id);
@ -209,6 +209,9 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
const stockDiff = diff(variantStocks, dataStocks); const stockDiff = diff(variantStocks, dataStocks);
return { return {
addStocks: stocks.filter(stock =>
stockDiff.added.some(addedStock => addedStock === stock.id)
),
removeStocks: stockDiff.removed, removeStocks: stockDiff.removed,
updateStocks: stocks.filter( updateStocks: stocks.filter(
stock => !stockDiff.added.some(addedStock => addedStock === stock.id) stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
@ -228,7 +231,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
...getAvailabilityData(data), ...getAvailabilityData(data),
...getStocksData(), ...getStocksData(),
...getMetadata(data), ...getMetadata(data),
addStocks: [],
attributes attributes
}); });

View file

@ -11673,24 +11673,6 @@ exports[`Storyshots Orders / OrderDraftCancelDialog errors 1`] = `
/> />
`; `;
exports[`Storyshots Orders / OrderDraftFinalizeDialog default 1`] = `
<div
style="padding:24px"
/>
`;
exports[`Storyshots Orders / OrderDraftFinalizeDialog with errors 1`] = `
<div
style="padding:24px"
/>
`;
exports[`Storyshots Orders / OrderDraftFinalizeDialog with warnings 1`] = `
<div
style="padding:24px"
/>
`;
exports[`Storyshots Orders / OrderFulfillmentCancelDialog default 1`] = ` exports[`Storyshots Orders / OrderFulfillmentCancelDialog default 1`] = `
<div <div
style="padding:24px" style="padding:24px"
@ -113412,6 +113394,289 @@ Ctrl + K"
</div> </div>
</div> </div>
</div> </div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="false"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id Metadata-content-id"
>
<div
class="Metadata-togglable-id"
>
<div
class="MuiTypography-root-id MuiTypography-body2-id MuiTypography-colorTextSecondary-id"
>
1 Field
</div>
<button
class="MuiButtonBase-root-id MuiIconButton-root-id"
data-test="expand"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</span>
</button>
</div>
</div>
<table
class="MuiTable-root-id Metadata-table-id"
>
<thead
class="MuiTableHead-root-id"
>
<tr
class="MuiTableRow-root-id MuiTableRow-head-id"
>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colNameHeader-id"
scope="col"
>
Field
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colValue-id"
scope="col"
>
Value
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colActionHeader-id"
scope="col"
>
Actions
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id"
data-test="field"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colName-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id Metadata-nameInput-id"
name="name:0"
type="text"
value="integration.id"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colValue-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id Metadata-input-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-multiline-id MuiOutlinedInput-multiline-id"
>
<textarea
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputMultiline-id MuiOutlinedInput-inputMultiline-id"
name="value:0"
rows="1"
>
100023123
</textarea>
<textarea
aria-hidden="true"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputMultiline-id MuiOutlinedInput-inputMultiline-id"
readonly=""
style="visibility:hidden;position:absolute;overflow:hidden;height:0;top:0;left:0;transform:translateZ(0)"
tabindex="-1"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colAction-id"
>
<button
class="MuiButtonBase-root-id MuiIconButton-root-id MuiIconButton-colorPrimary-id"
data-test="deleteField"
data-test-id="0"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</span>
</button>
</td>
</tr>
</tbody>
</table>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-text-id MuiButton-textPrimary-id"
data-test="addField"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Add Field
</span>
</button>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="true"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Private Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id Metadata-content-id"
/>
<div
class="Metadata-emptyContainer-id"
>
<span
class="isvg pending Metadata-emptyImage-id"
/>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorTextSecondary-id"
>
There is no private metadata created for this element.
</div>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorTextSecondary-id"
>
Use the button below to add new metadata field
</div>
</div>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-text-id MuiButton-textPrimary-id"
data-test="addField"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Add Field
</span>
</button>
</div>
</div>
</div> </div>
<div> <div>
<div <div
@ -114236,6 +114501,289 @@ Ctrl + K"
</div> </div>
</div> </div>
</div> </div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="false"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id Metadata-content-id"
>
<div
class="Metadata-togglable-id"
>
<div
class="MuiTypography-root-id MuiTypography-body2-id MuiTypography-colorTextSecondary-id"
>
1 Field
</div>
<button
class="MuiButtonBase-root-id MuiIconButton-root-id"
data-test="expand"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</span>
</button>
</div>
</div>
<table
class="MuiTable-root-id Metadata-table-id"
>
<thead
class="MuiTableHead-root-id"
>
<tr
class="MuiTableRow-root-id MuiTableRow-head-id"
>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colNameHeader-id"
scope="col"
>
Field
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colValue-id"
scope="col"
>
Value
</th>
<th
class="MuiTableCell-root-id MuiTableCell-head-id Metadata-colActionHeader-id"
scope="col"
>
Actions
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id"
data-test="field"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colName-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id Metadata-nameInput-id"
name="name:0"
type="text"
value="integration.id"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colValue-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id Metadata-input-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-multiline-id MuiOutlinedInput-multiline-id"
>
<textarea
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputMultiline-id MuiOutlinedInput-inputMultiline-id"
name="value:0"
rows="1"
>
100023123
</textarea>
<textarea
aria-hidden="true"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputMultiline-id MuiOutlinedInput-inputMultiline-id"
readonly=""
style="visibility:hidden;position:absolute;overflow:hidden;height:0;top:0;left:0;transform:translateZ(0)"
tabindex="-1"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id Metadata-colAction-id"
>
<button
class="MuiButtonBase-root-id MuiIconButton-root-id MuiIconButton-colorPrimary-id"
data-test="deleteField"
data-test-id="0"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</span>
</button>
</td>
</tr>
</tbody>
</table>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-text-id MuiButton-textPrimary-id"
data-test="addField"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Add Field
</span>
</button>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="true"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Private Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id Metadata-content-id"
/>
<div
class="Metadata-emptyContainer-id"
>
<span
class="isvg pending Metadata-emptyImage-id"
/>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorTextSecondary-id"
>
There is no private metadata created for this element.
</div>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorTextSecondary-id"
>
Use the button below to add new metadata field
</div>
</div>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-text-id MuiButton-textPrimary-id"
data-test="addField"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Add Field
</span>
</button>
</div>
</div>
</div> </div>
<div> <div>
<div <div
@ -114863,6 +115411,80 @@ Ctrl + K"
</div> </div>
</div> </div>
</div> </div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="false"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
>
<span
class="Skeleton-skeleton-id"
>
</span>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
data-test="metadataEditor"
data-test-expanded="true"
data-test-is-private="true"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Private Metadata
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
>
<span
class="Skeleton-skeleton-id"
>
</span>
</div>
</div>
</div> </div>
<div> <div>
<div <div

View file

@ -121,7 +121,6 @@ function loadStories() {
require("./stories/orders/OrderCustomer"); require("./stories/orders/OrderCustomer");
require("./stories/orders/OrderDetailsPage"); require("./stories/orders/OrderDetailsPage");
require("./stories/orders/OrderDraftCancelDialog"); require("./stories/orders/OrderDraftCancelDialog");
require("./stories/orders/OrderDraftFinalizeDialog");
require("./stories/orders/OrderDraftListPage"); require("./stories/orders/OrderDraftListPage");
require("./stories/orders/OrderDraftPage"); require("./stories/orders/OrderDraftPage");
require("./stories/orders/OrderFulfillmentCancelDialog"); require("./stories/orders/OrderFulfillmentCancelDialog");

View file

@ -1,46 +0,0 @@
import { OrderErrorCode } from "@saleor/types/globalTypes";
import { storiesOf } from "@storybook/react";
import React from "react";
import OrderDraftFinalize, {
OrderDraftFinalizeDialogProps,
OrderDraftFinalizeWarning
} from "../../../orders/components/OrderDraftFinalizeDialog";
import Decorator from "../../Decorator";
const props: OrderDraftFinalizeDialogProps = {
confirmButtonState: "default",
errors: [],
onClose: () => undefined,
onConfirm: () => undefined,
open: true,
orderNumber: "5",
warnings: []
};
storiesOf("Orders / OrderDraftFinalizeDialog", module)
.addDecorator(Decorator)
.add("default", () => <OrderDraftFinalize {...props} />)
.add("with warnings", () => (
<OrderDraftFinalize
{...props}
warnings={[
OrderDraftFinalizeWarning.NO_SHIPPING_METHOD,
OrderDraftFinalizeWarning.NO_SHIPPING,
OrderDraftFinalizeWarning.NO_BILLING,
OrderDraftFinalizeWarning.NO_USER
]}
/>
))
.add("with errors", () => (
<OrderDraftFinalize
{...props}
errors={[
{
__typename: "OrderError",
code: OrderErrorCode.GRAPHQL_ERROR,
field: null
}
]}
/>
));

View file

@ -119,6 +119,7 @@ export enum CollectionSortField {
AVAILABILITY = "AVAILABILITY", AVAILABILITY = "AVAILABILITY",
NAME = "NAME", NAME = "NAME",
PRODUCT_COUNT = "PRODUCT_COUNT", PRODUCT_COUNT = "PRODUCT_COUNT",
PUBLICATION_DATE = "PUBLICATION_DATE",
} }
export enum ConfigurationTypeFieldEnum { export enum ConfigurationTypeFieldEnum {
@ -737,6 +738,7 @@ export enum ProductOrderField {
MINIMAL_PRICE = "MINIMAL_PRICE", MINIMAL_PRICE = "MINIMAL_PRICE",
NAME = "NAME", NAME = "NAME",
PRICE = "PRICE", PRICE = "PRICE",
PUBLICATION_DATE = "PUBLICATION_DATE",
PUBLISHED = "PUBLISHED", PUBLISHED = "PUBLISHED",
TYPE = "TYPE", TYPE = "TYPE",
} }
@ -1176,6 +1178,7 @@ export interface IntRangeInput {
export interface MenuCreateInput { export interface MenuCreateInput {
name: string; name: string;
slug?: string | null;
items?: (MenuItemInput | null)[] | null; items?: (MenuItemInput | null)[] | null;
} }
@ -1379,6 +1382,7 @@ export interface ProductFilterInput {
price?: PriceRangeInput | null; price?: PriceRangeInput | null;
minimalPrice?: PriceRangeInput | null; minimalPrice?: PriceRangeInput | null;
productTypes?: (string | null)[] | null; productTypes?: (string | null)[] | null;
ids?: (string | null)[] | null;
} }
export interface ProductInput { export interface ProductInput {

View file

@ -38,7 +38,7 @@ export interface UpdateMetadata_deleteMetadata_item_privateMetadata {
} }
export interface UpdateMetadata_deleteMetadata_item { export interface UpdateMetadata_deleteMetadata_item {
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice"; __typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "Page" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
metadata: (UpdateMetadata_deleteMetadata_item_metadata | null)[]; metadata: (UpdateMetadata_deleteMetadata_item_metadata | null)[];
privateMetadata: (UpdateMetadata_deleteMetadata_item_privateMetadata | null)[]; privateMetadata: (UpdateMetadata_deleteMetadata_item_privateMetadata | null)[];
id: string; id: string;

View file

@ -38,7 +38,7 @@ export interface UpdatePrivateMetadata_deletePrivateMetadata_item_privateMetadat
} }
export interface UpdatePrivateMetadata_deletePrivateMetadata_item { export interface UpdatePrivateMetadata_deletePrivateMetadata_item {
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice"; __typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "Page" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
metadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_metadata | null)[]; metadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_metadata | null)[];
privateMetadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_privateMetadata | null)[]; privateMetadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_privateMetadata | null)[];
id: string; id: string;