Merge pull request #262 from mirumee/ref/hook-searches

Use searches as hooks instead of components
This commit is contained in:
Marcin Gębala 2019-11-21 16:10:22 +01:00 committed by GitHub
commit 29bd1a43a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1812 additions and 1990 deletions

View file

@ -12,6 +12,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Use Apollo Hooks - #254 by @dominik-zeglen
- Fix disappearing products description - #259 by @dominik-zeglen
- Improve mobile appearance - #240 by @benekex2 and @dominik-zeglen
- Use searches as hooks instead of components - #262 by @dominik-zeglen
## 2.0.0

View file

@ -6,6 +6,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import AssignProductDialog from "@saleor/components/AssignProductDialog";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -13,8 +14,7 @@ import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { commonMessages } from "@saleor/intl";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchProducts from "../../containers/SearchProducts";
import useProductSearch from "@saleor/searches/useProductSearch";
import { getMutationState, maybe } from "../../misc";
import { productUrl } from "../../products/urls";
import { CollectionInput } from "../../types/globalTypes";
@ -50,6 +50,9 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
);
const paginate = usePaginator();
const intl = useIntl();
const { search, result } = useProductSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const closeModal = () =>
navigate(
@ -284,29 +287,25 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
toggle={toggle}
toggleAll={toggleAll}
/>
<SearchProducts variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({ search, result }) => (
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign"}
onFetch={search}
loading={result.loading}
onClose={closeModal}
onSubmit={products =>
assignProduct.mutate({
...paginationState,
collectionId: id,
productIds: products.map(product => product.id)
})
}
products={maybe(() =>
result.data.search.edges
.map(edge => edge.node)
.filter(suggestedProduct => suggestedProduct.id)
)}
/>
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign"}
onFetch={search}
loading={result.loading}
onClose={closeModal}
onSubmit={products =>
assignProduct.mutate({
...paginationState,
collectionId: id,
productIds: products.map(product => product.id)
})
}
products={maybe(() =>
result.data.search.edges
.map(edge => edge.node)
.filter(suggestedProduct => suggestedProduct.id)
)}
</SearchProducts>
/>
<ActionDialog
confirmButtonState={removeTransitionState}
onClose={closeModal}

View file

@ -19,7 +19,7 @@ import FormSpacer from "@saleor/components/FormSpacer";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { buttonMessages } from "@saleor/intl";
import { SearchCategories_search_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import Checkbox from "../Checkbox";
export interface FormData {

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

@ -21,7 +21,7 @@ import TableCellAvatar from "@saleor/components/TableCellAvatar";
import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { buttonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { SearchProducts_search_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
import Checkbox from "../Checkbox";
export interface FormData {

View file

@ -1,10 +1,10 @@
import { SearchQueryVariables } from "./containers/BaseSearch";
import { SearchVariables } from "./hooks/makeSearch";
import { ListSettings, ListViews } from "./types";
export const APP_MOUNT_URI = process.env.APP_MOUNT_URI || "/";
export const API_URI = process.env.API_URI || "/graphql/";
export const DEFAULT_INITIAL_SEARCH_DATA: SearchQueryVariables = {
export const DEFAULT_INITIAL_SEARCH_DATA: SearchVariables = {
after: null,
first: 20,
query: ""

View file

@ -1,77 +0,0 @@
import { DocumentNode } from "graphql";
import React from "react";
import Debounce from "../components/Debounce";
import { TypedQuery, TypedQueryResult } from "../queries";
export interface SearchQueryVariables {
after?: string;
first: number;
query: string;
}
interface BaseSearchProps<
TQuery,
TQueryVariables extends SearchQueryVariables
> {
children: (props: {
loadMore: () => void;
search: (query: string) => void;
result: TypedQueryResult<TQuery, TQueryVariables>;
}) => React.ReactElement<any>;
variables: TQueryVariables;
}
function BaseSearch<TQuery, TQueryVariables extends SearchQueryVariables>(
query: DocumentNode,
loadMoreFn: (result: TypedQueryResult<TQuery, TQueryVariables>) => void
) {
const Query = TypedQuery<TQuery, TQueryVariables>(query);
class BaseSearchComponent extends React.Component<
BaseSearchProps<TQuery, TQueryVariables>,
SearchQueryVariables
> {
state: SearchQueryVariables = {
first: this.props.variables.first,
query: this.props.variables.query
};
search = (query: string) => {
if (query === undefined) {
this.setState({ query: "" });
} else {
this.setState({ query });
}
};
render() {
const { children, variables } = this.props;
return (
<Debounce debounceFn={this.search} time={200}>
{search => (
<Query
displayLoader={true}
variables={{
...variables,
query: this.state.query
}}
>
{result =>
children({
loadMore: () => loadMoreFn(result),
result,
search
})
}
</Query>
)}
</Debounce>
);
}
}
return BaseSearchComponent;
}
export default BaseSearch;

View file

@ -8,6 +8,7 @@ import AssignCategoriesDialog from "@saleor/components/AssignCategoryDialog";
import AssignCollectionDialog from "@saleor/components/AssignCollectionDialog";
import AssignProductDialog from "@saleor/components/AssignProductDialog";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -16,12 +17,11 @@ import usePaginator, {
} from "@saleor/hooks/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 useProductSearch from "@saleor/searches/useProductSearch";
import { categoryUrl } from "../../categories/urls";
import { collectionUrl } from "../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchCategories from "../../containers/SearchCategories";
import SearchCollections from "../../containers/SearchCollections";
import SearchProducts from "../../containers/SearchProducts";
import { decimal, getMutationState, joinDateTime, maybe } from "../../misc";
import { productUrl } from "../../products/urls";
import { DiscountValueTypeEnum, SaleType } from "../../types/globalTypes";
@ -66,6 +66,24 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
params.ids
);
const intl = useIntl();
const {
search: searchCategories,
result: searchCategoriesOpts
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchProducts,
result: searchProductsOpts
} = useProductSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const paginationState = createPaginationState(PAGINATE_BY, params);
const changeTab = (tab: SaleDetailsPageTab) => {
@ -341,116 +359,87 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
toggle={toggle}
toggleAll={toggleAll}
/>
<SearchProducts
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchProducts,
result: searchProductsOpts
}) => (
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign-product"}
onFetch={searchProducts}
loading={searchProductsOpts.loading}
onClose={closeModal}
onSubmit={products =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
products: products.map(
product => product.id
)
}
}
})
}
products={maybe(() =>
searchProductsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedProduct => suggestedProduct.id
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign-product"}
onFetch={searchProducts}
loading={searchProductsOpts.loading}
onClose={closeModal}
onSubmit={products =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
products: products.map(
product => product.id
)
)}
/>
)}
</SearchProducts>
<SearchCategories
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchCategories,
result: searchCategoriesOpts
}) => (
<AssignCategoriesDialog
categories={maybe(() =>
searchCategoriesOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory =>
suggestedCategory.id
)
)}
confirmButtonState={assignTransitionState}
open={params.action === "assign-category"}
onFetch={searchCategories}
loading={searchCategoriesOpts.loading}
onClose={closeModal}
onSubmit={categories =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
categories: categories.map(
product => product.id
)
}
}
})
}
}
/>
})
}
products={maybe(() =>
searchProductsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedProduct => suggestedProduct.id
)
)}
</SearchCategories>
<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
/>
<AssignCategoriesDialog
categories={maybe(() =>
searchCategoriesOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory => suggestedCategory.id
)
)}
confirmButtonState={assignTransitionState}
open={params.action === "assign-category"}
onFetch={searchCategories}
loading={searchCategoriesOpts.loading}
onClose={closeModal}
onSubmit={categories =>
saleCataloguesAdd({
variables: {
...paginationState,
id,
input: {
categories: categories.map(
product => product.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

@ -8,6 +8,7 @@ import AssignCategoriesDialog from "@saleor/components/AssignCategoryDialog";
import AssignCollectionDialog from "@saleor/components/AssignCollectionDialog";
import AssignProductDialog from "@saleor/components/AssignProductDialog";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -16,12 +17,11 @@ import usePaginator, {
} from "@saleor/hooks/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 useProductSearch from "@saleor/searches/useProductSearch";
import { categoryUrl } from "../../categories/urls";
import { collectionUrl } from "../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchCategories from "../../containers/SearchCategories";
import SearchCollections from "../../containers/SearchCollections";
import SearchProducts from "../../containers/SearchProducts";
import { decimal, getMutationState, joinDateTime, maybe } from "../../misc";
import { productUrl } from "../../products/urls";
import {
@ -68,6 +68,24 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
params.ids
);
const intl = useIntl();
const {
search: searchCategories,
result: searchCategoriesOpts
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
search: searchProducts,
result: searchProductsOpts
} = useProductSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const paginationState = createPaginationState(PAGINATE_BY, params);
const changeTab = (tab: VoucherDetailsPageTab) => {
@ -420,80 +438,60 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
toggle={toggle}
toggleAll={toggleAll}
/>
<SearchCategories
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchCategories,
result: searchCategoriesOpts
}) => (
<AssignCategoriesDialog
categories={maybe(() =>
searchCategoriesOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory =>
suggestedCategory.id
)
)}
confirmButtonState={assignTransitionState}
open={params.action === "assign-category"}
onFetch={searchCategories}
loading={searchCategoriesOpts.loading}
onClose={closeModal}
onSubmit={categories =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
categories: categories.map(
product => product.id
)
}
}
})
}
/>
<AssignCategoriesDialog
categories={maybe(() =>
searchCategoriesOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedCategory => suggestedCategory.id
)
)}
</SearchCategories>
<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-category"}
onFetch={searchCategories}
loading={searchCategoriesOpts.loading}
onClose={closeModal}
onSubmit={categories =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
categories: categories.map(
product => product.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, [])}
@ -517,42 +515,33 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
[]
)}
/>
<SearchProducts
variables={DEFAULT_INITIAL_SEARCH_DATA}
>
{({
search: searchProducts,
result: searchProductsOpts
}) => (
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign-product"}
onFetch={searchProducts}
loading={searchProductsOpts.loading}
onClose={closeModal}
onSubmit={products =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
products: products.map(
product => product.id
)
}
}
})
}
products={maybe(() =>
searchProductsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedProduct => suggestedProduct.id
<AssignProductDialog
confirmButtonState={assignTransitionState}
open={params.action === "assign-product"}
onFetch={searchProducts}
loading={searchProductsOpts.loading}
onClose={closeModal}
onSubmit={products =>
voucherCataloguesAdd({
variables: {
...paginationState,
id,
input: {
products: products.map(
product => product.id
)
)}
/>
}
}
})
}
products={maybe(() =>
searchProductsOpts.data.search.edges
.map(edge => edge.node)
.filter(
suggestedProduct => suggestedProduct.id
)
)}
</SearchProducts>
/>
<ActionDialog
open={
params.action === "unassign-category" &&

View file

@ -16,7 +16,7 @@ export interface LoadMore<TData, TVariables> {
) => Promise<ApolloQueryResult<TData>>;
}
type UseQuery<TData, TVariables> = QueryResult<TData, TVariables> &
export type UseQueryResult<TData, TVariables> = QueryResult<TData, TVariables> &
LoadMore<TData, TVariables>;
type UseQueryOpts<TData, TVariables> = Partial<{
displayLoader: boolean;
@ -26,17 +26,17 @@ type UseQueryOpts<TData, TVariables> = Partial<{
}>;
type UseQueryHook<TData, TVariables> = (
opts: UseQueryOpts<TData, TVariables>
) => UseQuery<TData, TVariables>;
) => UseQueryResult<TData, TVariables>;
function makeQuery<TData, TVariables>(
query: DocumentNode
): UseQueryHook<TData, TVariables> {
function useQuery<TData, TVariables>({
function useQuery({
displayLoader,
require,
skip,
variables
}: UseQueryOpts<TData, TVariables>): UseQuery<TData, TVariables> {
}: UseQueryOpts<TData, TVariables>): UseQueryResult<TData, TVariables> {
const notify = useNotifier();
const intl = useIntl();
const [, dispatchAppState] = useAppState();

57
src/hooks/makeSearch.ts Normal file
View file

@ -0,0 +1,57 @@
import { DocumentNode } from "graphql";
import { useState } from "react";
import { QueryResult } from "react-apollo";
import makeQuery, { UseQueryResult } from "./makeQuery";
import useDebounce from "./useDebounce";
export interface SearchVariables {
after?: string;
first: number;
query: string;
}
export interface UseSearchResult<TData, TVariables extends SearchVariables> {
loadMore: () => void;
result: QueryResult<TData, TVariables>;
search: (query: string) => void;
}
export type UseSearchOpts<TVariables extends SearchVariables> = Partial<{
skip: boolean;
variables: TVariables;
}>;
export type UseSearchHook<TData, TVariables extends SearchVariables> = (
opts: UseSearchOpts<TVariables>
) => UseSearchResult<TData, TVariables>;
function makeSearch<TData, TVariables extends SearchVariables>(
query: DocumentNode,
loadMoreFn: (result: UseQueryResult<TData, TVariables>) => void
): UseSearchHook<TData, TVariables> {
const useSearchQuery = makeQuery<TData, TVariables>(query);
function useSearch(
opts: UseSearchOpts<TVariables>
): UseSearchResult<TData, TVariables> {
const [searchQuery, setSearchQuery] = useState("");
const debouncedSearch = useDebounce(setSearchQuery);
const result = useSearchQuery({
...opts,
displayLoader: true,
variables: {
...opts.variables,
query: searchQuery
}
});
return {
loadMore: () => loadMoreFn(result),
result,
search: debouncedSearch
};
}
return useSearch;
}
export default makeSearch;

View file

@ -1,9 +1,9 @@
import { DocumentNode } from "graphql";
import { PageInfoFragment } from "@saleor/types/PageInfoFragment";
import BaseSearch, { SearchQueryVariables } from "./BaseSearch";
import makeSearch, { SearchVariables, UseSearchHook } from "./makeSearch";
export interface SearchQuery {
export interface SearchData {
search: {
edges: Array<{
node: any;
@ -12,11 +12,11 @@ export interface SearchQuery {
};
}
function TopLevelSearch<
TQuery extends SearchQuery,
TQueryVariables extends SearchQueryVariables
>(query: DocumentNode) {
return BaseSearch<TQuery, TQueryVariables>(query, result => {
function makeTopLevelSearch<
TData extends SearchData,
TVariables extends SearchVariables
>(query: DocumentNode): UseSearchHook<TData, TVariables> {
return makeSearch<TData, TVariables>(query, result => {
if (result.data.search.pageInfo.hasNextPage) {
result.loadMore(
(prev, next) => {
@ -44,4 +44,4 @@ function TopLevelSearch<
});
}
export default TopLevelSearch;
export default makeTopLevelSearch;

19
src/hooks/useDebounce.ts Normal file
View file

@ -0,0 +1,19 @@
import { useEffect, useRef } from "react";
export type UseDebounceFn<T> = (...args: T[]) => void;
function useDebounce<T>(
debounceFn: UseDebounceFn<T>,
time = 200
): UseDebounceFn<T> {
const timer = useRef(null);
useEffect(() => () => clearTimeout(timer.current));
return (...args: T[]) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => debounceFn(...args), time);
};
}
export default useDebounce;

View file

@ -14,13 +14,13 @@ import ConfirmButton, {
ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer";
import { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
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 { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { UserError } from "@saleor/types";
import { getErrors, getFieldError } from "@saleor/utils/errors";
import { getMenuItemByValue, IMenu } from "@saleor/utils/menu";

View file

@ -3,14 +3,14 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
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 usePageSearch from "@saleor/searches/usePageSearch";
import { categoryUrl } from "../../../categories/urls";
import { collectionUrl } from "../../../collections/urls";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config";
import SearchCategories from "../../../containers/SearchCategories";
import SearchCollections from "../../../containers/SearchCollections";
import SearchPages from "../../../containers/SearchPages";
import { getMutationState, maybe } from "../../../misc";
import { pageUrl } from "../../../pages/urls";
import MenuDetailsPage, {
@ -59,6 +59,15 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
const categorySearch = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const collectionSearch = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const pageSearch = usePageSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const closeModal = () =>
navigate(
@ -95,328 +104,267 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
};
return (
<SearchPages variables={DEFAULT_INITIAL_SEARCH_DATA}>
{pageSearch => (
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
{categorySearch => (
<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 => handleUpdate(data, notify, refetch, intl)}
>
{(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)
);
// 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"
})
)
}
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>
<MenuItemCreateMutation
onCompleted={data =>
handleDelete(data, navigate, notify, intl)
handleItemCreate(data, notify, closeModal, 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
)
);
{(menuItemCreate, menuItemCreateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemCreateVariables = {
input: getMenuItemCreateInputData(id, data)
};
const updateState = getMutationState(
menuUpdateOpts.called,
menuUpdateOpts.loading,
maybe(
() => menuUpdateOpts.data.menuUpdate.errors
),
maybe(
() => menuUpdateOpts.data.menuItemMove.errors
)
);
menuItemCreate({ variables });
};
// 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;
}
};
const formTransitionState = getMutationState(
menuItemCreateOpts.called,
menuItemCreateOpts.loading,
maybe(
() =>
menuItemCreateOpts.data.menuItemCreate.errors
)
);
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>
)
}}
/>
</DialogContentText>
</ActionDialog>
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)
};
<MenuItemCreateMutation
onCompleted={data =>
handleItemCreate(
data,
notify,
closeModal,
intl
)
}
>
{(menuItemCreate, menuItemCreateOpts) => {
const handleSubmit = (
data: MenuItemDialogFormData
) => {
const variables: MenuItemCreateVariables = {
input: getMenuItemCreateInputData(
id,
data
)
};
menuItemUpdate({ variables });
};
menuItemCreate({ variables });
};
const menuItem = maybe(() =>
getNode(
data.menu.items,
findNode(data.menu.items, params.id)
)
);
const formTransitionState = getMutationState(
menuItemCreateOpts.called,
menuItemCreateOpts.loading,
maybe(
() =>
menuItemCreateOpts.data
.menuItemCreate.errors
)
);
const formTransitionState = getMutationState(
menuItemUpdateOpts.called,
menuItemUpdateOpts.loading,
maybe(
() =>
menuItemUpdateOpts.data.menuItemUpdate.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)
};
const initialFormData: MenuItemDialogFormData = {
id: maybe(() => getItemId(menuItem)),
name: maybe(() => menuItem.name, "..."),
type: maybe<MenuItemType>(
() => getItemType(menuItem),
"category"
)
};
menuItemUpdate({ variables });
};
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 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>
)}
</SearchCategories>
)}
</SearchPages>
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>
);
};
MenuDetails.displayName = "MenuDetails";

View file

@ -16,10 +16,10 @@ import SingleAutocompleteSelectField from "@saleor/components/SingleAutocomplete
import Skeleton from "@saleor/components/Skeleton";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { buttonMessages } from "@saleor/intl";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { FetchMoreProps, UserPermissionProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { SearchCustomers_search_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
import { customerUrl } from "../../../customers/urls";
import { createHref, maybe } from "../../../misc";
import { OrderDetails_order } from "../../types/OrderDetails";

View file

@ -13,8 +13,8 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import Skeleton from "@saleor/components/Skeleton";
import { sectionNames } from "@saleor/intl";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { FetchMoreProps, UserPermissionProps } from "@saleor/types";
import { SearchCustomers_search_edges_node } from "../../../containers/SearchCustomers/types/SearchCustomers";
import { maybe } from "../../../misc";
import { DraftOrderInput } from "../../../types/globalTypes";
import { OrderDetails_order } from "../../types/OrderDetails";

View file

@ -1,5 +1,5 @@
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { MessageDescriptor } from "react-intl";
import { SearchCustomers_search_edges_node } from "../containers/SearchCustomers/types/SearchCustomers";
import { transformOrderStatus, transformPaymentStatus } from "../misc";
import {
FulfillmentStatus,

View file

@ -1,6 +1,6 @@
import gql from "graphql-tag";
import TopLevelSearch from "../containers/TopLevelSearch";
import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch";
import { TypedQuery } from "../queries";
import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails";
import {
@ -314,7 +314,7 @@ export const searchOrderVariant = gql`
}
}
`;
export const SearchOrderVariant = TopLevelSearch<
export const useOrderVariantSearch = makeTopLevelSearch<
SearchOrderVariantType,
SearchOrderVariantVariables
>(searchOrderVariant);

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { buttonMessages } from "@saleor/intl";
import { maybe, renderCollection } from "@saleor/misc";
import { FetchMoreProps } from "@saleor/types";
import { SearchAttributes_productType_availableAttributes_edges_node } from "../../containers/SearchAttributes/types/SearchAttributes";
import { SearchAttributes_productType_availableAttributes_edges_node } from "../../hooks/useAvailableAttributeSearch/types/SearchAttributes";
const useStyles = makeStyles(theme => ({
actions: {

View file

@ -1,7 +1,7 @@
import {
SearchProductTypes_search_edges_node,
SearchProductTypes_search_edges_node_productAttributes
} from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
} from "@saleor/searches/types/SearchProductTypes";
import { AttributeInputTypeEnum } from "../types/globalTypes";
import { ProductTypeDetails_productType } from "./types/ProductTypeDetails";
import { ProductTypeList_productTypes_edges_node } from "./types/ProductTypeList";

View file

@ -1,7 +1,7 @@
import gql from "graphql-tag";
import makeSearch from "@saleor/hooks/makeSearch";
import { pageInfoFragment } from "@saleor/queries";
import BaseSearch from "../../../containers/BaseSearch";
import {
SearchAttributes,
SearchAttributesVariables
@ -37,24 +37,33 @@ export const searchAttributes = gql`
}
`;
export default BaseSearch<SearchAttributes, SearchAttributesVariables>(
export default makeSearch<SearchAttributes, SearchAttributesVariables>(
searchAttributes,
result =>
result.loadMore(
(prev, next) => ({
...prev,
productType: {
...prev.productType,
availableAttributes: {
...prev.productType.availableAttributes,
edges: [
...prev.productType.availableAttributes.edges,
...next.productType.availableAttributes.edges
],
pageInfo: next.productType.availableAttributes.pageInfo
}
(prev, next) => {
if (
prev.productType.availableAttributes.pageInfo.endCursor ===
next.productType.availableAttributes.pageInfo.endCursor
) {
return prev;
}
}),
return {
...prev,
productType: {
...prev.productType,
availableAttributes: {
...prev.productType.availableAttributes,
edges: [
...prev.productType.availableAttributes.edges,
...next.productType.availableAttributes.edges
],
pageInfo: next.productType.availableAttributes.pageInfo
}
}
};
},
{
after: result.data.productType.availableAttributes.pageInfo.endCursor
}

View file

@ -4,6 +4,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import { attributeUrl } from "@saleor/attributes/urls";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -19,7 +20,7 @@ import ProductTypeDetailsPage, {
ProductTypeForm
} from "../../components/ProductTypeDetailsPage";
import ProductTypeOperations from "../../containers/ProductTypeOperations";
import SearchAttributes from "../../containers/SearchAttributes";
import useAvailableAttributeSearch from "../../hooks/useAvailableAttributeSearch";
import { TypedProductTypeDetailsQuery } from "../../queries";
import { AssignAttribute } from "../../types/AssignAttribute";
import { ProductTypeDelete } from "../../types/ProductTypeDelete";
@ -46,6 +47,12 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
const productAttributeListActions = useBulkActions();
const variantAttributeListActions = useBulkActions();
const intl = useIntl();
const { loadMore, search, result } = useAvailableAttributeSearch({
variables: {
...DEFAULT_INITIAL_SEARCH_DATA,
id
}
});
return (
<ProductTypeUpdateErrors>
@ -330,109 +337,55 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
)
}}
/>
{!dataLoading && (
<SearchAttributes
variables={{
first: 15,
id,
query: ""
}}
>
{({ search, result }) => {
const fetchMore = () =>
result.loadMore(
(prev, next) => {
if (
prev.productType.availableAttributes
.pageInfo.endCursor ===
next.productType.availableAttributes
.pageInfo.endCursor
) {
return prev;
}
return {
...prev,
productType: {
...prev.productType,
availableAttributes: {
...prev.productType.availableAttributes,
edges: [
...prev.productType
.availableAttributes.edges,
...next.productType
.availableAttributes.edges
],
pageInfo:
next.productType.availableAttributes
.pageInfo
}
}
};
},
{
after:
result.data.productType.availableAttributes
.pageInfo.endCursor
}
);
return (
<>
{Object.keys(AttributeTypeEnum).map(key => (
<AssignAttributeDialog
attributes={maybe(() =>
result.data.productType.availableAttributes.edges.map(
edge => edge.node
{!dataLoading &&
Object.keys(AttributeTypeEnum).map(key => (
<AssignAttributeDialog
attributes={maybe(() =>
result.data.productType.availableAttributes.edges.map(
edge => edge.node
)
)}
confirmButtonState={assignTransactionState}
errors={maybe(
() =>
assignAttribute.opts.data.attributeAssign.errors.map(
err => err.message
),
[]
)}
loading={result.loading}
onClose={closeModal}
onSubmit={handleAssignAttribute}
onFetch={search}
onFetchMore={loadMore}
onOpen={result.refetch}
hasMore={maybe(
() =>
result.data.productType.availableAttributes
.pageInfo.hasNextPage,
false
)}
open={
params.action === "assign-attribute" &&
params.type === AttributeTypeEnum[key]
}
selected={maybe(() => params.ids, [])}
onToggle={attributeId => {
const ids = maybe(() => params.ids, []);
navigate(
productTypeUrl(id, {
...params,
ids: ids.includes(attributeId)
? params.ids.filter(
selectedId => selectedId !== attributeId
)
)}
confirmButtonState={assignTransactionState}
errors={maybe(
() =>
assignAttribute.opts.data.attributeAssign.errors.map(
err => err.message
),
[]
)}
loading={result.loading}
onClose={closeModal}
onSubmit={handleAssignAttribute}
onFetch={search}
onFetchMore={fetchMore}
onOpen={result.refetch}
hasMore={maybe(
() =>
result.data.productType
.availableAttributes.pageInfo
.hasNextPage,
false
)}
open={
params.action === "assign-attribute" &&
params.type === AttributeTypeEnum[key]
}
selected={maybe(() => params.ids, [])}
onToggle={attributeId => {
const ids = maybe(() => params.ids, []);
navigate(
productTypeUrl(id, {
...params,
ids: ids.includes(attributeId)
? params.ids.filter(
selectedId =>
selectedId !== attributeId
)
: [...ids, attributeId]
})
);
}}
key={key}
/>
))}
</>
);
}}
</SearchAttributes>
)}
: [...ids, attributeId]
})
);
}}
key={key}
/>
))}
<ProductTypeDeleteDialog
confirmButtonState={deleteTransactionState}
name={maybe(() => data.productType.name, "...")}

View file

@ -13,9 +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 { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
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";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
@ -25,6 +22,9 @@ import {
ProductAttributeValueChoices,
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 { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
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 { SearchCategories_search_edges_node } from "@saleor/containers/SearchCategories/types/SearchCategories";
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

@ -2,13 +2,13 @@ import { RawDraftContentState } from "draft-js";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/containers/SearchProductTypes/types/SearchProductTypes";
import { maybe } from "@saleor/misc";
import {
ProductDetails_product,
ProductDetails_product_collections,
ProductDetails_product_variants
} from "@saleor/products/types/ProductDetails";
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
import { ProductAttributeInput } from "../components/ProductAttributes";
import { VariantAttributeInput } from "../components/ProductVariantAttributes";
import { ProductVariant } from "../types/ProductVariant";

View file

@ -2,13 +2,13 @@ import React from "react";
import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle";
import SearchProductTypes from "@saleor/containers/SearchProductTypes";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import SearchCategories from "../../containers/SearchCategories";
import SearchCollections from "../../containers/SearchCollections";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import useProductTypeSearch from "@saleor/searches/useProductTypeSearch";
import { decimal, getMutationState, maybe } from "../../misc";
import ProductCreatePage, {
ProductCreatePageSubmitData
@ -26,174 +26,152 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = () => {
const notify = useNotifier();
const shop = useShop();
const intl = useIntl();
const {
loadMore: loadMoreCategories,
search: searchCategory,
result: searchCategoryOpts
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
loadMore: loadMoreCollections,
search: searchCollection,
result: searchCollectionOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
loadMore: loadMoreProductTypes,
search: searchProductTypes,
result: searchProductTypesOpts
} = useProductTypeSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const handleBack = () => navigate(productListUrl());
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 (
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({
loadMore: loadMoreCategories,
search: searchCategory,
result: searchCategoryOpts
}) => (
<SearchCollections 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 });
}
}
};
<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
}
});
};
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>
);
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
}}
</SearchProductTypes>
)}
</SearchCollections>
)}
</SearchCategories>
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>
);
};
export default ProductUpdate;

View file

@ -7,6 +7,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import placeholderImg from "@assets/images/placeholder255x255.png";
import ActionDialog from "@saleor/components/ActionDialog";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
@ -14,9 +15,8 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl";
import ProductVariantCreateDialog from "@saleor/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog";
import { ProductVariantBulkCreate } from "@saleor/products/types/ProductVariantBulkCreate";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config";
import SearchCategories from "../../../containers/SearchCategories";
import SearchCollections from "../../../containers/SearchCollections";
import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import { getMutationState, maybe } from "../../../misc";
import ProductUpdatePage from "../../components/ProductUpdatePage";
import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
@ -55,6 +55,20 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
);
const intl = useIntl();
const shop = useShop();
const {
loadMore: loadMoreCategories,
search: searchCategories,
result: searchCategoriesOpts
} = useCategorySearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const {
loadMore: loadMoreCollections,
search: searchCollections,
result: searchCollectionsOpts
} = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const openModal = (action: ProductUrlDialog) =>
navigate(
@ -64,356 +78,320 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
);
return (
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({
loadMore: loadMoreCategories,
search: searchCategories,
result: searchCategoriesOpts
}) => (
<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)
);
if (imageError) {
notify({
text: imageError.message
});
}
};
const handleImageDeleteSuccess = () =>
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
const handleVariantAdd = () =>
navigate(productVariantAddUrl(id));
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 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 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 handleVariantCreatorOpen = () =>
navigate(
productUrl(id, {
...params,
action: "create-variants"
})
);
const product = data ? data.product : undefined;
return (
<ProductUpdateOperations
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 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,
[]
);
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}
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 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,
[]
);
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={() =>
navigate(
productUrl(id, {
...params,
action: undefined
})
)
}
onSubmit={inputs =>
bulkProductVariantCreate.mutate({
id,
inputs
})
}
/>
</>
);
variants={maybe(() => product.variants)}
onBack={() => {
navigate(productListUrl());
}}
</ProductUpdateOperations>
);
}}
</TypedProductDetailsQuery>
)}
</SearchCollections>
)}
</SearchCategories>
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={() =>
navigate(
productUrl(id, {
...params,
action: undefined
})
)
}
onSubmit={inputs =>
bulkProductVariantCreate.mutate({
id,
inputs
})
}
/>
</>
);
}}
</ProductUpdateOperations>
);
}}
</TypedProductDetailsQuery>
);
};
export default ProductUpdate;

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AttributeInputTypeEnum } from "./../../../types/globalTypes";
import { AttributeInputTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: SearchProductTypes
@ -22,7 +22,9 @@ export interface SearchProductTypes_search_edges_node_productAttributes {
slug: string | null;
name: string | null;
valueRequired: boolean;
values: (SearchProductTypes_search_edges_node_productAttributes_values | null)[] | null;
values:
| (SearchProductTypes_search_edges_node_productAttributes_values | null)[]
| null;
}
export interface SearchProductTypes_search_edges_node {
@ -30,7 +32,9 @@ export interface SearchProductTypes_search_edges_node {
id: string;
name: string;
hasVariants: boolean;
productAttributes: (SearchProductTypes_search_edges_node_productAttributes | null)[] | null;
productAttributes:
| (SearchProductTypes_search_edges_node_productAttributes | null)[]
| null;
}
export interface SearchProductTypes_search_edges {

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 {
SearchCategories,
SearchCategoriesVariables
@ -24,6 +24,6 @@ export const searchCategories = gql`
}
`;
export default TopLevelSearch<SearchCategories, SearchCategoriesVariables>(
export default makeTopLevelSearch<SearchCategories, SearchCategoriesVariables>(
searchCategories
);

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);

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 {
SearchCustomers,
SearchCustomersVariables
@ -24,6 +24,6 @@ export const searchCustomers = gql`
}
`;
export default TopLevelSearch<SearchCustomers, SearchCustomersVariables>(
export default makeTopLevelSearch<SearchCustomers, SearchCustomersVariables>(
searchCustomers
);

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 { SearchPages, SearchPagesVariables } from "./types/SearchPages";
export const searchPages = gql`
@ -21,4 +21,6 @@ export const searchPages = gql`
}
`;
export default TopLevelSearch<SearchPages, SearchPagesVariables>(searchPages);
export default makeTopLevelSearch<SearchPages, SearchPagesVariables>(
searchPages
);

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 {
SearchProducts,
SearchProductsVariables
@ -27,6 +27,6 @@ export const searchProducts = gql`
}
`;
export default TopLevelSearch<SearchProducts, SearchProductsVariables>(
export default makeTopLevelSearch<SearchProducts, SearchProductsVariables>(
searchProducts
);

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 {
SearchProductTypes,
SearchProductTypesVariables
@ -41,6 +41,7 @@ export const searchProductTypes = gql`
}
`;
export default TopLevelSearch<SearchProductTypes, SearchProductTypesVariables>(
searchProductTypes
);
export default makeTopLevelSearch<
SearchProductTypes,
SearchProductTypesVariables
>(searchProductTypes);

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 {
SearchServiceAccount,
SearchServiceAccountVariables
@ -28,7 +28,7 @@ export const searchServiceAccount = gql`
}
`;
export default TopLevelSearch<
export default makeTopLevelSearch<
SearchServiceAccount,
SearchServiceAccountVariables
>(searchServiceAccount);

View file

@ -125505,6 +125505,7 @@ exports[`Storyshots Views / Webhooks / Create webhook default 1`] = `
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
name="name"
type="text"
value=""
/>
<fieldset
aria-hidden="true"
@ -126037,6 +126038,7 @@ exports[`Storyshots Views / Webhooks / Create webhook form errors 1`] = `
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
name="name"
type="text"
value=""
/>
<fieldset
aria-hidden="true"
@ -126575,6 +126577,7 @@ exports[`Storyshots Views / Webhooks / Create webhook loading 1`] = `
disabled=""
name="name"
type="text"
value=""
/>
<fieldset
aria-hidden="true"

View file

@ -6,9 +6,9 @@ import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { SearchServiceAccount_search_edges_node } from "@saleor/containers/SearchServiceAccount/types/SearchServiceAccount";
import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { SearchServiceAccount_search_edges_node } from "@saleor/searches/types/SearchServiceAccount";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
@ -19,7 +19,6 @@ import React from "react";
import { useIntl } from "react-intl";
export interface FormData {
id: string;
events: WebhookEventTypeEnum[];
isActive: boolean;
name: string;
@ -52,9 +51,8 @@ const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
const initialForm: FormData = {
allEvents: false,
events: [],
id: null,
isActive: false,
name: null,
name: "",
secretKey: "",
serviceAccount: "",
targetUrl: ""

View file

@ -1,3 +1,6 @@
import React from "react";
import { useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
@ -6,10 +9,10 @@ import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { SearchServiceAccount_search_edges_node } from "@saleor/containers/SearchServiceAccount/types/SearchServiceAccount";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { SearchServiceAccount_search_edges_node } from "@saleor/searches/types/SearchServiceAccount";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
@ -18,11 +21,7 @@ import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate";
import { WebhookDetails_webhook } from "@saleor/webhooks/types/WebhookDetails";
import React from "react";
import { useIntl } from "react-intl";
export interface FormData {
id: string;
events: WebhookEventTypeEnum[];
isActive: boolean;
name: string;
@ -63,7 +62,6 @@ const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
events: maybe(() => webhook.events, [])
.map(event => event.eventType)
.filter(event => event !== WebhookEventTypeEnum.ANY_EVENTS),
id: maybe(() => webhook.id, null),
isActive: maybe(() => webhook.isActive, false),
name: maybe(() => webhook.name, ""),
secretKey: maybe(() => webhook.secretKey, ""),

View file

@ -1,13 +1,13 @@
import { WindowTitle } from "@saleor/components/WindowTitle";
import SearchServiceAccount from "@saleor/containers/SearchServiceAccount";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl";
import useServiceAccountSearch from "@saleor/searches/useServiceAccountSearch";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import { WebhookCreate as WebhookCreateData } from "@saleor/webhooks/types/WebhookCreate";
import React from "react";
import { useIntl } from "react-intl";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import { getMutationState, maybe } from "../../misc";
import WebhookCreatePage, { FormData } from "../components/WebhookCreatePage";
import { TypedWebhookCreate } from "../mutations";
@ -26,6 +26,12 @@ export const WebhooksCreate: React.FC<WebhooksCreateProps> = () => {
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
const {
search: searchServiceAccount,
result: searchServiceAccountOpt
} = useServiceAccountSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const onSubmit = (data: WebhookCreateData) => {
if (data.webhookCreate.webhookErrors.length === 0) {
@ -39,62 +45,56 @@ export const WebhooksCreate: React.FC<WebhooksCreateProps> = () => {
const handleBack = () => navigate(webhooksListUrl());
return (
<SearchServiceAccount variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({ search: searchServiceAccount, result: searchServiceAccountOpt }) => (
<TypedWebhookCreate onCompleted={onSubmit}>
{(webhookCreate, webhookCreateOpts) => {
const handleSubmit = (data: FormData) =>
webhookCreate({
variables: {
input: {
events: data.allEvents
? [WebhookEventTypeEnum.ANY_EVENTS]
: data.events,
isActive: data.isActive,
name: data.name,
secretKey: data.secretKey,
serviceAccount: data.serviceAccount,
targetUrl: data.targetUrl
}
}
});
<TypedWebhookCreate onCompleted={onSubmit}>
{(webhookCreate, webhookCreateOpts) => {
const handleSubmit = (data: FormData) =>
webhookCreate({
variables: {
input: {
events: data.allEvents
? [WebhookEventTypeEnum.ANY_EVENTS]
: data.events,
isActive: data.isActive,
name: data.name,
secretKey: data.secretKey,
serviceAccount: data.serviceAccount,
targetUrl: data.targetUrl
}
}
});
const formTransitionState = getMutationState(
webhookCreateOpts.called,
webhookCreateOpts.loading,
maybe(() => webhookCreateOpts.data.webhookCreate.webhookErrors)
);
const formTransitionState = getMutationState(
webhookCreateOpts.called,
webhookCreateOpts.loading,
maybe(() => webhookCreateOpts.data.webhookCreate.webhookErrors)
);
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Webhook",
description: "window title"
})}
/>
<WebhookCreatePage
disabled={false}
errors={maybe(
() => webhookCreateOpts.data.webhookCreate.webhookErrors,
[]
)}
fetchServiceAccounts={searchServiceAccount}
services={maybe(() =>
searchServiceAccountOpt.data.search.edges.map(
edge => edge.node
)
)}
onBack={handleBack}
onSubmit={handleSubmit}
saveButtonBarState={formTransitionState}
/>
</>
);
}}
</TypedWebhookCreate>
)}
</SearchServiceAccount>
return (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Webhook",
description: "window title"
})}
/>
<WebhookCreatePage
disabled={false}
errors={maybe(
() => webhookCreateOpts.data.webhookCreate.webhookErrors,
[]
)}
fetchServiceAccounts={searchServiceAccount}
services={maybe(() =>
searchServiceAccountOpt.data.search.edges.map(edge => edge.node)
)}
onBack={handleBack}
onSubmit={handleSubmit}
saveButtonBarState={formTransitionState}
/>
</>
);
}}
</TypedWebhookCreate>
);
};
WebhooksCreate.displayName = "WebhooksCreate";

View file

@ -1,15 +1,16 @@
import React from "react";
import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle";
import SearchServiceAccount from "@saleor/containers/SearchServiceAccount";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl";
import useServiceAccountSearch from "@saleor/searches/useServiceAccountSearch";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog";
import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete";
import { WebhookUpdate } from "@saleor/webhooks/types/WebhookUpdate";
import React from "react";
import { useIntl } from "react-intl";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import { getMutationState, maybe } from "../../misc";
import WebhooksDetailsPage from "../components/WebhooksDetailsPage";
import { TypedWebhookDelete, TypedWebhookUpdate } from "../mutations";
@ -33,6 +34,12 @@ export const WebhooksDetails: React.FC<WebhooksDetailsProps> = ({
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
const {
search: searchServiceAccount,
result: searchServiceAccountOpt
} = useServiceAccountSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const closeModal = () =>
navigate(
@ -72,96 +79,92 @@ export const WebhooksDetails: React.FC<WebhooksDetailsProps> = ({
};
return (
<SearchServiceAccount variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({ search: searchServiceAccount, result: searchServiceAccountOpt }) => (
<TypedWebhookUpdate onCompleted={onWebhookUpdate}>
{(webhookUpdate, webhookUpdateOpts) => (
<TypedWebhookDelete onCompleted={onWebhookDelete}>
{(webhookDelete, webhookDeleteOpts) => (
<TypedWebhooksDetailsQuery variables={{ id }}>
{webhookDetails => {
const formTransitionState = getMutationState(
webhookUpdateOpts.called,
webhookUpdateOpts.loading,
maybe(
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors
)
);
<TypedWebhookUpdate onCompleted={onWebhookUpdate}>
{(webhookUpdate, webhookUpdateOpts) => (
<TypedWebhookDelete onCompleted={onWebhookDelete}>
{(webhookDelete, webhookDeleteOpts) => (
<TypedWebhooksDetailsQuery variables={{ id }}>
{webhookDetails => {
const formTransitionState = getMutationState(
webhookUpdateOpts.called,
webhookUpdateOpts.loading,
maybe(
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors
)
);
const handleRemoveConfirm = () =>
webhookDelete({
variables: {
id
}
});
const handleRemoveConfirm = () =>
webhookDelete({
variables: {
id
}
});
const formErrors = maybe(
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors,
[]
);
const formErrors = maybe(
() => webhookUpdateOpts.data.webhookUpdate.webhookErrors,
[]
);
const deleteTransitionState = getMutationState(
webhookDeleteOpts.called,
webhookDeleteOpts.loading,
maybe(() => webhookDeleteOpts.data.webhookDelete.errors)
);
const deleteTransitionState = getMutationState(
webhookDeleteOpts.called,
webhookDeleteOpts.loading,
maybe(() => webhookDeleteOpts.data.webhookDelete.errors)
);
return (
<>
<WindowTitle
title={maybe(() => webhookDetails.data.webhook.name)}
/>
<WebhooksDetailsPage
disabled={webhookDetails.loading}
errors={formErrors}
saveButtonBarState={formTransitionState}
webhook={maybe(() => webhookDetails.data.webhook)}
fetchServiceAccounts={searchServiceAccount}
services={maybe(() =>
searchServiceAccountOpt.data.search.edges.map(
edge => edge.node
)
)}
onBack={() => navigate(webhooksListUrl())}
onDelete={() => openModal("remove")}
onSubmit={data => {
webhookUpdate({
variables: {
id,
input: {
events: data.allEvents
? [WebhookEventTypeEnum.ANY_EVENTS]
: data.events,
isActive: data.isActive,
name: data.name,
secretKey: data.secretKey,
serviceAccount: data.serviceAccount,
targetUrl: data.targetUrl
}
}
});
}}
/>
<WebhookDeleteDialog
confirmButtonState={deleteTransitionState}
name={maybe(
() => webhookDetails.data.webhook.name,
"..."
)}
onClose={closeModal}
onConfirm={handleRemoveConfirm}
open={params.action === "remove"}
/>
</>
);
}}
</TypedWebhooksDetailsQuery>
)}
</TypedWebhookDelete>
return (
<>
<WindowTitle
title={maybe(() => webhookDetails.data.webhook.name)}
/>
<WebhooksDetailsPage
disabled={webhookDetails.loading}
errors={formErrors}
saveButtonBarState={formTransitionState}
webhook={maybe(() => webhookDetails.data.webhook)}
fetchServiceAccounts={searchServiceAccount}
services={maybe(() =>
searchServiceAccountOpt.data.search.edges.map(
edge => edge.node
)
)}
onBack={() => navigate(webhooksListUrl())}
onDelete={() => openModal("remove")}
onSubmit={data => {
webhookUpdate({
variables: {
id,
input: {
events: data.allEvents
? [WebhookEventTypeEnum.ANY_EVENTS]
: data.events,
isActive: data.isActive,
name: data.name,
secretKey: data.secretKey,
serviceAccount: data.serviceAccount,
targetUrl: data.targetUrl
}
}
});
}}
/>
<WebhookDeleteDialog
confirmButtonState={deleteTransitionState}
name={maybe(
() => webhookDetails.data.webhook.name,
"..."
)}
onClose={closeModal}
onConfirm={handleRemoveConfirm}
open={params.action === "remove"}
/>
</>
);
}}
</TypedWebhooksDetailsQuery>
)}
</TypedWebhookUpdate>
</TypedWebhookDelete>
)}
</SearchServiceAccount>
</TypedWebhookUpdate>
);
};
WebhooksDetails.displayName = "WebhooksDetails";