Use collection search hook

This commit is contained in:
dominik-zeglen 2019-11-19 17:04:53 +01:00
parent e67727af03
commit 3f28673c94
11 changed files with 786 additions and 834 deletions

View file

@ -15,7 +15,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { buttonMessages } from "@saleor/intl";
import { SearchCollections_search_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import Checkbox from "../Checkbox";
import ConfirmButton, {
ConfirmButtonTransitionState

View file

@ -17,10 +17,10 @@ import usePaginator, {
import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { categoryUrl } from "../../categories/urls";
import { collectionUrl } from "../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchCollections from "../../containers/SearchCollections";
import SearchProducts from "../../containers/SearchProducts";
import { decimal, getMutationState, joinDateTime, maybe } from "../../misc";
import { productUrl } from "../../products/urls";
@ -72,6 +72,12 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const paginationState = createPaginationState(PAGINATE_BY, params);
const changeTab = (tab: SaleDetailsPageTab) => {
@ -410,43 +416,33 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
})
}
/>
<SearchCollections
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchCollections,
result: searchCollectionsOpts
}) => (
<AssignCollectionDialog
collections={maybe(() =>
searchCollectionsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory =>
suggestedCategory.id
)
)}
confirmButtonState={assignTransitionState}
open={params.action === "assign-collection"}
onFetch={searchCollections}
loading={searchCollectionsOpts.loading}
onClose={closeModal}
onSubmit={collections =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
collections: collections.map(
product => product.id
)
}
}
})
}
/>
<AssignCollectionDialog
collections={maybe(() =>
searchCollectionsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory => suggestedCategory.id
)
)}
</SearchCollections>
confirmButtonState={assignTransitionState}
open={params.action === "assign-collection"}
onFetch={searchCollections}
loading={searchCollectionsOpts.loading}
onClose={closeModal}
onSubmit={collections =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
collections: collections.map(
product => product.id
)
}
}
})
}
/>
<ActionDialog
open={
params.action === "unassign-category" &&

View file

@ -17,10 +17,10 @@ import usePaginator, {
import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { categoryUrl } from "../../categories/urls";
import { collectionUrl } from "../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchCollections from "../../containers/SearchCollections";
import SearchProducts from "../../containers/SearchProducts";
import { decimal, getMutationState, joinDateTime, maybe } from "../../misc";
import { productUrl } from "../../products/urls";
@ -74,6 +74,12 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const paginationState = createPaginationState(PAGINATE_BY, params);
const changeTab = (tab: VoucherDetailsPageTab) => {
@ -453,43 +459,33 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
})
}
/>
<SearchCollections
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchCollections,
result: searchCollectionsOpts
}) => (
<AssignCollectionDialog
collections={maybe(() =>
searchCollectionsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory =>
suggestedCategory.id
)
)}
confirmButtonState={assignTransitionState}
open={params.action === "assign-collection"}
onFetch={searchCollections}
loading={searchCollectionsOpts.loading}
onClose={closeModal}
onSubmit={collections =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
collections: collections.map(
product => product.id
)
}
}
})
}
/>
<AssignCollectionDialog
collections={maybe(() =>
searchCollectionsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory => suggestedCategory.id
)
)}
</SearchCollections>
confirmButtonState={assignTransitionState}
open={params.action === "assign-collection"}
onFetch={searchCollections}
loading={searchCollectionsOpts.loading}
onClose={closeModal}
onSubmit={collections =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
collections: collections.map(
product => product.id
)
}
}
})
}
/>
<DiscountCountrySelectDialog
confirmButtonState={formTransitionState}
countries={maybe(() => shop.countries, [])}

View file

@ -14,13 +14,13 @@ import ConfirmButton, {
ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer";
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
import { SearchPages_search_edges_node } from "@saleor/containers/SearchPages/types/SearchPages";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { buttonMessages, sectionNames } from "@saleor/intl";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { UserError } from "@saleor/types";
import { getErrors, getFieldError } from "@saleor/utils/errors";
import { getMenuItemByValue, IMenu } from "@saleor/utils/menu";

View file

@ -6,10 +6,10 @@ import ActionDialog from "@saleor/components/ActionDialog";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { categoryUrl } from "../../../categories/urls";
import { collectionUrl } from "../../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config";
import SearchCollections from "../../../containers/SearchCollections";
import SearchPages from "../../../containers/SearchPages";
import { getMutationState, maybe } from "../../../misc";
import { pageUrl } from "../../../pages/urls";
@ -62,6 +62,9 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
const categorySearch = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const collectionSearch = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const closeModal = () =>
navigate(
@ -100,304 +103,279 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
return (
<SearchPages variables={DEFAULT_INITIAL_SEARCH_DATA}>
{pageSearch => (
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
{collectionSearch => (
<MenuDetailsQuery displayLoader variables={{ id }}>
{({ data, loading, refetch }) => {
const handleQueryChange = (query: string) => {
categorySearch.search(query);
collectionSearch.search(query);
pageSearch.search(query);
};
<MenuDetailsQuery displayLoader variables={{ id }}>
{({ data, loading, refetch }) => {
const handleQueryChange = (query: string) => {
categorySearch.search(query);
collectionSearch.search(query);
pageSearch.search(query);
};
const categories = maybe(
() =>
categorySearch.result.data.search.edges.map(
edge => edge.node
),
[]
);
const categories = maybe(
() =>
categorySearch.result.data.search.edges.map(edge => edge.node),
[]
);
const collections = maybe(
() =>
collectionSearch.result.data.search.edges.map(
edge => edge.node
),
[]
);
const collections = maybe(
() =>
collectionSearch.result.data.search.edges.map(
edge => edge.node
),
[]
);
const pages = maybe(
() =>
pageSearch.result.data.search.edges.map(edge => edge.node),
[]
);
const pages = maybe(
() => pageSearch.result.data.search.edges.map(edge => edge.node),
[]
);
return (
<MenuDeleteMutation
return (
<MenuDeleteMutation
onCompleted={data => handleDelete(data, navigate, notify, intl)}
>
{(menuDelete, menuDeleteOpts) => (
<MenuUpdateMutation
onCompleted={data =>
handleDelete(data, navigate, notify, intl)
handleUpdate(data, notify, refetch, intl)
}
>
{(menuDelete, menuDeleteOpts) => (
<MenuUpdateMutation
onCompleted={data =>
handleUpdate(data, notify, refetch, intl)
}
>
{(menuUpdate, menuUpdateOpts) => {
const deleteState = getMutationState(
menuDeleteOpts.called,
menuDeleteOpts.loading,
maybe(() => menuDeleteOpts.data.menuDelete.errors)
);
{(menuUpdate, menuUpdateOpts) => {
const deleteState = getMutationState(
menuDeleteOpts.called,
menuDeleteOpts.loading,
maybe(() => menuDeleteOpts.data.menuDelete.errors)
);
const updateState = getMutationState(
menuUpdateOpts.called,
menuUpdateOpts.loading,
maybe(() => menuUpdateOpts.data.menuUpdate.errors),
maybe(() => menuUpdateOpts.data.menuItemMove.errors)
);
const updateState = getMutationState(
menuUpdateOpts.called,
menuUpdateOpts.loading,
maybe(() => menuUpdateOpts.data.menuUpdate.errors),
maybe(() => menuUpdateOpts.data.menuItemMove.errors)
);
// This is a workaround to let know <MenuDetailsPage />
// that it should clean operation stack if mutations
// were successful
const handleSubmit = async (
data: MenuDetailsSubmitData
) => {
try {
const result = await menuUpdate({
variables: {
id,
moves: getMoves(data),
name: data.name,
removeIds: getRemoveIds(data)
}
});
if (result) {
if (
result.data.menuItemBulkDelete.errors.length >
0 ||
result.data.menuItemMove.errors.length > 0 ||
result.data.menuUpdate.errors.length > 0
) {
return false;
}
}
return true;
} catch {
// This is a workaround to let know <MenuDetailsPage />
// that it should clean operation stack if mutations
// were successful
const handleSubmit = async (
data: MenuDetailsSubmitData
) => {
try {
const result = await menuUpdate({
variables: {
id,
moves: getMoves(data),
name: data.name,
removeIds: getRemoveIds(data)
}
});
if (result) {
if (
result.data.menuItemBulkDelete.errors.length >
0 ||
result.data.menuItemMove.errors.length > 0 ||
result.data.menuUpdate.errors.length > 0
) {
return false;
}
};
}
return true;
} catch {
return false;
}
};
return (
<>
<MenuDetailsPage
disabled={loading}
menu={maybe(() => data.menu)}
onBack={() => navigate(menuListUrl())}
onDelete={() =>
navigate(
menuUrl(id, {
action: "remove"
})
return (
<>
<MenuDetailsPage
disabled={loading}
menu={maybe(() => data.menu)}
onBack={() => navigate(menuListUrl())}
onDelete={() =>
navigate(
menuUrl(id, {
action: "remove"
})
)
}
onItemAdd={() =>
navigate(
menuUrl(id, {
action: "add-item"
})
)
}
onItemClick={handleItemClick}
onItemEdit={itemId =>
navigate(
menuUrl(id, {
action: "edit-item",
id: itemId
})
)
}
onSubmit={handleSubmit}
saveButtonState={updateState}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={deleteState}
onConfirm={() => menuDelete({ variables: { id } })}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuDetailsDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete menu {menuName}?"
id="menuDetailsDeleteMenuContent"
values={{
menuName: (
<strong>
{maybe(() => data.menu.name, "...")}
</strong>
)
}
onItemAdd={() =>
navigate(
menuUrl(id, {
action: "add-item"
})
)
}
onItemClick={handleItemClick}
onItemEdit={itemId =>
navigate(
menuUrl(id, {
action: "edit-item",
id: itemId
})
)
}
onSubmit={handleSubmit}
saveButtonState={updateState}
}}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={deleteState}
onConfirm={() =>
menuDelete({ variables: { id } })
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuDetailsDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete menu {menuName}?"
id="menuDetailsDeleteMenuContent"
values={{
menuName: (
<strong>
{maybe(() => data.menu.name, "...")}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</DialogContentText>
</ActionDialog>
<MenuItemCreateMutation
onCompleted={data =>
handleItemCreate(
data,
notify,
closeModal,
intl
)
}
>
{(menuItemCreate, menuItemCreateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemCreateVariables = {
input: getMenuItemCreateInputData(
id,
data
)
};
<MenuItemCreateMutation
onCompleted={data =>
handleItemCreate(data, notify, closeModal, intl)
}
>
{(menuItemCreate, menuItemCreateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemCreateVariables = {
input: getMenuItemCreateInputData(id, data)
};
menuItemCreate({ variables });
};
menuItemCreate({ variables });
};
const formTransitionState = getMutationState(
menuItemCreateOpts.called,
menuItemCreateOpts.loading,
maybe(
() =>
menuItemCreateOpts.data.menuItemCreate
.errors
)
);
const formTransitionState = getMutationState(
menuItemCreateOpts.called,
menuItemCreateOpts.loading,
maybe(
() =>
menuItemCreateOpts.data.menuItemCreate
.errors
)
);
return (
<MenuItemDialog
open={params.action === "add-item"}
categories={categories}
collections={collections}
errors={maybe(
() =>
menuItemCreateOpts.data.menuItemCreate
.errors,
[]
)}
pages={pages}
loading={
categorySearch.result.loading ||
collectionSearch.result.loading
}
confirmButtonState={formTransitionState}
disabled={menuItemCreateOpts.loading}
onClose={closeModal}
onSubmit={handleSubmit}
onQueryChange={handleQueryChange}
/>
);
}}
</MenuItemCreateMutation>
<MenuItemUpdateMutation
onCompleted={data =>
handleItemUpdate(
data,
id,
navigate,
notify,
intl
)
}
>
{(menuItemUpdate, menuItemUpdateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemUpdateVariables = {
id: params.id,
input: getMenuItemInputData(data)
};
return (
<MenuItemDialog
open={params.action === "add-item"}
categories={categories}
collections={collections}
errors={maybe(
() =>
menuItemCreateOpts.data.menuItemCreate
.errors,
[]
)}
pages={pages}
loading={
categorySearch.result.loading ||
collectionSearch.result.loading
}
confirmButtonState={formTransitionState}
disabled={menuItemCreateOpts.loading}
onClose={closeModal}
onSubmit={handleSubmit}
onQueryChange={handleQueryChange}
/>
);
}}
</MenuItemCreateMutation>
<MenuItemUpdateMutation
onCompleted={data =>
handleItemUpdate(data, id, navigate, notify, intl)
}
>
{(menuItemUpdate, menuItemUpdateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemUpdateVariables = {
id: params.id,
input: getMenuItemInputData(data)
};
menuItemUpdate({ variables });
};
menuItemUpdate({ variables });
};
const menuItem = maybe(() =>
getNode(
data.menu.items,
findNode(data.menu.items, params.id)
)
);
const menuItem = maybe(() =>
getNode(
data.menu.items,
findNode(data.menu.items, params.id)
)
);
const formTransitionState = getMutationState(
menuItemUpdateOpts.called,
menuItemUpdateOpts.loading,
maybe(
() =>
menuItemUpdateOpts.data.menuItemUpdate
.errors
)
);
const formTransitionState = getMutationState(
menuItemUpdateOpts.called,
menuItemUpdateOpts.loading,
maybe(
() =>
menuItemUpdateOpts.data.menuItemUpdate
.errors
)
);
const initialFormData: MenuItemDialogFormData = {
id: maybe(() => getItemId(menuItem)),
name: maybe(() => menuItem.name, "..."),
type: maybe<MenuItemType>(
() => getItemType(menuItem),
"category"
)
};
const initialFormData: MenuItemDialogFormData = {
id: maybe(() => getItemId(menuItem)),
name: maybe(() => menuItem.name, "..."),
type: maybe<MenuItemType>(
() => getItemType(menuItem),
"category"
)
};
return (
<MenuItemDialog
open={params.action === "edit-item"}
categories={categories}
collections={collections}
errors={maybe(
() =>
menuItemUpdateOpts.data.menuItemUpdate
.errors,
[]
)}
pages={pages}
initial={initialFormData}
initialDisplayValue={getInitialDisplayValue(
menuItem
)}
loading={
categorySearch.result.loading ||
collectionSearch.result.loading
}
confirmButtonState={formTransitionState}
disabled={menuItemUpdateOpts.loading}
onClose={closeModal}
onSubmit={handleSubmit}
onQueryChange={handleQueryChange}
/>
);
}}
</MenuItemUpdateMutation>
</>
);
}}
</MenuUpdateMutation>
)}
</MenuDeleteMutation>
);
}}
</MenuDetailsQuery>
)}
</SearchCollections>
return (
<MenuItemDialog
open={params.action === "edit-item"}
categories={categories}
collections={collections}
errors={maybe(
() =>
menuItemUpdateOpts.data.menuItemUpdate
.errors,
[]
)}
pages={pages}
initial={initialFormData}
initialDisplayValue={getInitialDisplayValue(
menuItem
)}
loading={
categorySearch.result.loading ||
collectionSearch.result.loading
}
confirmButtonState={formTransitionState}
disabled={menuItemUpdateOpts.loading}
onClose={closeModal}
onSubmit={handleSubmit}
onQueryChange={handleQueryChange}
/>
);
}}
</MenuItemUpdateMutation>
</>
);
}}
</MenuUpdateMutation>
)}
</MenuDeleteMutation>
);
}}
</MenuDetailsQuery>
)}
</SearchPages>
);

View file

@ -13,7 +13,6 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard";
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import useFormset from "@saleor/hooks/useFormset";
@ -25,6 +24,7 @@ import {
ProductType
} from "@saleor/products/utils/data";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { FetchMoreProps, UserError } from "../../../types";

View file

@ -12,13 +12,13 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard";
import { SearchCollections_search_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import useFormset from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
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, UserError } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";

View file

@ -7,8 +7,8 @@ import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import SearchCollections from "../../containers/SearchCollections";
import { decimal, getMutationState, maybe } from "../../misc";
import ProductCreatePage, {
ProductCreatePageSubmitData
@ -33,164 +33,159 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = () => {
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
loadMore: loadMoreCollections,
search: searchCollection,
result: searchCollectionOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const handleBack = () => navigate(productListUrl());
return (
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
<SearchProductTypes variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({
loadMore: loadMoreCollections,
search: searchCollection,
result: searchCollectionOpts
}) => (
<SearchProductTypes variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({
loadMore: loadMoreProductTypes,
search: searchProductTypes,
result: searchProductTypesOpts
}) => {
const handleSuccess = (data: ProductCreate) => {
if (data.productCreate.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Product created"
})
});
navigate(productUrl(data.productCreate.product.id));
} else {
const attributeError = data.productCreate.errors.find(
err => err.field === "attributes"
);
if (!!attributeError) {
notify({ text: attributeError.message });
}
}
};
return (
<TypedProductCreateMutation onCompleted={handleSuccess}>
{(
productCreate,
{
called: productCreateCalled,
data: productCreateData,
loading: productCreateDataLoading
}
) => {
const handleSubmit = (
formData: ProductCreatePageSubmitData
) => {
productCreate({
variables: {
attributes: formData.attributes.map(attribute => ({
id: attribute.id,
values: attribute.value
})),
basePrice: decimal(formData.basePrice),
category: formData.category,
chargeTaxes: formData.chargeTaxes,
collections: formData.collections,
descriptionJson: JSON.stringify(formData.description),
isPublished: formData.isPublished,
name: formData.name,
productType: formData.productType,
publicationDate:
formData.publicationDate !== ""
? formData.publicationDate
: null,
seo: {
description: formData.seoDescription,
title: formData.seoTitle
},
sku: formData.sku,
stockQuantity:
formData.stockQuantity !== null
? formData.stockQuantity
: 0
}
});
};
const formTransitionState = getMutationState(
productCreateCalled,
productCreateDataLoading,
maybe(() => productCreateData.productCreate.errors)
);
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Product",
description: "window title"
})}
/>
<ProductCreatePage
currency={maybe(() => shop.defaultCurrency)}
categories={maybe(
() => searchCategoryOpts.data.search.edges,
[]
).map(edge => edge.node)}
collections={maybe(
() => searchCollectionOpts.data.search.edges,
[]
).map(edge => edge.node)}
disabled={productCreateDataLoading}
errors={maybe(
() => productCreateData.productCreate.errors,
[]
)}
fetchCategories={searchCategory}
fetchCollections={searchCollection}
fetchProductTypes={searchProductTypes}
header={intl.formatMessage({
defaultMessage: "New Product",
description: "page header"
})}
productTypes={maybe(() =>
searchProductTypesOpts.data.search.edges.map(
edge => edge.node
)
)}
onBack={handleBack}
onSubmit={handleSubmit}
saveButtonBarState={formTransitionState}
fetchMoreCategories={{
hasMore: maybe(
() =>
searchCategoryOpts.data.search.pageInfo
.hasNextPage
),
loading: searchCategoryOpts.loading,
onFetchMore: loadMoreCategories
}}
fetchMoreCollections={{
hasMore: maybe(
() =>
searchCollectionOpts.data.search.pageInfo
.hasNextPage
),
loading: searchCollectionOpts.loading,
onFetchMore: loadMoreCollections
}}
fetchMoreProductTypes={{
hasMore: maybe(
() =>
searchProductTypesOpts.data.search.pageInfo
.hasNextPage
),
loading: searchProductTypesOpts.loading,
onFetchMore: loadMoreProductTypes
}}
/>
</>
);
}}
</TypedProductCreateMutation>
loadMore: loadMoreProductTypes,
search: searchProductTypes,
result: searchProductTypesOpts
}) => {
const handleSuccess = (data: ProductCreate) => {
if (data.productCreate.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Product created"
})
});
navigate(productUrl(data.productCreate.product.id));
} else {
const attributeError = data.productCreate.errors.find(
err => err.field === "attributes"
);
}}
</SearchProductTypes>
)}
</SearchCollections>
if (!!attributeError) {
notify({ text: attributeError.message });
}
}
};
return (
<TypedProductCreateMutation onCompleted={handleSuccess}>
{(
productCreate,
{
called: productCreateCalled,
data: productCreateData,
loading: productCreateDataLoading
}
) => {
const handleSubmit = (formData: ProductCreatePageSubmitData) => {
productCreate({
variables: {
attributes: formData.attributes.map(attribute => ({
id: attribute.id,
values: attribute.value
})),
basePrice: decimal(formData.basePrice),
category: formData.category,
chargeTaxes: formData.chargeTaxes,
collections: formData.collections,
descriptionJson: JSON.stringify(formData.description),
isPublished: formData.isPublished,
name: formData.name,
productType: formData.productType,
publicationDate:
formData.publicationDate !== ""
? formData.publicationDate
: null,
seo: {
description: formData.seoDescription,
title: formData.seoTitle
},
sku: formData.sku,
stockQuantity:
formData.stockQuantity !== null
? formData.stockQuantity
: 0
}
});
};
const formTransitionState = getMutationState(
productCreateCalled,
productCreateDataLoading,
maybe(() => productCreateData.productCreate.errors)
);
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Product",
description: "window title"
})}
/>
<ProductCreatePage
currency={maybe(() => shop.defaultCurrency)}
categories={maybe(
() => searchCategoryOpts.data.search.edges,
[]
).map(edge => edge.node)}
collections={maybe(
() => searchCollectionOpts.data.search.edges,
[]
).map(edge => edge.node)}
disabled={productCreateDataLoading}
errors={maybe(
() => productCreateData.productCreate.errors,
[]
)}
fetchCategories={searchCategory}
fetchCollections={searchCollection}
fetchProductTypes={searchProductTypes}
header={intl.formatMessage({
defaultMessage: "New Product",
description: "page header"
})}
productTypes={maybe(() =>
searchProductTypesOpts.data.search.edges.map(
edge => edge.node
)
)}
onBack={handleBack}
onSubmit={handleSubmit}
saveButtonBarState={formTransitionState}
fetchMoreCategories={{
hasMore: maybe(
() =>
searchCategoryOpts.data.search.pageInfo.hasNextPage
),
loading: searchCategoryOpts.loading,
onFetchMore: loadMoreCategories
}}
fetchMoreCollections={{
hasMore: maybe(
() =>
searchCollectionOpts.data.search.pageInfo.hasNextPage
),
loading: searchCollectionOpts.loading,
onFetchMore: loadMoreCollections
}}
fetchMoreProductTypes={{
hasMore: maybe(
() =>
searchProductTypesOpts.data.search.pageInfo
.hasNextPage
),
loading: searchProductTypesOpts.loading,
onFetchMore: loadMoreProductTypes
}}
/>
</>
);
}}
</TypedProductCreateMutation>
);
}}
</SearchProductTypes>
);
};
export default ProductUpdate;

View file

@ -15,8 +15,8 @@ import { commonMessages } from "@saleor/intl";
import ProductVariantCreateDialog from "@saleor/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog";
import { ProductVariantBulkCreate } from "@saleor/products/types/ProductVariantBulkCreate";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config";
import SearchCollections from "../../../containers/SearchCollections";
import { getMutationState, maybe } from "../../../misc";
import ProductUpdatePage from "../../components/ProductUpdatePage";
import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
@ -62,6 +62,13 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
loadMore: loadMoreCollections,
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const openModal = (action: ProductUrlDialog) =>
navigate(
@ -71,341 +78,320 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
);
return (
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({
loadMore: loadMoreCollections,
search: searchCollections,
result: searchCollectionsOpts
}) => (
<TypedProductDetailsQuery
displayLoader
require={["product"]}
variables={{ id }}
>
{({ data, loading, refetch }) => {
const handleDelete = () => {
notify({
text: intl.formatMessage({
defaultMessage: "Product removed"
})
});
navigate(productListUrl());
};
const handleUpdate = (data: ProductUpdateMutationResult) => {
if (data.productUpdate.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
} else {
const attributeError = data.productUpdate.errors.find(
err => err.field === "attributes"
);
if (!!attributeError) {
notify({ text: attributeError.message });
}
}
};
<TypedProductDetailsQuery
displayLoader
require={["product"]}
variables={{ id }}
>
{({ data, loading, refetch }) => {
const handleDelete = () => {
notify({
text: intl.formatMessage({
defaultMessage: "Product removed"
})
});
navigate(productListUrl());
};
const handleUpdate = (data: ProductUpdateMutationResult) => {
if (data.productUpdate.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
} else {
const attributeError = data.productUpdate.errors.find(
err => err.field === "attributes"
);
if (!!attributeError) {
notify({ text: attributeError.message });
}
}
};
const handleImageCreate = (data: ProductImageCreate) => {
const imageError = data.productImageCreate.errors.find(
error =>
error.field === ("image" as keyof ProductImageCreateVariables)
const handleImageCreate = (data: ProductImageCreate) => {
const imageError = data.productImageCreate.errors.find(
error =>
error.field === ("image" as keyof ProductImageCreateVariables)
);
if (imageError) {
notify({
text: imageError.message
});
}
};
const handleImageDeleteSuccess = () =>
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
const handleVariantAdd = () => navigate(productVariantAddUrl(id));
const handleBulkProductVariantCreate = (
data: ProductVariantBulkCreate
) => {
if (data.productVariantBulkCreate.errors.length === 0) {
navigate(productUrl(id), true);
refetch();
}
};
const handleBulkProductVariantDelete = (
data: ProductVariantBulkDelete
) => {
if (data.productVariantBulkDelete.errors.length === 0) {
navigate(productUrl(id), true);
reset();
refetch();
}
};
const handleVariantCreatorOpen = () =>
navigate(
productUrl(id, {
...params,
action: "create-variants"
})
);
const product = data ? data.product : undefined;
return (
<ProductUpdateOperations
product={product}
onBulkProductVariantCreate={handleBulkProductVariantCreate}
onBulkProductVariantDelete={handleBulkProductVariantDelete}
onDelete={handleDelete}
onImageCreate={handleImageCreate}
onImageDelete={handleImageDeleteSuccess}
onUpdate={handleUpdate}
>
{({
bulkProductVariantCreate,
bulkProductVariantDelete,
createProductImage,
deleteProduct,
deleteProductImage,
reorderProductImages,
updateProduct,
updateSimpleProduct
}) => {
const handleImageDelete = (id: string) => () =>
deleteProductImage.mutate({ id });
const handleImageEdit = (imageId: string) => () =>
navigate(productImageUrl(id, imageId));
const handleSubmit = createUpdateHandler(
product,
updateProduct.mutate,
updateSimpleProduct.mutate
);
if (imageError) {
notify({
text: imageError.message
});
}
};
const handleImageDeleteSuccess = () =>
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
const handleVariantAdd = () => navigate(productVariantAddUrl(id));
const handleBulkProductVariantCreate = (
data: ProductVariantBulkCreate
) => {
if (data.productVariantBulkCreate.errors.length === 0) {
navigate(productUrl(id), true);
refetch();
}
};
const handleBulkProductVariantDelete = (
data: ProductVariantBulkDelete
) => {
if (data.productVariantBulkDelete.errors.length === 0) {
navigate(productUrl(id), true);
reset();
refetch();
}
};
const handleVariantCreatorOpen = () =>
navigate(
productUrl(id, {
...params,
action: "create-variants"
})
const handleImageUpload = createImageUploadHandler(
id,
createProductImage.mutate
);
const handleImageReorder = createImageReorderHandler(
product,
reorderProductImages.mutate
);
const product = data ? data.product : undefined;
return (
<ProductUpdateOperations
product={product}
onBulkProductVariantCreate={handleBulkProductVariantCreate}
onBulkProductVariantDelete={handleBulkProductVariantDelete}
onDelete={handleDelete}
onImageCreate={handleImageCreate}
onImageDelete={handleImageDeleteSuccess}
onUpdate={handleUpdate}
>
{({
bulkProductVariantCreate,
bulkProductVariantDelete,
createProductImage,
deleteProduct,
deleteProductImage,
reorderProductImages,
updateProduct,
updateSimpleProduct
}) => {
const handleImageDelete = (id: string) => () =>
deleteProductImage.mutate({ id });
const handleImageEdit = (imageId: string) => () =>
navigate(productImageUrl(id, imageId));
const handleSubmit = createUpdateHandler(
product,
updateProduct.mutate,
updateSimpleProduct.mutate
);
const handleImageUpload = createImageUploadHandler(
id,
createProductImage.mutate
);
const handleImageReorder = createImageReorderHandler(
product,
reorderProductImages.mutate
);
const disableFormSave =
createProductImage.opts.loading ||
deleteProduct.opts.loading ||
reorderProductImages.opts.loading ||
updateProduct.opts.loading ||
loading;
const formTransitionState = getMutationState(
updateProduct.opts.called || updateSimpleProduct.opts.called,
updateProduct.opts.loading || updateSimpleProduct.opts.loading,
maybe(() => updateProduct.opts.data.productUpdate.errors),
maybe(() => updateSimpleProduct.opts.data.productUpdate.errors),
maybe(
() =>
updateSimpleProduct.opts.data.productVariantUpdate.errors
)
);
const deleteTransitionState = getMutationState(
deleteProduct.opts.called,
deleteProduct.opts.loading,
maybe(() => deleteProduct.opts.data.productDelete.errors)
);
const disableFormSave =
createProductImage.opts.loading ||
deleteProduct.opts.loading ||
reorderProductImages.opts.loading ||
updateProduct.opts.loading ||
loading;
const formTransitionState = getMutationState(
updateProduct.opts.called ||
updateSimpleProduct.opts.called,
updateProduct.opts.loading ||
updateSimpleProduct.opts.loading,
maybe(() => updateProduct.opts.data.productUpdate.errors),
maybe(
() => updateSimpleProduct.opts.data.productUpdate.errors
),
maybe(
() =>
updateSimpleProduct.opts.data.productVariantUpdate
.errors
)
);
const deleteTransitionState = getMutationState(
deleteProduct.opts.called,
deleteProduct.opts.loading,
maybe(() => deleteProduct.opts.data.productDelete.errors)
);
const bulkProductVariantDeleteTransitionState = getMutationState(
bulkProductVariantDelete.opts.called,
bulkProductVariantDelete.opts.loading,
maybe(
() =>
bulkProductVariantDelete.opts.data.productVariantBulkDelete
.errors
)
);
const bulkProductVariantDeleteTransitionState = getMutationState(
bulkProductVariantDelete.opts.called,
bulkProductVariantDelete.opts.loading,
maybe(
() =>
bulkProductVariantDelete.opts.data
.productVariantBulkDelete.errors
)
);
const categories = maybe(
() => searchCategoriesOpts.data.search.edges,
[]
).map(edge => edge.node);
const collections = maybe(
() => searchCollectionsOpts.data.search.edges,
[]
).map(edge => edge.node);
const errors = maybe(
() => updateProduct.opts.data.productUpdate.errors,
[]
);
const categories = maybe(
() => searchCategoriesOpts.data.search.edges,
[]
).map(edge => edge.node);
const collections = maybe(
() => searchCollectionsOpts.data.search.edges,
[]
).map(edge => edge.node);
const errors = maybe(
() => updateProduct.opts.data.productUpdate.errors,
[]
);
return (
<>
<WindowTitle title={maybe(() => data.product.name)} />
<ProductUpdatePage
categories={categories}
collections={collections}
disabled={disableFormSave}
errors={errors}
fetchCategories={searchCategories}
fetchCollections={searchCollections}
saveButtonBarState={formTransitionState}
images={maybe(() => data.product.images)}
header={maybe(() => product.name)}
placeholderImage={placeholderImg}
product={product}
variants={maybe(() => product.variants)}
onBack={() => {
navigate(productListUrl());
}}
onDelete={() => openModal("remove")}
onProductShow={() => {
if (product) {
window.open(product.url);
}
}}
onImageReorder={handleImageReorder}
onSubmit={handleSubmit}
onVariantAdd={handleVariantAdd}
onVariantsAdd={handleVariantCreatorOpen}
onVariantShow={variantId => () =>
navigate(
productVariantEditUrl(product.id, variantId)
)}
onImageUpload={handleImageUpload}
onImageEdit={handleImageEdit}
onImageDelete={handleImageDelete}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
productUrl(id, {
action: "remove-variants",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
fetchMoreCategories={{
hasMore: maybe(
() =>
searchCategoriesOpts.data.search.pageInfo
.hasNextPage
),
loading: searchCategoriesOpts.loading,
onFetchMore: loadMoreCategories
}}
fetchMoreCollections={{
hasMore: maybe(
() =>
searchCollectionsOpts.data.search.pageInfo
.hasNextPage
),
loading: searchCollectionsOpts.loading,
onFetchMore: loadMoreCollections
}}
/>
<ActionDialog
open={params.action === "remove"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={deleteTransitionState}
onConfirm={() => deleteProduct.mutate({ id })}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Product",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {name}?"
description="delete product"
values={{
name: product ? product.name : undefined
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "remove-variants"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={
bulkProductVariantDeleteTransitionState
}
onConfirm={() =>
bulkProductVariantDelete.mutate({
ids: params.ids
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Product Variants",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this variant} other{{displayQuantity} variants}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ProductVariantCreateDialog
defaultPrice={maybe(() =>
data.product.basePrice.amount.toFixed(2)
)}
errors={maybe(
() =>
bulkProductVariantCreate.opts.data
.productVariantBulkCreate.bulkProductErrors,
[]
)}
open={params.action === "create-variants"}
attributes={maybe(
() => data.product.productType.variantAttributes,
[]
)}
currencySymbol={maybe(() => shop.defaultCurrency)}
onClose={() =>
return (
<>
<WindowTitle title={maybe(() => data.product.name)} />
<ProductUpdatePage
categories={categories}
collections={collections}
disabled={disableFormSave}
errors={errors}
fetchCategories={searchCategories}
fetchCollections={searchCollections}
saveButtonBarState={formTransitionState}
images={maybe(() => data.product.images)}
header={maybe(() => product.name)}
placeholderImage={placeholderImg}
product={product}
variants={maybe(() => product.variants)}
onBack={() => {
navigate(productListUrl());
}}
onDelete={() => openModal("remove")}
onProductShow={() => {
if (product) {
window.open(product.url);
}
}}
onImageReorder={handleImageReorder}
onSubmit={handleSubmit}
onVariantAdd={handleVariantAdd}
onVariantsAdd={handleVariantCreatorOpen}
onVariantShow={variantId => () =>
navigate(productVariantEditUrl(product.id, variantId))}
onImageUpload={handleImageUpload}
onImageEdit={handleImageEdit}
onImageDelete={handleImageDelete}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
productUrl(id, {
...params,
action: undefined
action: "remove-variants",
ids: listElements
})
)
}
onSubmit={inputs =>
bulkProductVariantCreate.mutate({
id,
inputs
})
}
>
<DeleteIcon />
</IconButton>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
fetchMoreCategories={{
hasMore: maybe(
() =>
searchCategoriesOpts.data.search.pageInfo.hasNextPage
),
loading: searchCategoriesOpts.loading,
onFetchMore: loadMoreCategories
}}
fetchMoreCollections={{
hasMore: maybe(
() =>
searchCollectionsOpts.data.search.pageInfo.hasNextPage
),
loading: searchCollectionsOpts.loading,
onFetchMore: loadMoreCollections
}}
/>
<ActionDialog
open={params.action === "remove"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={deleteTransitionState}
onConfirm={() => deleteProduct.mutate({ id })}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Product",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {name}?"
description="delete product"
values={{
name: product ? product.name : undefined
}}
/>
</>
);
}}
</ProductUpdateOperations>
);
}}
</TypedProductDetailsQuery>
)}
</SearchCollections>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "remove-variants"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={bulkProductVariantDeleteTransitionState}
onConfirm={() =>
bulkProductVariantDelete.mutate({
ids: params.ids
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Product Variants",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this variant} other{{displayQuantity} variants}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ProductVariantCreateDialog
defaultPrice={maybe(() =>
data.product.basePrice.amount.toFixed(2)
)}
errors={maybe(
() =>
bulkProductVariantCreate.opts.data
.productVariantBulkCreate.bulkProductErrors,
[]
)}
open={params.action === "create-variants"}
attributes={maybe(
() => data.product.productType.variantAttributes,
[]
)}
currencySymbol={maybe(() => shop.defaultCurrency)}
onClose={() =>
navigate(
productUrl(id, {
...params,
action: undefined
})
)
}
onSubmit={inputs =>
bulkProductVariantCreate.mutate({
id,
inputs
})
}
/>
</>
);
}}
</ProductUpdateOperations>
);
}}
</TypedProductDetailsQuery>
);
};
export default ProductUpdate;

View file

@ -1,7 +1,7 @@
import gql from "graphql-tag";
import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch";
import { pageInfoFragment } from "@saleor/queries";
import TopLevelSearch from "../TopLevelSearch";
import {
SearchCollections,
SearchCollectionsVariables
@ -24,6 +24,7 @@ export const searchCollections = gql`
}
`;
export default TopLevelSearch<SearchCollections, SearchCollectionsVariables>(
searchCollections
);
export default makeTopLevelSearch<
SearchCollections,
SearchCollectionsVariables
>(searchCollections);