Multichannel (#833)
* add multichannel to configuration view * create multichannels list view * create multichannels list view * add ChannelsCreate view * update channels in configuration * add stories * update default messages * fix ChannelForm props * update channels list styles * update snapshots * update channel form currency input * update Channels fragments * extract messages * remove tabs from channelsList * channel details, channel delete modal (#598) * create Channel details view * create ChannelDeleteDialog * add channels delete dialog to channels list * update messages and types * fixes after review * channels availability (#609) * create Channel details view * update messages and types * create ChannelsAvailability component * create more product channels components * create channels stories, update fixtures, types * update product views with channels data * update schema and snapshots * update defaultMessages * update ProductUpdate view * create ChannelsAvailabilityDropdown component * add product channels to local storage * update globalTypes * Update to new schema and resolve issues * Update messages * create deleteChannel mutation * add channels availability component to product create view * refactor ProductCreate and ProductUpdate views * CollectionProducts view cleanup * add disabled prop to ActionDialog * use updateChannels mutation in ProductCreate view * ProductCreate - update submit function * fixes after review * update snapshots and messages Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> * channels shipping components (#655) * create PricingCard, OrderValue and OrderWeight components * create ShippingZoneRatesPage and DeleteShippingRateDialog * update ChannelsAvailability component * updates after review * channels shipping views (#662) * update ChannelsAvailability component * updates after review * create PriceRate views, update types * create weight rates views * update shipping views, stories, messages * update snapshots * update snapshots * update useChannels hook * orders channels components (#698) * create OrderChannelSectionCard component * update OrderDetailsPage * update DraftOrderChannelCard * update snapshots * update fixtures * small change after review, update snapshots * product pricing (#702) * update product types * update Pricing in simple product view * use productVariantCreate mutation in simple product view * update snapshots and messages * handle create variant (#715) * update product types * update Pricing in simple product view * handle product create and update errors * update snapshots and messages * fix update and create product handlers * update pricing types * channels modal - new styles, search input (#717) * update product types * update Pricing in simple product view * handle product create and update errors * update pricing types * add search input in ChannelsAvailabilityDialog * update ChannelsAvailabilityDialog in all views * update snapshots * fix search input label styles * update toggleAllChannels function * update variant creator (#724) * update product types * update Pricing in simple product view * handle product create and update errors * update pricing types * add search input in ChannelsAvailabilityDialog * update ChannelsAvailabilityDialog in all views * update snapshots * add channelLisitngs to variant creator * update variant creator price styles * update product variant creator reducer tests * update createVariants tests * update error handling in product variant creator * add Skip pricing for now option * use PriceField instead of TextField in ProductVariantCreatorSummary * create price validation function * fix errors handling in ProductVariantPrice component * fixes after review * Product List - remove publish/unpublish buttons (#727) * ProductList - remove publish and unpublish buttons * update messages * update snapshots, messages * revert changes in ChannelsAvailabilityDropdown * products/shipping/discount list settings (#739) * create ChannelSettingsDialog component * update snapshots * ProductList - open settings modal when there is no selected channel * add settings modal to vouchers list * add settings dialog to sales list * add setting modal to shipping list * update shipping * update snapshots, messages * useChannelsSettings - remove selectedChannelSlug * fix channels update handler in product and shipping view * messages update in ChannelSettingsDialog * handle product/discount list when there is no channels * update onSettingsOpen prop * collection availability dropdown (#743) * add availability dropdown to collection products list * update channelListingProduct fragment name * update voucher view/components with channels (#746) * update voucher view/components with channels * update VoucherSummary, remove defaultCurrency from voucher components * update snapshots * move getChannelsVariables func to discounts handlers * update voucher messages * sale view/components with channels (#750) * update sale views with channels * small fixes in discounts * order views with channels (#752) * update draft orders with channels * add channel activate/deactivate mutations * remove sort by total in orders list * add error notification on channel activate/deactivate * product variants channel select (#755) * add channels selector to ProductVariants component * remove selectedChannel from ProductUpdate, update messages and snapshots * update product fragments * update translations (#762) * update translations * fix translation types * update messages * update Availability component (#766) * update ChannelsAvailability component * update product fixtures * update collection and channel fixtures * ChannelsAvailability - handle errors * update product handlers * update ChannelsAvailability styles * update ProductVariant * update snapshots * fix missing things in multichannel (#785) * add availability dropdown to discount products list * fix error handling in shipping components * update product views and components * update messages * update category view/components * update CategoryProducts styles * remove defaultCurrency from shipping components * create ChannelsSelect component * update channels error handling after review * another fixes after review * Add channels to collection views/components (#791) ** update collection components and views * update create collection view * update error handling in collection * remove filter bar from collection list * update products fragments * small fix in collection create view * use collectionFragment in useCatalogSearch * update defaultMessages and snapshots * update homepage view/drop defaultCurrency (#801) * update homepage view * drop defaultCurrency prop * fix onChannelChange function in home view * remove visibility from product list filters * update export products with channels (#803) * update ProductExportDialog with channels * add new channel error code * remover VISIBLE from product export dialog Financial information * fix input size in ProductVariantCreatorSummary (#804) * channels currency code select (#806) * create select with currency codes * fix ChannelDeleteDialog * update defaultMessages, remove unneeded ChannelDetails handlers * fixes after rebase * replace channelListing with channelListings * [multichannel] Update schema] * Fix product create test Co-authored-by: AlicjaSzu <alicja.szukuc@gmail.com> Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com>
This commit is contained in:
parent
596ba1f63b
commit
6f9060144a
490 changed files with 53395 additions and 19298 deletions
|
@ -10,5 +10,6 @@ export const PRODUCTS_SELECTORS = {
|
|||
firstCategoryItem: "#downshift-0-item-0",
|
||||
visibleRadioBtn: "[name='isPublished']",
|
||||
saveBtn: "[data-test='button-bar-confirm']",
|
||||
confirmationMsg: "[data-test='notification']"
|
||||
confirmationMsg: "[data-test='notification']",
|
||||
channelAvailabilityItem: "[data-test='channel-availability-item']"
|
||||
};
|
||||
|
|
5
cypress/elements/shared/button-selectors.js
Normal file
5
cypress/elements/shared/button-selectors.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* eslint-disable sort-keys */
|
||||
export const BUTTON_SELECTORS = {
|
||||
back: '[data-test="back"]',
|
||||
submit: '[data-test="submit"]'
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import { LEFT_MENU_SELECTORS } from "../elements/account/left-menu/left-menu-selectors";
|
||||
import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors";
|
||||
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
|
||||
|
||||
// <reference types="cypress" />
|
||||
describe("Products", () => {
|
||||
|
@ -13,6 +14,8 @@ describe("Products", () => {
|
|||
.click()
|
||||
.get(PRODUCTS_SELECTORS.products)
|
||||
.click()
|
||||
.get(BUTTON_SELECTORS.submit)
|
||||
.click()
|
||||
.get(PRODUCTS_SELECTORS.createProductBtn)
|
||||
.click()
|
||||
.get(PRODUCTS_SELECTORS.productNameInput)
|
||||
|
@ -33,6 +36,9 @@ describe("Products", () => {
|
|||
.get(PRODUCTS_SELECTORS.categoryItem)
|
||||
.first()
|
||||
.click()
|
||||
.get(PRODUCTS_SELECTORS.channelAvailabilityItem)
|
||||
.first()
|
||||
.click()
|
||||
.get(PRODUCTS_SELECTORS.visibleRadioBtn)
|
||||
.first()
|
||||
.click()
|
||||
|
|
File diff suppressed because it is too large
Load diff
60
package-lock.json
generated
60
package-lock.json
generated
|
@ -9496,6 +9496,15 @@
|
|||
"integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=",
|
||||
"dev": true
|
||||
},
|
||||
"currency-codes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/currency-codes/-/currency-codes-2.1.0.tgz",
|
||||
"integrity": "sha512-aASwFNP8VjZ0y0PWlSW7c9N/isYTLxK6OCbm7aVuQMk7dWO2zgup9KGiFQgeL9OGL5P/ulvCHcjQizmuEeZXtw==",
|
||||
"requires": {
|
||||
"first-match": "~0.0.1",
|
||||
"nub": "~0.0.0"
|
||||
}
|
||||
},
|
||||
"cyclist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
||||
|
@ -12260,6 +12269,11 @@
|
|||
"resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz",
|
||||
"integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw=="
|
||||
},
|
||||
"first-match": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/first-match/-/first-match-0.0.1.tgz",
|
||||
"integrity": "sha1-pg7GQnAPD0NyNOu37D84JHblQv0="
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
||||
|
@ -12566,8 +12580,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -12585,13 +12598,11 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -12604,18 +12615,15 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -12718,8 +12726,7 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -12729,7 +12736,6 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -12742,20 +12748,17 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -12772,7 +12775,6 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -12845,8 +12847,7 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -12856,7 +12857,6 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -12932,8 +12932,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -12963,7 +12962,6 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -12981,7 +12979,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -13020,13 +13017,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -17570,6 +17565,11 @@
|
|||
"boolbase": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"nub": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nub/-/nub-0.0.0.tgz",
|
||||
"integrity": "sha1-s2m9Mr3eZq9ZYFw7BSC8IZ3MwE8="
|
||||
},
|
||||
"num2fraction": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"apollo-upload-client": "^9.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"crc-32": "^1.2.0",
|
||||
"currency-codes": "^2.1.0",
|
||||
"downshift": "^1.31.16",
|
||||
"draft-js": "^0.10.5",
|
||||
"draftail": "^1.2.1",
|
||||
|
|
483
schema.graphql
483
schema.graphql
File diff suppressed because it is too large
Load diff
|
@ -62,7 +62,7 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
|||
const navigate = useNavigator();
|
||||
const { updateListSettings, settings } = useListSettings(ListViews.APPS_LIST);
|
||||
const paginate = usePaginator();
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const paginationState = createPaginationState(settings?.rowNumber, params);
|
||||
const queryVariables = {
|
||||
sort: {
|
||||
direction: OrderDirection.DESC,
|
||||
|
|
|
@ -35,7 +35,6 @@ export interface AttributeListPageProps
|
|||
}
|
||||
|
||||
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||
currencySymbol,
|
||||
filterOpts,
|
||||
initialSearch,
|
||||
onAdd,
|
||||
|
@ -73,7 +72,6 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
|||
defaultMessage: "All Attributes",
|
||||
description: "tab name"
|
||||
})}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
filterStructure={structure}
|
||||
initialSearch={initialSearch}
|
||||
|
|
|
@ -19,7 +19,6 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import useShop from "@saleor/hooks/useShop";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
|
@ -52,7 +51,6 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
const navigate = useNavigator();
|
||||
const paginate = usePaginator();
|
||||
const notify = useNotifier();
|
||||
const shop = useShop();
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
|
@ -145,13 +143,11 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
);
|
||||
|
||||
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||
|
||||
return (
|
||||
<>
|
||||
<AttributeListPage
|
||||
attributes={maybe(() => data.attributes.edges.map(edge => edge.node))}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||
filterOpts={getFilterOpts(params)}
|
||||
|
|
|
@ -3,11 +3,11 @@ import TableBody from "@material-ui/core/TableBody";
|
|||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Money from "@saleor/components/Money";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
|
@ -16,12 +16,9 @@ import TablePagination from "@saleor/components/TablePagination";
|
|||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import {
|
||||
CategoryDetails_category_products_edges_node,
|
||||
CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted
|
||||
} from "../../types/CategoryDetails";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -74,11 +71,14 @@ const useStyles = makeStyles(
|
|||
);
|
||||
|
||||
interface CategoryProductListProps extends ListProps, ListActions {
|
||||
channelsCount: number;
|
||||
selectedChannel: string;
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
}
|
||||
|
||||
export const CategoryProductList: React.FC<CategoryProductListProps> = props => {
|
||||
const {
|
||||
channelsCount,
|
||||
disabled,
|
||||
isChecked,
|
||||
pageInfo,
|
||||
|
@ -89,59 +89,14 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
|||
toolbar,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick
|
||||
onRowClick,
|
||||
selectedChannel
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const getProductPrice = (
|
||||
priceRangeUndiscounted: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted
|
||||
) => {
|
||||
if (!priceRangeUndiscounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { start, stop } = priceRangeUndiscounted;
|
||||
const {
|
||||
gross: { amount: startAmount }
|
||||
} = start;
|
||||
const {
|
||||
gross: { amount: stopAmount }
|
||||
} = stop;
|
||||
|
||||
if (startAmount === stopAmount) {
|
||||
return (
|
||||
<Money
|
||||
money={{
|
||||
amount: startAmount,
|
||||
currency: start.gross.currency
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Money
|
||||
money={{
|
||||
amount: startAmount,
|
||||
currency: start.gross.currency
|
||||
}}
|
||||
/>
|
||||
{" - "}
|
||||
<Money
|
||||
money={{
|
||||
amount: stopAmount,
|
||||
currency: stop.gross.currency
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.tableContainer}>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
|
@ -173,8 +128,8 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
|||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product status"
|
||||
defaultMessage="Availability"
|
||||
description="availability status"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
|
@ -202,6 +157,9 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
|||
products,
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
const channel = product?.channelListings.find(
|
||||
listing => listing.channel.id === selectedChannel
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
|
@ -233,30 +191,21 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
|||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{product &&
|
||||
maybe(() => product.isAvailable !== undefined) ? (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isAvailable
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product",
|
||||
id: "productStatusLabel"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product"
|
||||
})
|
||||
}
|
||||
status={product.isAvailable ? "success" : "error"}
|
||||
{product && !product?.channelListings?.length ? (
|
||||
"-"
|
||||
) : product?.channelListings !== undefined ? (
|
||||
<ChannelsAvailabilityDropdown
|
||||
allChannelsCount={channelsCount}
|
||||
currentChannel={channel || product?.channelListings[0]}
|
||||
channels={product?.channelListings}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
{product?.pricing?.priceRangeUndiscounted ? (
|
||||
getProductPrice(product?.pricing?.priceRangeUndiscounted)
|
||||
{product?.channelListings ? (
|
||||
<Money money={channel?.discountedPrice} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ChannelsSelect } from "@saleor/components/ChannelsSelect";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -10,10 +15,23 @@ import CategoryProductList from "../CategoryProductList";
|
|||
|
||||
interface CategoryProductsProps extends PageListProps, ListActions {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
channelChoices: SingleAutocompleteChoiceType[];
|
||||
channelsCount: number;
|
||||
categoryName: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
channelsSelectContainer: {
|
||||
paddingTop: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "CategoryProducts" }
|
||||
);
|
||||
|
||||
export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
||||
channelChoices,
|
||||
channelsCount,
|
||||
products,
|
||||
disabled,
|
||||
pageInfo,
|
||||
|
@ -29,6 +47,11 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
|||
toolbar
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
const [channelChoice, setChannelChoice] = useStateFromProps(
|
||||
channelChoices?.length ? channelChoices[0]?.value : ""
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
@ -49,7 +72,16 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
|||
</Button>
|
||||
}
|
||||
/>
|
||||
<CardContent className={classes.channelsSelectContainer}>
|
||||
<ChannelsSelect
|
||||
channelChoice={channelChoice}
|
||||
channelChoices={channelChoices}
|
||||
setChannelChoice={setChannelChoice}
|
||||
/>
|
||||
</CardContent>
|
||||
<CategoryProductList
|
||||
channelsCount={channelsCount}
|
||||
selectedChannel={channelChoice}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
|
|
|
@ -9,6 +9,7 @@ import Metadata from "@saleor/components/Metadata/Metadata";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
|
@ -48,6 +49,8 @@ export interface CategoryUpdatePageProps
|
|||
hasPreviousPage: boolean;
|
||||
};
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
channelChoices: SingleAutocompleteChoiceType[];
|
||||
channelsCount: number;
|
||||
onImageDelete: () => void;
|
||||
onSubmit: (data: CategoryUpdateData) => SubmitPromise;
|
||||
onImageUpload(file: File);
|
||||
|
@ -66,6 +69,8 @@ const ProductsTab = Tab(CategoryPageTab.products);
|
|||
|
||||
export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||
changeTab,
|
||||
channelChoices,
|
||||
channelsCount,
|
||||
currentTab,
|
||||
category,
|
||||
disabled,
|
||||
|
@ -198,6 +203,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
)}
|
||||
{currentTab === CategoryPageTab.products && (
|
||||
<CategoryProducts
|
||||
channelsCount={channelsCount}
|
||||
channelChoices={channelChoices}
|
||||
categoryName={category?.name}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -128,31 +128,50 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyMQ==",
|
||||
isAvailable: true,
|
||||
name: "Gardner-Schultz",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "12345",
|
||||
name: "Channel2"
|
||||
},
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyMQ==",
|
||||
name: "Gardner-Schultz",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -166,31 +185,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyMg==",
|
||||
isAvailable: true,
|
||||
name: "James, Martinez and Murray",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "523",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyMg==",
|
||||
name: "James, Martinez and Murray",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -204,31 +222,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjI=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyMw==",
|
||||
isAvailable: true,
|
||||
name: "Curtis, Joyce and Turner",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1234",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyMw==",
|
||||
name: "Curtis, Joyce and Turner",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -242,31 +259,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjM=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyNA==",
|
||||
isAvailable: true,
|
||||
name: "Davis, Brown and Ray",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1235",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyNA==",
|
||||
name: "Davis, Brown and Ray",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -280,31 +296,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjQ=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyNQ==",
|
||||
isAvailable: true,
|
||||
name: "Gallegos Ltd",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1236",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyNQ==",
|
||||
name: "Gallegos Ltd",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -318,31 +333,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjU=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyNg==",
|
||||
isAvailable: true,
|
||||
name: "Franklin Inc",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1237",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyNg==",
|
||||
name: "Franklin Inc",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -356,31 +370,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjY=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyNw==",
|
||||
isAvailable: true,
|
||||
name: "Williams-Taylor",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1238",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyNw==",
|
||||
name: "Williams-Taylor",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -394,31 +407,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjc=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyOA==",
|
||||
isAvailable: true,
|
||||
name: "Riddle, Evans and Hicks",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "1239",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyOA==",
|
||||
name: "Riddle, Evans and Hicks",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -432,31 +444,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjg=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDoyOQ==",
|
||||
isAvailable: true,
|
||||
name: "Hebert-Sherman",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "223",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDoyOQ==",
|
||||
name: "Hebert-Sherman",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
@ -470,31 +481,30 @@ export const category: (
|
|||
cursor: "YXJyYXljb25uZWN0aW9uOjk=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDozMA==",
|
||||
isAvailable: true,
|
||||
name: "Carter and Sons",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
priceRangeUndiscounted: {
|
||||
__typename: "TaxedMoneyRange",
|
||||
start: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 3,
|
||||
currency: "USD"
|
||||
}
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "323",
|
||||
name: "Channel1"
|
||||
},
|
||||
stop: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
}
|
||||
}
|
||||
currency: "USD",
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
}
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdDozMA==",
|
||||
name: "Carter and Sons",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
categoryFragment
|
||||
} from "@saleor/fragments/categories";
|
||||
import { pageInfoFragment } from "@saleor/fragments/pageInfo";
|
||||
import { fragmentMoney } from "@saleor/fragments/products";
|
||||
import { channelListingProductFragment } from "@saleor/fragments/products";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
|
@ -49,7 +49,7 @@ export const useRootCategoriesQuery = makeQuery<RootCategories, {}>(
|
|||
);
|
||||
|
||||
export const categoryDetails = gql`
|
||||
${fragmentMoney}
|
||||
${channelListingProductFragment}
|
||||
${categoryFragment}
|
||||
${categoryDetailsFragment}
|
||||
${pageInfoFragment}
|
||||
|
@ -81,7 +81,6 @@ export const categoryDetails = gql`
|
|||
node {
|
||||
id
|
||||
name
|
||||
isAvailable
|
||||
thumbnail {
|
||||
url
|
||||
}
|
||||
|
@ -89,19 +88,8 @@ export const categoryDetails = gql`
|
|||
id
|
||||
name
|
||||
}
|
||||
pricing {
|
||||
priceRangeUndiscounted {
|
||||
start {
|
||||
gross {
|
||||
...Money
|
||||
}
|
||||
}
|
||||
stop {
|
||||
gross {
|
||||
...Money
|
||||
}
|
||||
}
|
||||
}
|
||||
channelListings {
|
||||
...ChannelListingProductFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,47 +85,37 @@ export interface CategoryDetails_category_products_edges_node_productType {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start_gross {
|
||||
export interface CategoryDetails_category_products_edges_node_channelListings_discountedPrice {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start {
|
||||
__typename: "TaxedMoney";
|
||||
gross: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start_gross;
|
||||
export interface CategoryDetails_category_products_edges_node_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop_gross {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop {
|
||||
__typename: "TaxedMoney";
|
||||
gross: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop_gross;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted {
|
||||
__typename: "TaxedMoneyRange";
|
||||
start: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start | null;
|
||||
stop: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop | null;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node_pricing {
|
||||
__typename: "ProductPricingInfo";
|
||||
priceRangeUndiscounted: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted | null;
|
||||
export interface CategoryDetails_category_products_edges_node_channelListings {
|
||||
__typename: "ProductChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
discountedPrice: CategoryDetails_category_products_edges_node_channelListings_discountedPrice | null;
|
||||
isAvailableForPurchase: boolean | null;
|
||||
availableForPurchase: any | null;
|
||||
visibleInListings: boolean;
|
||||
channel: CategoryDetails_category_products_edges_node_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
name: string;
|
||||
isAvailable: boolean | null;
|
||||
thumbnail: CategoryDetails_category_products_edges_node_thumbnail | null;
|
||||
productType: CategoryDetails_category_products_edges_node_productType;
|
||||
pricing: CategoryDetails_category_products_edges_node_pricing | null;
|
||||
channelListings: CategoryDetails_category_products_edges_node_channelListings[] | null;
|
||||
}
|
||||
|
||||
export interface CategoryDetails_category_products_edges {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import { useChannelsList } from "@saleor/channels/queries";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
|
@ -78,6 +79,13 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
variables: { ...paginationState, id }
|
||||
});
|
||||
|
||||
const { data: channelsData } = useChannelsList({});
|
||||
|
||||
const channelChoices = channelsData?.channels?.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}));
|
||||
|
||||
const category = data?.category;
|
||||
|
||||
if (category === null) {
|
||||
|
@ -205,6 +213,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
<>
|
||||
<WindowTitle title={maybe(() => data.category.name)} />
|
||||
<CategoryUpdatePage
|
||||
channelsCount={channelsData?.channels?.length}
|
||||
channelChoices={channelChoices}
|
||||
changeTab={changeTab}
|
||||
currentTab={params.activeTab}
|
||||
category={maybe(() => data.category)}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { channelsList } from "../../fixtures";
|
||||
import ChannelDeleteDialog, {
|
||||
ChannelDeleteDialogProps
|
||||
} from "./ChannelDeleteDialog";
|
||||
|
||||
const props: ChannelDeleteDialogProps = {
|
||||
channelsChoices: channelsList.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
})),
|
||||
confirmButtonState: "default",
|
||||
onBack: () => undefined,
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Delete channel", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelDeleteDialog {...props} />)
|
||||
.add("without channels to choose", () => (
|
||||
<ChannelDeleteDialog {...props} channelsChoices={[]} />
|
||||
));
|
|
@ -0,0 +1,96 @@
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import {
|
||||
Choices,
|
||||
SingleSelectField
|
||||
} from "@saleor/components/SingleSelectField";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "../styles";
|
||||
|
||||
export interface ChannelDeleteDialogProps {
|
||||
channelsChoices: Choices;
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
onBack: () => void;
|
||||
onClose: () => void;
|
||||
onConfirm: (targetChannelId: string) => void;
|
||||
}
|
||||
|
||||
const ChannelDeleteDialog: React.FC<ChannelDeleteDialogProps> = ({
|
||||
channelsChoices = [],
|
||||
confirmButtonState,
|
||||
open,
|
||||
onBack,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
const [choice, setChoice] = useStateFromProps(
|
||||
!!channelsChoices.length ? channelsChoices[0].value : ""
|
||||
);
|
||||
const hasChannels = !!channelsChoices?.length;
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={() => (hasChannels ? onConfirm(choice) : onBack())}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Channel",
|
||||
description: "dialog header"
|
||||
})}
|
||||
confirmButtonLabel={intl.formatMessage(
|
||||
hasChannels ? buttonMessages.delete : buttonMessages.ok
|
||||
)}
|
||||
variant={hasChannels ? "delete" : "default"}
|
||||
>
|
||||
<div>
|
||||
{hasChannels ? (
|
||||
<>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="All order information from this channel need to be moved to a different channel. Please select channel orders need to be moved to:."
|
||||
description="delete channel"
|
||||
/>
|
||||
</Typography>
|
||||
<div className={classes.select}>
|
||||
<SingleSelectField
|
||||
choices={channelsChoices}
|
||||
name="channels"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Select Channel",
|
||||
description: "dialog header"
|
||||
})}
|
||||
value={choice}
|
||||
onChange={e => setChoice(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="Deleting channel will delete all product data regarding this channel. Are you sure you want to delete this channel?"
|
||||
description="delete channel"
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="There is no available channel to move order information to. Please create a channel with same currency so that information can be moved to it."
|
||||
description="currency channel"
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
ChannelDeleteDialog.displayName = "ChannelDeleteDialog";
|
||||
export default ChannelDeleteDialog;
|
2
src/channels/components/ChannelDeleteDialog/index.ts
Normal file
2
src/channels/components/ChannelDeleteDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelDeleteDialog";
|
||||
export { default } from "./ChannelDeleteDialog";
|
25
src/channels/components/ChannelForm/ChannelForm.stories.tsx
Normal file
25
src/channels/components/ChannelForm/ChannelForm.stories.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { channelCreateErrors } from "../../fixtures";
|
||||
import ChannelForm, { ChannelFormProps } from "./ChannelForm";
|
||||
|
||||
const props: ChannelFormProps = {
|
||||
data: {
|
||||
currencyCode: "euro",
|
||||
name: "Test",
|
||||
slug: "test"
|
||||
},
|
||||
disabled: false,
|
||||
errors: [],
|
||||
onChange: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Channel form", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelForm {...props} />)
|
||||
.add("disabled", () => <ChannelForm {...props} disabled={true} />)
|
||||
.add("with errors", () => (
|
||||
<ChannelForm {...props} errors={channelCreateErrors} />
|
||||
));
|
162
src/channels/components/ChannelForm/ChannelForm.tsx
Normal file
162
src/channels/components/ChannelForm/ChannelForm.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import SingleAutocompleteSelectField, {
|
||||
SingleAutocompleteChoiceType
|
||||
} from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||
import useClipboard from "@saleor/hooks/useClipboard";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "../styles";
|
||||
|
||||
export interface FormData {
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export interface ChannelFormProps {
|
||||
data: FormData;
|
||||
disabled: boolean;
|
||||
currencyCodes?: SingleAutocompleteChoiceType[];
|
||||
errors: ChannelErrorFragment[];
|
||||
selectedCurrencyCode?: string;
|
||||
onChange: FormChange;
|
||||
onCurrencyCodeChange?: (event: ChangeEvent) => void;
|
||||
}
|
||||
|
||||
export const ChannelForm: React.FC<ChannelFormProps> = ({
|
||||
currencyCodes,
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
selectedCurrencyCode,
|
||||
onChange,
|
||||
onCurrencyCodeChange
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [copied, copy] = useClipboard();
|
||||
const formErrors = getFormErrors<keyof FormData, ChannelErrorFragment>(
|
||||
["name", "slug", "currencyCode"],
|
||||
errors
|
||||
);
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
||||
/>
|
||||
<CardContent>
|
||||
<TextField
|
||||
error={!!formErrors.name}
|
||||
helperText={getChannelsErrorMessage(formErrors?.name, intl)}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Channel Name",
|
||||
description: "channel name"
|
||||
})}
|
||||
name="name"
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
error={!!formErrors.slug}
|
||||
helperText={getChannelsErrorMessage(formErrors?.slug, intl)}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Slug",
|
||||
description: "channel slug"
|
||||
})}
|
||||
name="slug"
|
||||
value={data.slug}
|
||||
onChange={onChange}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment
|
||||
className={classes.copyBtn}
|
||||
position="end"
|
||||
disableTypography
|
||||
onClick={() => copy(data.slug)}
|
||||
>
|
||||
{copied ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Copied"
|
||||
description="button"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Copy"
|
||||
description="button"
|
||||
/>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Channel Settings",
|
||||
description: "channel settings"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
{!!currencyCodes ? (
|
||||
<SingleAutocompleteSelectField
|
||||
allowCustomValues
|
||||
error={!!formErrors.currencyCode}
|
||||
helperText={getChannelsErrorMessage(
|
||||
formErrors?.currencyCode,
|
||||
intl
|
||||
)}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Currency",
|
||||
description: "channel currency"
|
||||
})}
|
||||
choices={currencyCodes}
|
||||
name="currencyCode"
|
||||
displayValue={selectedCurrencyCode}
|
||||
value={selectedCurrencyCode}
|
||||
onChange={onCurrencyCodeChange}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Typography variant="caption" className={classes.label}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Selected Currency"
|
||||
description="selected currency"
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>{data.currencyCode}</Typography>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelForm.displayName = "ChannelForm";
|
||||
export default ChannelForm;
|
2
src/channels/components/ChannelForm/index.ts
Normal file
2
src/channels/components/ChannelForm/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelForm";
|
||||
export { default } from "./ChannelForm";
|
|
@ -0,0 +1,26 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { channelsList } from "../../fixtures";
|
||||
import ChannelSettingsDialog, {
|
||||
ChannelSettingsDialogProps
|
||||
} from "./ChannelSettingsDialog";
|
||||
|
||||
const channelsChoices = channelsList.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}));
|
||||
|
||||
const props: ChannelSettingsDialogProps = {
|
||||
channelsChoices,
|
||||
confirmButtonState: "default",
|
||||
defaultChoice: channelsChoices[0]?.value,
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Settings dialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelSettingsDialog {...props} />);
|
|
@ -0,0 +1,71 @@
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import {
|
||||
Choices,
|
||||
SingleSelectField
|
||||
} from "@saleor/components/SingleSelectField";
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "../styles";
|
||||
|
||||
export interface ChannelSettingsDialogProps {
|
||||
channelsChoices: Choices;
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
defaultChoice: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: (choice: string) => void;
|
||||
}
|
||||
|
||||
const ChannelSettingsDialog: React.FC<ChannelSettingsDialogProps> = ({
|
||||
channelsChoices = [],
|
||||
confirmButtonState,
|
||||
defaultChoice,
|
||||
open,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const [choice, setChoice] = useState(
|
||||
defaultChoice || (!!channelsChoices.length ? channelsChoices[0].value : "")
|
||||
);
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={() => onConfirm(choice)}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Settings",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="Configure the way information are presented in catalog section of Dashboard."
|
||||
description="channel settings"
|
||||
/>
|
||||
</Typography>
|
||||
<div className={classes.select}>
|
||||
<SingleSelectField
|
||||
choices={channelsChoices}
|
||||
name="channels"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Show prices for",
|
||||
description: "select label"
|
||||
})}
|
||||
value={choice}
|
||||
onChange={e => setChoice(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
ChannelSettingsDialog.displayName = "ChannelSettingsDialog";
|
||||
export default ChannelSettingsDialog;
|
2
src/channels/components/ChannelSettingsDialog/index.ts
Normal file
2
src/channels/components/ChannelSettingsDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelSettingsDialog";
|
||||
export { default } from "./ChannelSettingsDialog";
|
|
@ -0,0 +1,16 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ChannelStatus, { ChannelStatusProps } from "./ChannelStatus";
|
||||
|
||||
const props: ChannelStatusProps = {
|
||||
disabled: false,
|
||||
isActive: false,
|
||||
updateChannelStatus: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Channel status", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("inactive", () => <ChannelStatus {...props} />)
|
||||
.add("active", () => <ChannelStatus {...props} isActive={true} />);
|
71
src/channels/components/ChannelStatus/ChannelStatus.tsx
Normal file
71
src/channels/components/ChannelStatus/ChannelStatus.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "../styles";
|
||||
|
||||
export interface ChannelStatusProps {
|
||||
isActive: boolean;
|
||||
disabled: boolean;
|
||||
updateChannelStatus: () => void;
|
||||
}
|
||||
|
||||
export const ChannelStatus: React.FC<ChannelStatusProps> = ({
|
||||
disabled,
|
||||
isActive,
|
||||
updateChannelStatus
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Channel Status",
|
||||
description: "channel status title"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography variant="caption" className={classes.label}>
|
||||
<FormattedMessage defaultMessage="Status" description="status" />
|
||||
</Typography>
|
||||
<Typography>
|
||||
{isActive ? (
|
||||
<FormattedMessage defaultMessage="Active" description="active" />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Inactive"
|
||||
description="inactive"
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<Button
|
||||
color="primary"
|
||||
className={classes.activeBtn}
|
||||
disabled={disabled}
|
||||
onClick={() => updateChannelStatus()}
|
||||
>
|
||||
{isActive ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Deactivate"
|
||||
description="deactivate"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Activate"
|
||||
description="activate"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelStatus.displayName = "ChannelStatus";
|
||||
export default ChannelStatus;
|
2
src/channels/components/ChannelStatus/index.ts
Normal file
2
src/channels/components/ChannelStatus/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelStatus";
|
||||
export { default } from "./ChannelStatus";
|
27
src/channels/components/styles.ts
Normal file
27
src/channels/components/styles.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
activeBtn: {
|
||||
marginLeft: theme.spacing(-1),
|
||||
marginTop: theme.spacing(1.5)
|
||||
},
|
||||
copyBtn: {
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
textTransform: "uppercase"
|
||||
},
|
||||
currencyTitle: {
|
||||
marginBottom: theme.spacing(1)
|
||||
},
|
||||
label: {
|
||||
color: theme.palette.text.secondary
|
||||
},
|
||||
select: {
|
||||
marginBottom: theme.spacing(2),
|
||||
marginTop: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "ChannelComponents" }
|
||||
);
|
164
src/channels/fixtures.ts
Normal file
164
src/channels/fixtures.ts
Normal file
|
@ -0,0 +1,164 @@
|
|||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||
import { ProductDetails_product_channelListings } from "@saleor/products/types/ProductDetails";
|
||||
import { ChannelErrorCode } from "@saleor/types/globalTypes";
|
||||
|
||||
import { Channel_channel } from "./types/Channel";
|
||||
import { Channels_channels } from "./types/Channels";
|
||||
|
||||
export const channelCreateErrors: ChannelErrorFragment[] = [
|
||||
{
|
||||
__typename: "ChannelError",
|
||||
code: ChannelErrorCode.UNIQUE,
|
||||
field: "slug",
|
||||
message: "Channel with this Slug already exists."
|
||||
}
|
||||
];
|
||||
|
||||
export const channelsList: Channels_channels[] = [
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbm5lcDoy",
|
||||
isActive: true,
|
||||
name: "Test",
|
||||
slug: "test"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbm7lbDoy213",
|
||||
isActive: true,
|
||||
name: "Channel",
|
||||
slug: "channel"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbn5lbDoytr",
|
||||
isActive: true,
|
||||
name: "Channel test",
|
||||
slug: "channeltest"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbm5lbDo5bot",
|
||||
isActive: true,
|
||||
name: "Channel USD",
|
||||
slug: "channel-usd"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbm7lbDoyr0tr",
|
||||
isActive: true,
|
||||
name: "Channel",
|
||||
slug: "channel2"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbn5lbDoyya",
|
||||
isActive: true,
|
||||
name: "Channel test",
|
||||
slug: "channeltest4"
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
currencyCode: "euro",
|
||||
id: "Q2hhbm5lbDo5w0z",
|
||||
isActive: true,
|
||||
name: "Channel USD",
|
||||
slug: "channel-usd1"
|
||||
}
|
||||
];
|
||||
|
||||
export const channel: Channel_channel = {
|
||||
__typename: "Channel",
|
||||
currencyCode: "zl",
|
||||
id: "Q2hhbm5lbDov78",
|
||||
isActive: true,
|
||||
name: "Test",
|
||||
slug: "test"
|
||||
};
|
||||
|
||||
export const productChannels: ProductDetails_product_channelListings[] = [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 5,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "2020-07-14",
|
||||
visibleInListings: true
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "124",
|
||||
name: "Channel2"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 0,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: "2020-07-30",
|
||||
visibleInListings: true
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "125",
|
||||
name: "Channel3"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 8,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: null,
|
||||
visibleInListings: true
|
||||
}
|
||||
];
|
||||
|
||||
export const productPriceChannels = [
|
||||
{
|
||||
costPrice: "5",
|
||||
id: "123",
|
||||
name: "Channel1",
|
||||
sellingPrice: "10"
|
||||
},
|
||||
{
|
||||
costPrice: "15",
|
||||
id: "124",
|
||||
name: "Channel2",
|
||||
sellingPrice: "20"
|
||||
},
|
||||
{
|
||||
costPrice: "15",
|
||||
id: "125",
|
||||
name: "Channel3",
|
||||
sellingPrice: "100"
|
||||
}
|
||||
];
|
47
src/channels/index.tsx
Normal file
47
src/channels/index.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { parse as parseQs } from "qs";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
channelAddPath,
|
||||
channelPath,
|
||||
channelsListPath,
|
||||
ChannelsListUrlQueryParams,
|
||||
ChannelsListUrlSortField
|
||||
} from "./urls";
|
||||
import ChannelCreateComponent from "./views/ChannelCreate";
|
||||
import ChannelDetailsComponent from "./views/ChannelDetails";
|
||||
import ChannelsListComponent from "./views/ChannelsList";
|
||||
|
||||
const ChannelDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
|
||||
match
|
||||
}) => <ChannelDetailsComponent id={decodeURIComponent(match.params.id)} />;
|
||||
|
||||
const ChannelsList: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: ChannelsListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
ChannelsListUrlSortField
|
||||
);
|
||||
return <ChannelsListComponent params={params} />;
|
||||
};
|
||||
|
||||
export const ChannelsSection: React.FC<{}> = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.channels)} />
|
||||
<Switch>
|
||||
<Route exact path={channelsListPath} component={ChannelsList} />
|
||||
<Route exact path={channelAddPath} component={ChannelCreateComponent} />
|
||||
<Route exact path={channelPath(":id")} component={ChannelDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ChannelsSection;
|
117
src/channels/mutations.ts
Normal file
117
src/channels/mutations.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
channelDetailsFragment,
|
||||
channelErrorFragment
|
||||
} from "@saleor/fragments/channels";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import {
|
||||
ChannelActivate,
|
||||
ChannelActivateVariables
|
||||
} from "./types/ChannelActivate";
|
||||
import { ChannelCreate, ChannelCreateVariables } from "./types/ChannelCreate";
|
||||
import {
|
||||
ChannelDeactivate,
|
||||
ChannelDeactivateVariables
|
||||
} from "./types/ChannelDeactivate";
|
||||
import { ChannelDelete, ChannelDeleteVariables } from "./types/ChannelDelete";
|
||||
import { ChannelUpdate, ChannelUpdateVariables } from "./types/ChannelUpdate";
|
||||
|
||||
export const channelCreateMutation = gql`
|
||||
${channelErrorFragment}
|
||||
${channelDetailsFragment}
|
||||
mutation ChannelCreate($input: ChannelCreateInput!) {
|
||||
channelCreate(input: $input) {
|
||||
channel {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
errors: channelErrors {
|
||||
...ChannelErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const channelUpdateMutation = gql`
|
||||
${channelErrorFragment}
|
||||
${channelDetailsFragment}
|
||||
mutation ChannelUpdate($id: ID!, $input: ChannelUpdateInput!) {
|
||||
channelUpdate(id: $id, input: $input) {
|
||||
channel {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
errors: channelErrors {
|
||||
...ChannelErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const channelDeleteMutation = gql`
|
||||
${channelErrorFragment}
|
||||
${channelDetailsFragment}
|
||||
mutation ChannelDelete($id: ID!, $input: ChannelDeleteInput!) {
|
||||
channelDelete(id: $id, input: $input) {
|
||||
channel {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
errors: channelErrors {
|
||||
...ChannelErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const channelActivateMutation = gql`
|
||||
${channelErrorFragment}
|
||||
${channelDetailsFragment}
|
||||
mutation ChannelActivate($id: ID!) {
|
||||
channelActivate(id: $id) {
|
||||
channel {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
errors: channelErrors {
|
||||
...ChannelErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const channelDeactivateMutation = gql`
|
||||
${channelErrorFragment}
|
||||
${channelDetailsFragment}
|
||||
mutation ChannelDeactivate($id: ID!) {
|
||||
channelDeactivate(id: $id) {
|
||||
channel {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
errors: channelErrors {
|
||||
...ChannelErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useChannelCreateMutation = makeMutation<
|
||||
ChannelCreate,
|
||||
ChannelCreateVariables
|
||||
>(channelCreateMutation);
|
||||
|
||||
export const useChannelUpdateMutation = makeMutation<
|
||||
ChannelUpdate,
|
||||
ChannelUpdateVariables
|
||||
>(channelUpdateMutation);
|
||||
|
||||
export const useChannelDeleteMutation = makeMutation<
|
||||
ChannelDelete,
|
||||
ChannelDeleteVariables
|
||||
>(channelDeleteMutation);
|
||||
|
||||
export const useChannelActivateMutation = makeMutation<
|
||||
ChannelActivate,
|
||||
ChannelActivateVariables
|
||||
>(channelActivateMutation);
|
||||
export const useChannelDeactivateMutation = makeMutation<
|
||||
ChannelDeactivate,
|
||||
ChannelDeactivateVariables
|
||||
>(channelDeactivateMutation);
|
|
@ -0,0 +1,41 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { channel, channelCreateErrors } from "../../fixtures";
|
||||
import ChannelDetailsPage, {
|
||||
ChannelDetailsPageProps
|
||||
} from "./ChannelDetailsPage";
|
||||
|
||||
const props: ChannelDetailsPageProps = {
|
||||
currencyCodes: [
|
||||
{ label: "USD", value: "USD" },
|
||||
{ label: "PLN", value: "PLN" }
|
||||
],
|
||||
disabled: false,
|
||||
disabledStatus: false,
|
||||
errors: [],
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
updateChannelStatus: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Channel details", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelDetailsPage {...props} />)
|
||||
.add("disabled", () => <ChannelDetailsPage {...props} disabled={true} />)
|
||||
.add("loading", () => (
|
||||
<ChannelDetailsPage {...props} saveButtonBarState={"loading"} />
|
||||
))
|
||||
.add("with data", () => <ChannelDetailsPage {...props} channel={channel} />)
|
||||
.add("without editable currency code", () => (
|
||||
<ChannelDetailsPage
|
||||
{...props}
|
||||
currencyCodes={undefined}
|
||||
channel={channel}
|
||||
/>
|
||||
))
|
||||
.add("with errors", () => (
|
||||
<ChannelDetailsPage {...props} errors={channelCreateErrors} />
|
||||
));
|
93
src/channels/pages/ChannelDetailsPage/ChannelDetailsPage.tsx
Normal file
93
src/channels/pages/ChannelDetailsPage/ChannelDetailsPage.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import React from "react";
|
||||
|
||||
import { ChannelForm, FormData } from "../../components/ChannelForm";
|
||||
import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus";
|
||||
import { Channel_channel } from "../../types/Channel";
|
||||
|
||||
export interface ChannelDetailsPageProps {
|
||||
channel?: Channel_channel;
|
||||
currencyCodes?: SingleAutocompleteChoiceType[];
|
||||
disabled: boolean;
|
||||
disabledStatus?: boolean;
|
||||
errors: ChannelErrorFragment[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onBack?: () => void;
|
||||
onSubmit?: (data: FormData) => void;
|
||||
updateChannelStatus?: () => void;
|
||||
}
|
||||
|
||||
const initialData: FormData = {
|
||||
currencyCode: "",
|
||||
name: "",
|
||||
slug: ""
|
||||
};
|
||||
|
||||
export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
||||
channel,
|
||||
currencyCodes,
|
||||
disabled,
|
||||
disabledStatus,
|
||||
errors,
|
||||
onBack,
|
||||
onSubmit,
|
||||
saveButtonBarState,
|
||||
updateChannelStatus
|
||||
}) => {
|
||||
const [selectedCurrencyCode, setSelectedCurrencyCode] = React.useState("");
|
||||
|
||||
return (
|
||||
<Form onSubmit={onSubmit} initial={channel || initialData}>
|
||||
{({ change, data, hasChanged, submit }) => {
|
||||
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setSelectedCurrencyCode,
|
||||
currencyCodes
|
||||
);
|
||||
const formDisabled = !data.name || !data.slug || !data.currencyCode;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid>
|
||||
<div>
|
||||
<ChannelForm
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
currencyCodes={currencyCodes}
|
||||
selectedCurrencyCode={selectedCurrencyCode}
|
||||
onChange={change}
|
||||
onCurrencyCodeChange={handleCurrencyCodeSelect}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
{!!updateChannelStatus && (
|
||||
<div>
|
||||
<ChannelStatus
|
||||
isActive={channel?.isActive}
|
||||
disabled={disabledStatus}
|
||||
updateChannelStatus={updateChannelStatus}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || formDisabled || !onSubmit || !hasChanged}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelDetailsPage.displayName = "ChannelDetailsPage";
|
||||
export default ChannelDetailsPage;
|
2
src/channels/pages/ChannelDetailsPage/index.ts
Normal file
2
src/channels/pages/ChannelDetailsPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelDetailsPage";
|
||||
export { default } from "./ChannelDetailsPage";
|
|
@ -0,0 +1,19 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { channelsList } from "../../fixtures";
|
||||
import ChannelsListPage, { ChannelsListPageProps } from "./ChannelsListPage";
|
||||
|
||||
const props: ChannelsListPageProps = {
|
||||
channelsList,
|
||||
navigateToChannelCreate: () => undefined,
|
||||
onBack: () => undefined,
|
||||
onRemove: () => undefined,
|
||||
onRowClick: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Views / Channels / Channels list", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelsListPage {...props} />)
|
||||
.add("empty", () => <ChannelsListPage {...props} channelsList={[]} />);
|
126
src/channels/pages/ChannelsListPage/ChannelsListPage.tsx
Normal file
126
src/channels/pages/ChannelsListPage/ChannelsListPage.tsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { renderCollection, stopPropagation } from "@saleor/misc";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { Channels_channels } from "../../types/Channels";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface ChannelsListPageProps {
|
||||
channelsList: Channels_channels[] | undefined;
|
||||
navigateToChannelCreate: () => void;
|
||||
onBack: () => void;
|
||||
onRowClick: (id: string) => () => void;
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
const numberOfColumns = 2;
|
||||
|
||||
export const ChannelsListPage: React.FC<ChannelsListPageProps> = ({
|
||||
channelsList,
|
||||
navigateToChannelCreate,
|
||||
onBack,
|
||||
onRemove,
|
||||
onRowClick
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.channels)}>
|
||||
<Button
|
||||
onClick={navigateToChannelCreate}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
data-test="add-channel"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create Channel"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<ResponsiveTable>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCellHeader>
|
||||
<FormattedMessage
|
||||
defaultMessage="Channel Name"
|
||||
description="channel name"
|
||||
/>
|
||||
</TableCellHeader>
|
||||
<TableCell className={classes.colRight}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Actions"
|
||||
description="table actions"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
channelsList,
|
||||
channel => (
|
||||
<TableRow
|
||||
hover={!!channel}
|
||||
key={channel ? channel.id : "skeleton"}
|
||||
className={classes.tableRow}
|
||||
onClick={!!channel ? onRowClick(channel.id) : undefined}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<span data-test="name">
|
||||
{channel?.name || <Skeleton />}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
{channelsList?.length > 1 && (
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={
|
||||
channel
|
||||
? stopPropagation(() => onRemove(channel.id))
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No channels found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelsListPage.displayName = "ChannelsListPage";
|
||||
export default ChannelsListPage;
|
2
src/channels/pages/ChannelsListPage/index.ts
Normal file
2
src/channels/pages/ChannelsListPage/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelsListPage";
|
||||
export { default } from "./ChannelsListPage";
|
37
src/channels/pages/ChannelsListPage/styles.ts
Normal file
37
src/channels/pages/ChannelsListPage/styles.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colName: {
|
||||
"&&": {
|
||||
width: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
colAction: {
|
||||
"&&": {
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
textAlign: "right",
|
||||
width: 140
|
||||
},
|
||||
colName: {
|
||||
paddingLeft: 0,
|
||||
width: 250
|
||||
},
|
||||
colRight: {
|
||||
textAlign: "right"
|
||||
},
|
||||
columnPicker: {
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer"
|
||||
}
|
||||
}),
|
||||
{ name: "ChannelsListPage" }
|
||||
);
|
29
src/channels/queries.ts
Normal file
29
src/channels/queries.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { channelDetailsFragment } from "@saleor/fragments/channels";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { Channel, ChannelVariables } from "./types/Channel";
|
||||
import { Channels } from "./types/Channels";
|
||||
|
||||
export const channelsList = gql`
|
||||
${channelDetailsFragment}
|
||||
query Channels {
|
||||
channels {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const channelDetails = gql`
|
||||
${channelDetailsFragment}
|
||||
query Channel($id: ID!) {
|
||||
channel(id: $id) {
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useChannelsList = makeQuery<Channels, {}>(channelsList);
|
||||
export const useChannelDetails = makeQuery<Channel, ChannelVariables>(
|
||||
channelDetails
|
||||
);
|
24
src/channels/types/Channel.ts
Normal file
24
src/channels/types/Channel.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Channel
|
||||
// ====================================================
|
||||
|
||||
export interface Channel_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
channel: Channel_channel | null;
|
||||
}
|
||||
|
||||
export interface ChannelVariables {
|
||||
id: string;
|
||||
}
|
39
src/channels/types/ChannelActivate.ts
Normal file
39
src/channels/types/ChannelActivate.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ChannelActivate
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelActivate_channelActivate_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ChannelActivate_channelActivate_errors {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface ChannelActivate_channelActivate {
|
||||
__typename: "ChannelActivate";
|
||||
channel: ChannelActivate_channelActivate_channel | null;
|
||||
errors: ChannelActivate_channelActivate_errors[];
|
||||
}
|
||||
|
||||
export interface ChannelActivate {
|
||||
channelActivate: ChannelActivate_channelActivate | null;
|
||||
}
|
||||
|
||||
export interface ChannelActivateVariables {
|
||||
id: string;
|
||||
}
|
39
src/channels/types/ChannelCreate.ts
Normal file
39
src/channels/types/ChannelCreate.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelCreateInput, ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ChannelCreate
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelCreate_channelCreate_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ChannelCreate_channelCreate_errors {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface ChannelCreate_channelCreate {
|
||||
__typename: "ChannelCreate";
|
||||
channel: ChannelCreate_channelCreate_channel | null;
|
||||
errors: ChannelCreate_channelCreate_errors[];
|
||||
}
|
||||
|
||||
export interface ChannelCreate {
|
||||
channelCreate: ChannelCreate_channelCreate | null;
|
||||
}
|
||||
|
||||
export interface ChannelCreateVariables {
|
||||
input: ChannelCreateInput;
|
||||
}
|
39
src/channels/types/ChannelDeactivate.ts
Normal file
39
src/channels/types/ChannelDeactivate.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ChannelDeactivate
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelDeactivate_channelDeactivate_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ChannelDeactivate_channelDeactivate_errors {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface ChannelDeactivate_channelDeactivate {
|
||||
__typename: "ChannelDeactivate";
|
||||
channel: ChannelDeactivate_channelDeactivate_channel | null;
|
||||
errors: ChannelDeactivate_channelDeactivate_errors[];
|
||||
}
|
||||
|
||||
export interface ChannelDeactivate {
|
||||
channelDeactivate: ChannelDeactivate_channelDeactivate | null;
|
||||
}
|
||||
|
||||
export interface ChannelDeactivateVariables {
|
||||
id: string;
|
||||
}
|
40
src/channels/types/ChannelDelete.ts
Normal file
40
src/channels/types/ChannelDelete.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelDeleteInput, ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ChannelDelete
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelDelete_channelDelete_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ChannelDelete_channelDelete_errors {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface ChannelDelete_channelDelete {
|
||||
__typename: "ChannelDelete";
|
||||
channel: ChannelDelete_channelDelete_channel | null;
|
||||
errors: ChannelDelete_channelDelete_errors[];
|
||||
}
|
||||
|
||||
export interface ChannelDelete {
|
||||
channelDelete: ChannelDelete_channelDelete | null;
|
||||
}
|
||||
|
||||
export interface ChannelDeleteVariables {
|
||||
id: string;
|
||||
input: ChannelDeleteInput;
|
||||
}
|
15
src/channels/types/ChannelDetailsFragment.ts
Normal file
15
src/channels/types/ChannelDetailsFragment.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: ChannelDetailsFragment
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelDetailsFragment {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
16
src/channels/types/ChannelErrorFragment.ts
Normal file
16
src/channels/types/ChannelErrorFragment.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: ChannelErrorFragment
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelErrorFragment {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
40
src/channels/types/ChannelUpdate.ts
Normal file
40
src/channels/types/ChannelUpdate.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ChannelUpdateInput, ChannelErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ChannelUpdate
|
||||
// ====================================================
|
||||
|
||||
export interface ChannelUpdate_channelUpdate_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ChannelUpdate_channelUpdate_errors {
|
||||
__typename: "ChannelError";
|
||||
code: ChannelErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
export interface ChannelUpdate_channelUpdate {
|
||||
__typename: "ChannelUpdate";
|
||||
channel: ChannelUpdate_channelUpdate_channel | null;
|
||||
errors: ChannelUpdate_channelUpdate_errors[];
|
||||
}
|
||||
|
||||
export interface ChannelUpdate {
|
||||
channelUpdate: ChannelUpdate_channelUpdate | null;
|
||||
}
|
||||
|
||||
export interface ChannelUpdateVariables {
|
||||
id: string;
|
||||
input: ChannelUpdateInput;
|
||||
}
|
20
src/channels/types/Channels.ts
Normal file
20
src/channels/types/Channels.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Channels
|
||||
// ====================================================
|
||||
|
||||
export interface Channels_channels {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
name: string;
|
||||
slug: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface Channels {
|
||||
channels: Channels_channels[] | null;
|
||||
}
|
33
src/channels/urls.ts
Normal file
33
src/channels/urls.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { Dialog, Filters, SingleAction, Sort } from "../types";
|
||||
|
||||
export enum ChannelsListUrlFiltersEnum {
|
||||
query = "query"
|
||||
}
|
||||
export enum ChannelsListUrlSortField {
|
||||
name = "name"
|
||||
}
|
||||
export type ChannelsListUrlSort = Sort<ChannelsListUrlSortField>;
|
||||
export type ChannelsListUrlFilters = Filters<ChannelsListUrlFiltersEnum>;
|
||||
export type ChannelsListUrlDialog = "remove";
|
||||
export type ChannelsListUrlQueryParams = Dialog<ChannelsListUrlDialog> &
|
||||
ChannelsListUrlFilters &
|
||||
ChannelsListUrlSort &
|
||||
SingleAction;
|
||||
|
||||
export const channelsSection = "/channels/";
|
||||
|
||||
export const channelsListPath = channelsSection;
|
||||
|
||||
export const channelsListUrl = (params?: ChannelsListUrlQueryParams) =>
|
||||
channelsListPath + "?" + stringifyQs(params);
|
||||
|
||||
export const channelAddPath = urlJoin(channelsSection, "add");
|
||||
export const channelAddUrl = channelAddPath;
|
||||
|
||||
export const channelPath = (id: string) => urlJoin(channelsSection, id);
|
||||
|
||||
export const channelUrl = (id: string, params?: ChannelsListUrlQueryParams) =>
|
||||
channelPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
306
src/channels/utils.ts
Normal file
306
src/channels/utils.ts
Normal file
|
@ -0,0 +1,306 @@
|
|||
import { Channels_channels } from "@saleor/channels/types/Channels";
|
||||
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
||||
import { SaleDetails_sale } from "@saleor/discounts/types/SaleDetails";
|
||||
import { VoucherDetails_voucher } from "@saleor/discounts/types/VoucherDetails";
|
||||
import { RequireOnlyOne } from "@saleor/misc";
|
||||
import { ProductDetails_product } from "@saleor/products/types/ProductDetails";
|
||||
import { ProductVariantDetails_productVariant } from "@saleor/products/types/ProductVariantDetails";
|
||||
import { ShippingZone_shippingZone_shippingMethods_channelListings } from "@saleor/shipping/types/ShippingZone";
|
||||
import { uniqBy } from "lodash";
|
||||
|
||||
export interface Channel {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ChannelData {
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
publicationDate: string | null;
|
||||
currency: string;
|
||||
price: string;
|
||||
costPrice: string;
|
||||
availableForPurchase: string;
|
||||
isAvailableForPurchase: boolean;
|
||||
visibleInListings: boolean;
|
||||
}
|
||||
|
||||
export interface ChannelPriceData {
|
||||
id: string;
|
||||
name: string;
|
||||
currency: string;
|
||||
price: string;
|
||||
costPrice?: string;
|
||||
}
|
||||
|
||||
export interface IChannelPriceArgs {
|
||||
price: string;
|
||||
costPrice: string;
|
||||
}
|
||||
export type ChannelPriceArgs = RequireOnlyOne<
|
||||
IChannelPriceArgs,
|
||||
"price" | "costPrice"
|
||||
>;
|
||||
|
||||
export interface ChannelVoucherData {
|
||||
id: string;
|
||||
name: string;
|
||||
discountValue: string;
|
||||
currency: string;
|
||||
minSpent: string;
|
||||
}
|
||||
|
||||
export interface ChannelSaleData {
|
||||
id: string;
|
||||
name: string;
|
||||
discountValue: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ChannelCollectionData {
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
publicationDate: string | null;
|
||||
}
|
||||
|
||||
export const createCollectionChannels = (data?: Channels_channels[]) =>
|
||||
data?.map(channel => ({
|
||||
id: channel.id,
|
||||
isPublished: false,
|
||||
name: channel.name,
|
||||
publicationDate: null
|
||||
}));
|
||||
|
||||
export const createVoucherChannels = (data?: Channels_channels[]) =>
|
||||
data?.map(channel => ({
|
||||
currency: channel.currencyCode,
|
||||
discountValue: "",
|
||||
id: channel.id,
|
||||
minSpent: "",
|
||||
name: channel.name
|
||||
}));
|
||||
|
||||
export const createSaleChannels = (data?: Channels_channels[]) =>
|
||||
data?.map(channel => ({
|
||||
currency: channel.currencyCode,
|
||||
discountValue: "",
|
||||
id: channel.id,
|
||||
name: channel.name
|
||||
}));
|
||||
|
||||
export const createVariantChannels = (
|
||||
data?: ProductVariantDetails_productVariant
|
||||
): ChannelPriceData[] => {
|
||||
if (data) {
|
||||
const productChannels = data?.product.channelListings.map(listing => ({
|
||||
costPrice: "",
|
||||
currency: listing.channel.currencyCode,
|
||||
id: listing.channel.id,
|
||||
name: listing.channel.name,
|
||||
price: ""
|
||||
}));
|
||||
const variantChannels = data?.channelListings.map(listing => ({
|
||||
costPrice: listing.costPrice?.amount.toString() || "",
|
||||
currency: listing.channel.currencyCode,
|
||||
id: listing.channel.id,
|
||||
name: listing.channel.name,
|
||||
price: listing.price.amount.toString()
|
||||
}));
|
||||
return uniqBy([...variantChannels, ...productChannels], obj => obj.id);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createChannelsDataWithSaleDiscountPrice = (
|
||||
saleData?: SaleDetails_sale,
|
||||
data?: Channels_channels[]
|
||||
): ChannelSaleData[] => {
|
||||
if (data && saleData?.channelListings) {
|
||||
const dataArr = createSaleChannels(data);
|
||||
|
||||
const saleDataArr = createChannelsDataFromSale(saleData);
|
||||
return uniqBy([...saleDataArr, ...dataArr], obj => obj.id);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createChannelsDataWithDiscountPrice = (
|
||||
voucherData?: VoucherDetails_voucher,
|
||||
data?: Channels_channels[]
|
||||
): ChannelVoucherData[] => {
|
||||
if (data && voucherData?.channelListings) {
|
||||
const dataArr = createVoucherChannels(data);
|
||||
|
||||
const voucherDataArr = createChannelsDataFromVoucher(voucherData);
|
||||
return uniqBy([...voucherDataArr, ...dataArr], obj => obj.id);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createChannelsData = (data?: Channels_channels[]): ChannelData[] =>
|
||||
data?.map(channel => ({
|
||||
availableForPurchase: null,
|
||||
costPrice: "",
|
||||
currency: channel.currencyCode,
|
||||
id: channel.id,
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
name: channel.name,
|
||||
price: "",
|
||||
publicationDate: null,
|
||||
visibleInListings: false
|
||||
})) || [];
|
||||
|
||||
export const createChannelsDataWithPrice = (
|
||||
productData?: ProductDetails_product,
|
||||
data?: Channels_channels[]
|
||||
): ChannelData[] => {
|
||||
if (data && productData?.channelListings) {
|
||||
const dataArr = createChannelsData(data);
|
||||
|
||||
const productDataArr = createChannelsDataFromProduct(productData);
|
||||
return uniqBy([...productDataArr, ...dataArr], obj => obj.id);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createShippingChannels = (
|
||||
data?: Channels_channels[]
|
||||
): ChannelShippingData[] =>
|
||||
data?.map(channel => ({
|
||||
currency: channel.currencyCode,
|
||||
id: channel.id,
|
||||
maxValue: "",
|
||||
minValue: "",
|
||||
name: channel.name,
|
||||
price: ""
|
||||
})) || [];
|
||||
|
||||
export const createShippingChannelsFromRate = (
|
||||
data?: ShippingZone_shippingZone_shippingMethods_channelListings[]
|
||||
): ChannelShippingData[] =>
|
||||
data?.map(channelData => ({
|
||||
currency: channelData.channel.currencyCode,
|
||||
id: channelData.channel.id,
|
||||
maxValue: channelData.maximumOrderPrice
|
||||
? channelData.maximumOrderPrice.amount.toString()
|
||||
: "",
|
||||
minValue: channelData.minimumOrderPrice
|
||||
? channelData.minimumOrderPrice.amount.toString()
|
||||
: "",
|
||||
name: channelData.channel.name,
|
||||
price: channelData.price ? channelData.price.amount.toString() : ""
|
||||
})) || [];
|
||||
|
||||
export const createCollectionChannelsData = (
|
||||
collectionData?: CollectionDetails_collection
|
||||
) => {
|
||||
if (collectionData?.channelListings) {
|
||||
const collectionDataArr = collectionData?.channelListings.map(listing => ({
|
||||
id: listing.channel.id,
|
||||
isPublished: listing.isPublished,
|
||||
name: listing.channel.name,
|
||||
publicationDate: listing.publicationDate
|
||||
}));
|
||||
return collectionDataArr;
|
||||
}
|
||||
};
|
||||
|
||||
export interface ChannelShippingData {
|
||||
currency: string;
|
||||
id: string;
|
||||
minValue: string;
|
||||
name: string;
|
||||
maxValue: string;
|
||||
price: string;
|
||||
}
|
||||
|
||||
export const createChannelsDataFromVoucher = (
|
||||
voucherData?: VoucherDetails_voucher
|
||||
) =>
|
||||
voucherData?.channelListings?.map(option => ({
|
||||
currency: option.channel.currencyCode || option?.minSpent?.currency || "",
|
||||
discountValue: option.discountValue.toString() || "",
|
||||
id: option.channel.id,
|
||||
minSpent: option?.minSpent?.amount.toString() || "",
|
||||
name: option.channel.name
|
||||
})) || [];
|
||||
|
||||
export const createChannelsDataFromSale = (saleData?: SaleDetails_sale) =>
|
||||
saleData?.channelListings?.map(option => ({
|
||||
currency: option.channel.currencyCode || "",
|
||||
discountValue: option.discountValue.toString() || "",
|
||||
id: option.channel.id,
|
||||
name: option.channel.name
|
||||
})) || [];
|
||||
|
||||
export const createChannelsDataFromProduct = (
|
||||
productData?: ProductDetails_product
|
||||
) =>
|
||||
productData?.channelListings?.map(option => {
|
||||
const variantChannel = productData.variants[0]?.channelListings.find(
|
||||
listing => listing.channel.id === option.channel.id
|
||||
);
|
||||
const price = variantChannel?.price;
|
||||
const costPrice = variantChannel?.costPrice;
|
||||
return {
|
||||
availableForPurchase: option?.availableForPurchase,
|
||||
costPrice: costPrice ? costPrice.amount.toString() : "",
|
||||
currency: price ? price.currency : "",
|
||||
id: option.channel.id,
|
||||
isAvailableForPurchase: !!option?.isAvailableForPurchase,
|
||||
isPublished: option.isPublished,
|
||||
name: option.channel.name,
|
||||
price: price ? price.amount.toString() : "",
|
||||
publicationDate: option.publicationDate,
|
||||
visibleInListings: !!option.visibleInListings
|
||||
};
|
||||
}) || [];
|
||||
|
||||
export const createSortedChannelsDataFromProduct = (
|
||||
productData?: ProductDetails_product
|
||||
) =>
|
||||
createChannelsDataFromProduct(productData).sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedChannelsData = (data?: Channels_channels[]) =>
|
||||
createChannelsData(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedShippingChannels = (data?: Channels_channels[]) =>
|
||||
createShippingChannels(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedShippingChannelsFromRate = (
|
||||
data?: ShippingZone_shippingZone_shippingMethods_channelListings[]
|
||||
) =>
|
||||
createShippingChannelsFromRate(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedVoucherData = (data?: Channels_channels[]) =>
|
||||
createVoucherChannels(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedSaleData = (data?: Channels_channels[]) =>
|
||||
createSaleChannels(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedChannelsDataFromVoucher = (
|
||||
data?: VoucherDetails_voucher
|
||||
) =>
|
||||
createChannelsDataFromVoucher(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
export const createSortedChannelsDataFromSale = (data?: SaleDetails_sale) =>
|
||||
createChannelsDataFromSale(data)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
92
src/channels/views/ChannelCreate/ChannelCreate.tsx
Normal file
92
src/channels/views/ChannelCreate/ChannelCreate.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import currencyCodes from "currency-codes";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { ChannelCreateInput } from "../../../types/globalTypes";
|
||||
import { useChannelCreateMutation } from "../../mutations";
|
||||
import ChannelDetailsPage from "../../pages/ChannelDetailsPage";
|
||||
import { ChannelCreate } from "../../types/ChannelCreate";
|
||||
import { channelPath, channelsListUrl } from "../../urls";
|
||||
|
||||
export const ChannelCreateView = ({}) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleBack = () => navigate(channelsListUrl());
|
||||
|
||||
const onSubmit = (data: ChannelCreate) => {
|
||||
if (!data.channelCreate.errors.length) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(channelPath(data.channelCreate.channel.id));
|
||||
}
|
||||
};
|
||||
|
||||
const [createChannel, createChannelOpts] = useChannelCreateMutation({
|
||||
onCompleted: onSubmit
|
||||
});
|
||||
|
||||
const handleSubmit = (data: ChannelCreateInput) =>
|
||||
createChannel({
|
||||
variables: {
|
||||
input: { ...data, currencyCode: data.currencyCode.toUpperCase() }
|
||||
}
|
||||
});
|
||||
|
||||
const currencyCodeChoices = currencyCodes.data.map(currencyData => ({
|
||||
label: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{code} - {countries}",
|
||||
description: "currency code select"
|
||||
},
|
||||
{
|
||||
code: currencyData.code,
|
||||
countries: currencyData.countries.join(",")
|
||||
}
|
||||
),
|
||||
value: currencyData.code
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create Channel",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<Container>
|
||||
<AppHeader onBack={handleBack}>
|
||||
{intl.formatMessage(sectionNames.channels)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "New Channel",
|
||||
description: "channel create"
|
||||
})}
|
||||
/>
|
||||
<ChannelDetailsPage
|
||||
disabled={createChannelOpts.loading}
|
||||
errors={createChannelOpts?.data?.channelCreate?.errors || []}
|
||||
currencyCodes={currencyCodeChoices}
|
||||
onSubmit={handleSubmit}
|
||||
onBack={handleBack}
|
||||
saveButtonBarState={createChannelOpts.status}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelCreateView;
|
2
src/channels/views/ChannelCreate/index.ts
Normal file
2
src/channels/views/ChannelCreate/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelCreate";
|
||||
export { default } from "./ChannelCreate";
|
125
src/channels/views/ChannelDetails/ChannelDetails.tsx
Normal file
125
src/channels/views/ChannelDetails/ChannelDetails.tsx
Normal file
|
@ -0,0 +1,125 @@
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { ChannelUpdateInput } from "../../../types/globalTypes";
|
||||
import {
|
||||
useChannelActivateMutation,
|
||||
useChannelDeactivateMutation,
|
||||
useChannelUpdateMutation
|
||||
} from "../../mutations";
|
||||
import ChannelDetailsPage from "../../pages/ChannelDetailsPage";
|
||||
import { useChannelDetails } from "../../queries";
|
||||
import { ChannelUpdate } from "../../types/ChannelUpdate";
|
||||
import { channelsListUrl } from "../../urls";
|
||||
|
||||
interface ChannelDetailsProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const ChannelDetails: React.FC<ChannelDetailsProps> = ({ id }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleBack = () => navigate(channelsListUrl());
|
||||
|
||||
const onSubmit = (data: ChannelUpdate) => {
|
||||
if (!data.channelUpdate.errors.length) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
handleBack();
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (error: ChannelErrorFragment) => {
|
||||
notify({
|
||||
status: "error",
|
||||
text: getChannelsErrorMessage(error, intl)
|
||||
});
|
||||
};
|
||||
|
||||
const { data, loading } = useChannelDetails({
|
||||
displayLoader: true,
|
||||
variables: { id }
|
||||
});
|
||||
|
||||
const [updateChannel, updateChannelOpts] = useChannelUpdateMutation({
|
||||
onCompleted: onSubmit
|
||||
});
|
||||
|
||||
const [activateChannel, activateChannelOpts] = useChannelActivateMutation({
|
||||
onCompleted: data => {
|
||||
const errors = data.channelActivate.errors;
|
||||
if (errors.length) {
|
||||
errors.forEach(error => handleError(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
deactivateChannel,
|
||||
deactivateChannelOpts
|
||||
] = useChannelDeactivateMutation({
|
||||
onCompleted: data => {
|
||||
const errors = data.channelDeactivate.errors;
|
||||
if (errors.length) {
|
||||
errors.forEach(error => handleError(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleSubmit = (data: ChannelUpdateInput) =>
|
||||
updateChannel({
|
||||
variables: {
|
||||
id,
|
||||
input: { name: data.name, slug: data.slug }
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Channel details",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<Container>
|
||||
<AppHeader onBack={handleBack}>
|
||||
{intl.formatMessage(sectionNames.channels)}
|
||||
</AppHeader>
|
||||
<PageHeader title={data?.channel?.name} />
|
||||
<ChannelDetailsPage
|
||||
channel={data?.channel}
|
||||
disabled={updateChannelOpts.loading || loading}
|
||||
disabledStatus={
|
||||
activateChannelOpts.loading || deactivateChannelOpts.loading
|
||||
}
|
||||
errors={updateChannelOpts?.data?.channelUpdate?.errors || []}
|
||||
onSubmit={handleSubmit}
|
||||
onBack={handleBack}
|
||||
updateChannelStatus={() =>
|
||||
data?.channel?.isActive
|
||||
? deactivateChannel({ variables: { id } })
|
||||
: activateChannel({ variables: { id } })
|
||||
}
|
||||
saveButtonBarState={updateChannelOpts.status}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelDetails;
|
2
src/channels/views/ChannelDetails/index.ts
Normal file
2
src/channels/views/ChannelDetails/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelDetails";
|
||||
export { default } from "./ChannelDetails";
|
116
src/channels/views/ChannelsList/ChannelsList.tsx
Normal file
116
src/channels/views/ChannelsList/ChannelsList.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { configurationMenuUrl } from "@saleor/configuration";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import ChannelDeleteDialog from "../../components/ChannelDeleteDialog";
|
||||
import { useChannelDeleteMutation } from "../../mutations";
|
||||
import ChannelsListPage from "../../pages/ChannelsListPage";
|
||||
import { useChannelsList } from "../../queries";
|
||||
import { ChannelDelete } from "../../types/ChannelDelete";
|
||||
import {
|
||||
channelAddUrl,
|
||||
channelsListUrl,
|
||||
ChannelsListUrlDialog,
|
||||
ChannelsListUrlQueryParams,
|
||||
channelUrl
|
||||
} from "../../urls";
|
||||
|
||||
interface ChannelsListProps {
|
||||
params: ChannelsListUrlQueryParams;
|
||||
}
|
||||
|
||||
export const ChannelsList: React.FC<ChannelsListProps> = ({ params }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const { data, refetch } = useChannelsList({ displayLoader: true });
|
||||
|
||||
const selectedChannel = data?.channels.find(
|
||||
channel => channel.id === params?.id
|
||||
);
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
ChannelsListUrlDialog,
|
||||
ChannelsListUrlQueryParams
|
||||
>(navigate, channelsListUrl, params);
|
||||
|
||||
const onCompleted = (data: ChannelDelete) => {
|
||||
const errors = data.channelDelete.errors;
|
||||
if (errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Channel deleted"
|
||||
})
|
||||
});
|
||||
refetch();
|
||||
closeModal();
|
||||
} else {
|
||||
errors.map(error =>
|
||||
notify({
|
||||
status: "error",
|
||||
text: getChannelsErrorMessage(error, intl)
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const [deleteChannel, deleteChannelOpts] = useChannelDeleteMutation({
|
||||
onCompleted
|
||||
});
|
||||
|
||||
const channelsChoices = params.id
|
||||
? data?.channels
|
||||
?.filter(
|
||||
channel =>
|
||||
channel.id !== params.id &&
|
||||
channel.currencyCode === selectedChannel.currencyCode
|
||||
)
|
||||
.map(channel => ({
|
||||
label: channel.name,
|
||||
value: channel.id
|
||||
}))
|
||||
: [];
|
||||
|
||||
const navigateToChannelCreate = () => navigate(channelAddUrl);
|
||||
|
||||
const handleRemoveConfirm = (id: string) =>
|
||||
deleteChannel({
|
||||
variables: { id: params.id, input: { targetChannel: id } }
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChannelsListPage
|
||||
channelsList={data?.channels}
|
||||
navigateToChannelCreate={navigateToChannelCreate}
|
||||
onBack={() => navigate(configurationMenuUrl)}
|
||||
onRowClick={id => () => navigate(channelUrl(id))}
|
||||
onRemove={id =>
|
||||
openModal("remove", {
|
||||
id
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{!!selectedChannel && (
|
||||
<ChannelDeleteDialog
|
||||
channelsChoices={channelsChoices}
|
||||
open={params.action === "remove"}
|
||||
confirmButtonState={deleteChannelOpts.status}
|
||||
onBack={() => navigate(channelsListUrl())}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelsList.displayName = "ChannelsList";
|
||||
export default ChannelsList;
|
2
src/channels/views/ChannelsList/index.ts
Normal file
2
src/channels/views/ChannelsList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelsList";
|
||||
export { default } from "./ChannelsList";
|
|
@ -1,4 +1,6 @@
|
|||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { AvailabilityCard } from "@saleor/components/AvailabilityCard";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import { Container } from "@saleor/components/Container";
|
||||
|
@ -7,9 +9,8 @@ import Metadata from "@saleor/components/Metadata";
|
|||
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 { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import React from "react";
|
||||
|
@ -20,25 +21,38 @@ import { CollectionImage } from "../CollectionImage/CollectionImage";
|
|||
import CollectionCreateForm, { CollectionCreateData } from "./form";
|
||||
|
||||
export interface CollectionCreatePageProps {
|
||||
channelsCount: number;
|
||||
channelsErrors: CollectionChannelListingErrorFragment[];
|
||||
currentChannels: ChannelCollectionData[];
|
||||
disabled: boolean;
|
||||
errors: ProductErrorFragment[];
|
||||
errors: CollectionErrorFragment[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onBack: () => void;
|
||||
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
||||
onChannelsChange: (data: ChannelCollectionData[]) => void;
|
||||
openChannelsModal: () => void;
|
||||
}
|
||||
|
||||
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||
channelsCount,
|
||||
channelsErrors,
|
||||
currentChannels = [],
|
||||
disabled,
|
||||
errors,
|
||||
saveButtonBarState,
|
||||
onBack,
|
||||
onChannelsChange,
|
||||
openChannelsModal,
|
||||
onSubmit
|
||||
}: CollectionCreatePageProps) => {
|
||||
const intl = useIntl();
|
||||
const localizeDate = useDateLocalize();
|
||||
|
||||
return (
|
||||
<CollectionCreateForm onSubmit={onSubmit}>
|
||||
<CollectionCreateForm
|
||||
onSubmit={onSubmit}
|
||||
currentChannels={currentChannels}
|
||||
setChannels={onChannelsChange}
|
||||
>
|
||||
{({ change, data, handlers, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
|
@ -115,30 +129,25 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
|||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||
</div>
|
||||
<div>
|
||||
<VisibilityCard
|
||||
data={data}
|
||||
errors={errors}
|
||||
disabled={disabled}
|
||||
<AvailabilityCard
|
||||
messages={{
|
||||
hiddenLabel: intl.formatMessage({
|
||||
defaultMessage: "Hidden",
|
||||
description: "collection label"
|
||||
}),
|
||||
hiddenSecondLabel: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will be visible from {date}",
|
||||
description: "collection"
|
||||
},
|
||||
{
|
||||
date: localizeDate(data.publicationDate, "L")
|
||||
}
|
||||
),
|
||||
|
||||
visibleLabel: intl.formatMessage({
|
||||
defaultMessage: "Visible",
|
||||
description: "collection label"
|
||||
})
|
||||
}}
|
||||
onChange={change}
|
||||
errors={channelsErrors}
|
||||
selectedChannelsCount={data.channelListings.length}
|
||||
allChannelsCount={channelsCount}
|
||||
channels={data.channelListings}
|
||||
disabled={disabled}
|
||||
onChange={handlers.changeChannels}
|
||||
openModal={openChannelsModal}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||
|
@ -13,10 +15,9 @@ export interface CollectionCreateFormData extends MetadataFormData {
|
|||
value: string;
|
||||
};
|
||||
backgroundImageAlt: string;
|
||||
channelListings: ChannelCollectionData[];
|
||||
name: string;
|
||||
slug: string;
|
||||
publicationDate: string;
|
||||
isPublished: boolean;
|
||||
seoDescription: string;
|
||||
seoTitle: string;
|
||||
}
|
||||
|
@ -27,6 +28,10 @@ export interface CollectionCreateData extends CollectionCreateFormData {
|
|||
interface CollectionCreateHandlers {
|
||||
changeMetadata: FormChange;
|
||||
changeDescription: RichTextEditorChange;
|
||||
changeChannels: (
|
||||
id: string,
|
||||
data: Omit<ChannelCollectionData, "name" | "id">
|
||||
) => void;
|
||||
}
|
||||
export interface UseCollectionCreateFormResult {
|
||||
change: FormChange;
|
||||
|
@ -37,11 +42,15 @@ export interface UseCollectionCreateFormResult {
|
|||
}
|
||||
|
||||
export interface CollectionCreateFormProps {
|
||||
currentChannels: ChannelCollectionData[];
|
||||
setChannels: (data: ChannelCollectionData[]) => void;
|
||||
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
|
||||
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
||||
}
|
||||
|
||||
function useCollectionCreateForm(
|
||||
currentChannels: ChannelCollectionData[],
|
||||
setChannels: (data: ChannelCollectionData[]) => void,
|
||||
onSubmit: (data: CollectionCreateData) => SubmitPromise
|
||||
): UseCollectionCreateFormResult {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
|
@ -53,11 +62,10 @@ function useCollectionCreateForm(
|
|||
value: null
|
||||
},
|
||||
backgroundImageAlt: "",
|
||||
isPublished: false,
|
||||
channelListings: currentChannels,
|
||||
metadata: [],
|
||||
name: "",
|
||||
privateMetadata: [],
|
||||
publicationDate: "",
|
||||
seoDescription: "",
|
||||
seoTitle: "",
|
||||
slug: ""
|
||||
|
@ -83,12 +91,19 @@ function useCollectionCreateForm(
|
|||
description: description.current
|
||||
});
|
||||
|
||||
const handleChannelChange = createChannelsChangeHandler(
|
||||
currentChannels,
|
||||
setChannels,
|
||||
triggerChange
|
||||
);
|
||||
|
||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data: getData(),
|
||||
handlers: {
|
||||
changeChannels: handleChannelChange,
|
||||
changeDescription,
|
||||
changeMetadata
|
||||
},
|
||||
|
@ -98,10 +113,12 @@ function useCollectionCreateForm(
|
|||
}
|
||||
|
||||
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
|
||||
currentChannels,
|
||||
setChannels,
|
||||
children,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useCollectionCreateForm(onSubmit);
|
||||
const props = useCollectionCreateForm(currentChannels, setChannels, onSubmit);
|
||||
|
||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
|||
import RichTextEditor, {
|
||||
RichTextEditorChange
|
||||
} from "@saleor/components/RichTextEditor";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import React from "react";
|
||||
|
@ -19,7 +19,7 @@ export interface CollectionDetailsProps {
|
|||
name: string;
|
||||
};
|
||||
disabled: boolean;
|
||||
errors: ProductErrorFragment[];
|
||||
errors: CollectionErrorFragment[];
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
onDescriptionChange: RichTextEditorChange;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { AvailabilityCard } from "@saleor/components/AvailabilityCard";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import { Container } from "@saleor/components/Container";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||
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 { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { maybe } from "../../../misc";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
||||
import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
||||
|
@ -27,38 +24,50 @@ import CollectionProducts from "../CollectionProducts/CollectionProducts";
|
|||
import CollectionUpdateForm, { CollectionUpdateData } from "./form";
|
||||
|
||||
export interface CollectionDetailsPageProps extends PageListProps, ListActions {
|
||||
channelsCount: number;
|
||||
channelsErrors: CollectionChannelListingErrorFragment[];
|
||||
collection: CollectionDetails_collection;
|
||||
errors: ProductErrorFragment[];
|
||||
isFeatured: boolean;
|
||||
currentChannels: ChannelCollectionData[];
|
||||
errors: CollectionErrorFragment[];
|
||||
hasChannelChanged: boolean;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
selectedChannel: string;
|
||||
onBack: () => void;
|
||||
onCollectionRemove: () => void;
|
||||
onImageDelete: () => void;
|
||||
onImageUpload: (file: File) => void;
|
||||
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
||||
onSubmit: (data: CollectionUpdateData) => SubmitPromise;
|
||||
onChannelsChange: (data: ChannelCollectionData[]) => void;
|
||||
openChannelsModal: () => void;
|
||||
}
|
||||
|
||||
const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||
channelsCount,
|
||||
channelsErrors,
|
||||
collection,
|
||||
currentChannels = [],
|
||||
disabled,
|
||||
errors,
|
||||
isFeatured,
|
||||
hasChannelChanged,
|
||||
saveButtonBarState,
|
||||
selectedChannel,
|
||||
onBack,
|
||||
onCollectionRemove,
|
||||
onImageDelete,
|
||||
onImageUpload,
|
||||
onSubmit,
|
||||
onChannelsChange,
|
||||
openChannelsModal,
|
||||
...collectionProductsProps
|
||||
}: CollectionDetailsPageProps) => {
|
||||
const intl = useIntl();
|
||||
const localizeDate = useDateLocalize();
|
||||
|
||||
return (
|
||||
<CollectionUpdateForm
|
||||
collection={collection}
|
||||
isFeatured={isFeatured}
|
||||
currentChannels={currentChannels}
|
||||
setChannels={onChannelsChange}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ change, data, handlers, hasChanged, submit }) => (
|
||||
|
@ -66,7 +75,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.collections)}
|
||||
</AppHeader>
|
||||
<PageHeader title={maybe(() => collection.name)} />
|
||||
<PageHeader title={collection?.name} />
|
||||
<Grid>
|
||||
<div>
|
||||
<CollectionDetails
|
||||
|
@ -79,7 +88,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
<CardSpacer />
|
||||
<CollectionImage
|
||||
data={data}
|
||||
image={maybe(() => collection.backgroundImage)}
|
||||
image={collection?.backgroundImage}
|
||||
onImageDelete={onImageDelete}
|
||||
onImageUpload={onImageUpload}
|
||||
onChange={change}
|
||||
|
@ -89,6 +98,8 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
<CardSpacer />
|
||||
<CollectionProducts
|
||||
disabled={disabled}
|
||||
channelsCount={channelsCount}
|
||||
selectedChannel={selectedChannel}
|
||||
collection={collection}
|
||||
{...collectionProductsProps}
|
||||
/>
|
||||
|
@ -105,55 +116,38 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={maybe(() => collection.name)}
|
||||
titlePlaceholder={collection?.name}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<VisibilityCard
|
||||
data={data}
|
||||
errors={errors}
|
||||
<AvailabilityCard
|
||||
messages={{
|
||||
hiddenLabel: intl.formatMessage({
|
||||
defaultMessage: "Hidden",
|
||||
description: "collection label"
|
||||
}),
|
||||
hiddenSecondLabel: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will be visible from {date}",
|
||||
description: "collection"
|
||||
},
|
||||
{
|
||||
date: localizeDate(data.publicationDate, "L")
|
||||
}
|
||||
),
|
||||
|
||||
visibleLabel: intl.formatMessage({
|
||||
defaultMessage: "Visible",
|
||||
description: "collection label"
|
||||
})
|
||||
}}
|
||||
onChange={change}
|
||||
>
|
||||
<FormSpacer />
|
||||
<Hr />
|
||||
<ControlledCheckbox
|
||||
name={"isFeatured" as keyof CollectionUpdateData}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Feature on Homepage",
|
||||
description: "switch button"
|
||||
})}
|
||||
checked={data.isFeatured}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</VisibilityCard>
|
||||
errors={channelsErrors}
|
||||
selectedChannelsCount={data.channelListings.length}
|
||||
allChannelsCount={channelsCount}
|
||||
channels={data.channelListings}
|
||||
disabled={disabled}
|
||||
onChange={handlers.changeChannels}
|
||||
openModal={openChannelsModal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
disabled={disabled || (!hasChanged && !hasChannelChanged)}
|
||||
onCancel={onBack}
|
||||
onDelete={onCollectionRemove}
|
||||
onSave={submit}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
||||
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||
|
@ -13,13 +14,11 @@ import React from "react";
|
|||
|
||||
export interface CollectionUpdateFormData extends MetadataFormData {
|
||||
backgroundImageAlt: string;
|
||||
channelListings: ChannelCollectionData[];
|
||||
name: string;
|
||||
slug: string;
|
||||
publicationDate: string;
|
||||
seoDescription: string;
|
||||
seoTitle: string;
|
||||
isFeatured: boolean;
|
||||
isPublished: boolean;
|
||||
}
|
||||
export interface CollectionUpdateData extends CollectionUpdateFormData {
|
||||
description: OutputData;
|
||||
|
@ -28,6 +27,10 @@ export interface CollectionUpdateData extends CollectionUpdateFormData {
|
|||
interface CollectionUpdateHandlers {
|
||||
changeMetadata: FormChange;
|
||||
changeDescription: RichTextEditorChange;
|
||||
changeChannels: (
|
||||
id: string,
|
||||
data: Omit<ChannelCollectionData, "name" | "id">
|
||||
) => void;
|
||||
}
|
||||
export interface UseCollectionUpdateFormResult {
|
||||
change: FormChange;
|
||||
|
@ -40,26 +43,26 @@ export interface UseCollectionUpdateFormResult {
|
|||
export interface CollectionUpdateFormProps {
|
||||
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
|
||||
collection: CollectionDetails_collection;
|
||||
isFeatured: boolean;
|
||||
currentChannels: ChannelCollectionData[];
|
||||
setChannels: (data: ChannelCollectionData[]) => void;
|
||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
|
||||
}
|
||||
|
||||
function useCollectionUpdateForm(
|
||||
collection: CollectionDetails_collection,
|
||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>,
|
||||
isFeatured: boolean
|
||||
currentChannels: ChannelCollectionData[],
|
||||
setChannels: (data: ChannelCollectionData[]) => void,
|
||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>
|
||||
): UseCollectionUpdateFormResult {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const triggerChange = () => setChanged(true);
|
||||
|
||||
const form = useForm<CollectionUpdateFormData>({
|
||||
backgroundImageAlt: collection?.backgroundImage?.alt || "",
|
||||
isFeatured,
|
||||
isPublished: !!collection?.isPublished,
|
||||
channelListings: currentChannels,
|
||||
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
||||
name: collection?.name || "",
|
||||
privateMetadata: collection?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
publicationDate: collection?.publicationDate || "",
|
||||
seoDescription: collection?.seoDescription || "",
|
||||
seoTitle: collection?.seoTitle || "",
|
||||
slug: collection?.slug || ""
|
||||
|
@ -89,16 +92,22 @@ function useCollectionUpdateForm(
|
|||
|
||||
const getSubmitData = (): CollectionUpdateData => ({
|
||||
...getData(),
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
||||
...getPublicationData(form.data)
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
|
||||
});
|
||||
|
||||
const handleChannelChange = createChannelsChangeHandler(
|
||||
currentChannels,
|
||||
setChannels,
|
||||
triggerChange
|
||||
);
|
||||
|
||||
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data: getData(),
|
||||
handlers: {
|
||||
changeChannels: handleChannelChange,
|
||||
changeDescription,
|
||||
changeMetadata
|
||||
},
|
||||
|
@ -108,12 +117,18 @@ function useCollectionUpdateForm(
|
|||
}
|
||||
|
||||
const CollectionUpdateForm: React.FC<CollectionUpdateFormProps> = ({
|
||||
children,
|
||||
collection,
|
||||
isFeatured,
|
||||
currentChannels,
|
||||
setChannels,
|
||||
children,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useCollectionUpdateForm(collection, onSubmit, isFeatured);
|
||||
const props = useCollectionUpdateForm(
|
||||
collection,
|
||||
currentChannels,
|
||||
setChannels,
|
||||
onSubmit
|
||||
);
|
||||
|
||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||
};
|
||||
|
|
|
@ -4,10 +4,10 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
|
@ -15,7 +15,7 @@ import { maybe, renderCollection } from "@saleor/misc";
|
|||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
|
||||
|
@ -49,12 +49,15 @@ interface CollectionListProps
|
|||
ListActions,
|
||||
SortPage<CollectionListUrlSortField> {
|
||||
collections: CollectionList_collections_edges_node[];
|
||||
channelsCount: number;
|
||||
selectedChannel: string;
|
||||
}
|
||||
|
||||
const numberOfColumns = 4;
|
||||
|
||||
const CollectionList: React.FC<CollectionListProps> = props => {
|
||||
const {
|
||||
channelsCount,
|
||||
collections,
|
||||
disabled,
|
||||
settings,
|
||||
|
@ -67,13 +70,13 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
pageInfo,
|
||||
isChecked,
|
||||
selected,
|
||||
selectedChannel,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ResponsiveTable>
|
||||
|
@ -143,6 +146,9 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
collections,
|
||||
collection => {
|
||||
const isSelected = collection ? isChecked(collection.id) : false;
|
||||
const channel = collection?.channelListings.find(
|
||||
listing => listing.channel.id === selectedChannel
|
||||
);
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
|
@ -172,26 +178,18 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
</TableCell>
|
||||
<TableCell
|
||||
className={classes.colAvailability}
|
||||
data-test="published"
|
||||
data-test-published={maybe(() => collection.isPublished)}
|
||||
data-test="availability"
|
||||
data-test-availability={!!collection?.channelListings?.length}
|
||||
>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
status={collection.isPublished ? "success" : "error"}
|
||||
label={
|
||||
collection.isPublished
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "collection is published"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "collection is not published"
|
||||
})
|
||||
}
|
||||
/>
|
||||
),
|
||||
{collection && !collection?.channelListings?.length ? (
|
||||
"-"
|
||||
) : collection?.channelListings !== undefined ? (
|
||||
<ChannelsAvailabilityDropdown
|
||||
allChannelsCount={channelsCount}
|
||||
currentChannel={channel}
|
||||
channels={collection?.channelListings}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import { Container } from "@saleor/components/Container";
|
||||
import FilterBar from "@saleor/components/FilterBar";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SearchBar from "@saleor/components/SearchBar";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import {
|
||||
FilterPageProps,
|
||||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
SortPage,
|
||||
TabPageProps
|
||||
} from "@saleor/types";
|
||||
|
@ -17,44 +19,64 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
import CollectionList from "../CollectionList/CollectionList";
|
||||
import {
|
||||
CollectionFilterKeys,
|
||||
CollectionListFilterOpts,
|
||||
createFilterStructure
|
||||
} from "./filters";
|
||||
|
||||
export interface CollectionListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
FilterPageProps<CollectionFilterKeys, CollectionListFilterOpts>,
|
||||
SearchPageProps,
|
||||
SortPage<CollectionListUrlSortField>,
|
||||
TabPageProps {
|
||||
collections: CollectionList_collections_edges_node[];
|
||||
channelsCount: number;
|
||||
selectedChannel: string;
|
||||
onSettingsOpen?: () => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
settings: {
|
||||
marginRight: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "CollectionListPage" }
|
||||
);
|
||||
|
||||
const CollectionListPage: React.FC<CollectionListPageProps> = ({
|
||||
currencySymbol,
|
||||
channelsCount,
|
||||
currentTab,
|
||||
disabled,
|
||||
filterOpts,
|
||||
initialSearch,
|
||||
onAdd,
|
||||
onAll,
|
||||
onFilterChange,
|
||||
onSearchChange,
|
||||
onSettingsOpen,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
selectedChannel,
|
||||
tabs,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const structure = createFilterStructure(intl, filterOpts);
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.collections)}>
|
||||
{!!onSettingsOpen && (
|
||||
<CardMenu
|
||||
className={classes.settings}
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Settings",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: onSettingsOpen
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
|
@ -68,27 +90,29 @@ const CollectionListPage: React.FC<CollectionListPageProps> = ({
|
|||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<FilterBar
|
||||
<SearchBar
|
||||
allTabLabel={intl.formatMessage({
|
||||
defaultMessage: "All Collections",
|
||||
description: "tab name"
|
||||
})}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
filterStructure={structure}
|
||||
initialSearch={initialSearch}
|
||||
searchPlaceholder={intl.formatMessage({
|
||||
defaultMessage: "Search Collection"
|
||||
})}
|
||||
tabs={tabs}
|
||||
onAll={onAll}
|
||||
onFilterChange={onFilterChange}
|
||||
onSearchChange={onSearchChange}
|
||||
onTabChange={onTabChange}
|
||||
onTabDelete={onTabDelete}
|
||||
onTabSave={onTabSave}
|
||||
/>
|
||||
<CollectionList disabled={disabled} {...listProps} />
|
||||
<CollectionList
|
||||
disabled={disabled}
|
||||
channelsCount={channelsCount}
|
||||
selectedChannel={selectedChannel}
|
||||
{...listProps}
|
||||
/>
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -8,10 +8,10 @@ import TableFooter from "@material-ui/core/TableFooter";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
|
@ -57,6 +57,8 @@ const useStyles = makeStyles(
|
|||
|
||||
export interface CollectionProductsProps extends PageListProps, ListActions {
|
||||
collection: CollectionDetails_collection;
|
||||
channelsCount: number;
|
||||
selectedChannel: string;
|
||||
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
||||
}
|
||||
|
||||
|
@ -64,6 +66,7 @@ const numberOfColumns = 5;
|
|||
|
||||
const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||
const {
|
||||
channelsCount,
|
||||
collection,
|
||||
disabled,
|
||||
onAdd,
|
||||
|
@ -72,6 +75,7 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
|||
onProductUnassign,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
selectedChannel,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -139,8 +143,8 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
|||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product is published"
|
||||
defaultMessage="Availability"
|
||||
description="product availability"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions} />
|
||||
|
@ -149,9 +153,9 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
|||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={maybe(() => pageInfo.hasNextPage)}
|
||||
hasNextPage={pageInfo?.hasNextPage}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={maybe(() => pageInfo.hasPreviousPage)}
|
||||
hasPreviousPage={pageInfo?.hasPreviousPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
|
@ -161,6 +165,10 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
|||
maybe(() => collection.products.edges.map(edge => edge.node)),
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
const channel =
|
||||
product?.channelListings.find(
|
||||
listing => listing.channel.id === selectedChannel
|
||||
) || product?.channelListings[0];
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
|
@ -190,24 +198,16 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isPublished
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product is published"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product is not published"
|
||||
})
|
||||
}
|
||||
status={product.isPublished ? "success" : "error"}
|
||||
/>
|
||||
),
|
||||
<TableCell className={classes.colType}>
|
||||
{product && !product?.channelListings?.length ? (
|
||||
"-"
|
||||
) : product?.channelListings !== undefined ? (
|
||||
<ChannelsAvailabilityDropdown
|
||||
allChannelsCount={channelsCount}
|
||||
currentChannel={channel}
|
||||
channels={product?.channelListings}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
|
@ -7,8 +7,19 @@ const content = richTextEditorFixtures.richTextEditor;
|
|||
export const collections: CollectionList_collections_edges_node[] = [
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "123",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjox",
|
||||
isPublished: true,
|
||||
name: "Summer collection",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -17,8 +28,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "124",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjoy",
|
||||
isPublished: true,
|
||||
name: "Winter sale",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -27,8 +49,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "125",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjoz",
|
||||
isPublished: true,
|
||||
name: "Vintage vibes",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -37,8 +70,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "126",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjoa",
|
||||
isPublished: true,
|
||||
name: "Merry Christmas",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -47,8 +91,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "127",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjob",
|
||||
isPublished: true,
|
||||
name: "80s Miami",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -57,8 +112,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "128",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
id: "Q29sbGVjdGlvbjoc",
|
||||
isPublished: true,
|
||||
name: "Yellow Submarine 2019",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
|
@ -79,9 +145,20 @@ export const collection: (
|
|||
alt: "Alt text",
|
||||
url: placeholderCollectionImage
|
||||
},
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "CollectionChannelListing",
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
id: "223",
|
||||
name: "Channel"
|
||||
},
|
||||
isPublished: false,
|
||||
publicationDate: null
|
||||
}
|
||||
],
|
||||
descriptionJson: JSON.stringify(content),
|
||||
id: "Q29sbGVjdGlvbjox",
|
||||
isPublished: true,
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
|
@ -96,11 +173,49 @@ export const collection: (
|
|||
edges: [
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "2020-07-14",
|
||||
visibleInListings: true
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "124",
|
||||
name: "Channel2"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: "2020-07-30",
|
||||
visibleInListings: true
|
||||
}
|
||||
],
|
||||
id: "UHJvZHVjdDoxNw==",
|
||||
isPublished: true,
|
||||
name: "Murray Inc",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
|
@ -112,11 +227,49 @@ export const collection: (
|
|||
},
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "2020-07-14",
|
||||
visibleInListings: true
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "124",
|
||||
name: "Channel2"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: "2020-07-30",
|
||||
visibleInListings: true
|
||||
}
|
||||
],
|
||||
id: "UHJvZHVjdDoyNw==",
|
||||
isPublished: true,
|
||||
name: "Williams-Taylor",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
|
@ -128,11 +281,49 @@ export const collection: (
|
|||
},
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
cursor: "YXJyYXljb25uZWN0aW9uOjI=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "2020-07-14",
|
||||
visibleInListings: false
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "124",
|
||||
name: "Channel2"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: "2020-07-30",
|
||||
visibleInListings: false
|
||||
}
|
||||
],
|
||||
id: "UHJvZHVjdDoyOQ==",
|
||||
isPublished: true,
|
||||
name: "Hebert-Sherman",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
|
@ -144,11 +335,49 @@ export const collection: (
|
|||
},
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
cursor: "YXJyYXljb25uZWN0aW9uOjM=",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
channelListings: [
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "123",
|
||||
name: "Channel1"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "2020-07-14",
|
||||
visibleInListings: false
|
||||
},
|
||||
{
|
||||
__typename: "ProductChannelListing",
|
||||
availableForPurchase: null,
|
||||
channel: {
|
||||
__typename: "Channel",
|
||||
currencyCode: "USD",
|
||||
id: "124",
|
||||
name: "Channel2"
|
||||
},
|
||||
discountedPrice: {
|
||||
__typename: "Money",
|
||||
amount: 1,
|
||||
currency: "USD"
|
||||
},
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: false,
|
||||
publicationDate: "2020-07-30",
|
||||
visibleInListings: false
|
||||
}
|
||||
],
|
||||
id: "UHJvZHVjdDo1Mw==",
|
||||
isPublished: true,
|
||||
name: "Estes, Johnson and Graham",
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
|
@ -167,7 +396,6 @@ export const collection: (
|
|||
startCursor: ""
|
||||
}
|
||||
},
|
||||
publicationDate: "2018-08-25T18:45:54.125Z",
|
||||
seoDescription: "",
|
||||
seoTitle: "",
|
||||
slug: "summer-collection"
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import {
|
||||
CollectionChannelListingUpdate,
|
||||
CollectionChannelListingUpdateVariables
|
||||
} from "@saleor/collections/types/CollectionChannelListingUpdate";
|
||||
import {
|
||||
collectionDetailsFragment,
|
||||
collectionProductFragment
|
||||
} from "@saleor/fragments/collections";
|
||||
import {
|
||||
productErrorFragment,
|
||||
shopErrorFragment
|
||||
collectionChannelListingErrorFragment,
|
||||
collectionsErrorFragment,
|
||||
productErrorFragment
|
||||
} from "@saleor/fragments/errors";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
import gql from "graphql-tag";
|
||||
|
@ -17,18 +22,10 @@ import {
|
|||
CollectionBulkDelete,
|
||||
CollectionBulkDeleteVariables
|
||||
} from "./types/CollectionBulkDelete";
|
||||
import {
|
||||
CollectionBulkPublish,
|
||||
CollectionBulkPublishVariables
|
||||
} from "./types/CollectionBulkPublish";
|
||||
import {
|
||||
CollectionUpdate,
|
||||
CollectionUpdateVariables
|
||||
} from "./types/CollectionUpdate";
|
||||
import {
|
||||
CollectionUpdateWithHomepage,
|
||||
CollectionUpdateWithHomepageVariables
|
||||
} from "./types/CollectionUpdateWithHomepage";
|
||||
import {
|
||||
CreateCollection,
|
||||
CreateCollectionVariables
|
||||
|
@ -44,14 +41,14 @@ import {
|
|||
|
||||
const collectionUpdate = gql`
|
||||
${collectionDetailsFragment}
|
||||
${productErrorFragment}
|
||||
${collectionsErrorFragment}
|
||||
mutation CollectionUpdate($id: ID!, $input: CollectionInput!) {
|
||||
collectionUpdate(id: $id, input: $input) {
|
||||
collection {
|
||||
...CollectionDetailsFragment
|
||||
}
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
errors: collectionErrors {
|
||||
...CollectionErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,43 +58,9 @@ export const useCollectionUpdateMutation = makeMutation<
|
|||
CollectionUpdateVariables
|
||||
>(collectionUpdate);
|
||||
|
||||
const collectionUpdateWithHomepage = gql`
|
||||
${collectionDetailsFragment}
|
||||
${productErrorFragment}
|
||||
${shopErrorFragment}
|
||||
mutation CollectionUpdateWithHomepage(
|
||||
$id: ID!
|
||||
$input: CollectionInput!
|
||||
$homepageId: ID
|
||||
) {
|
||||
homepageCollectionUpdate(collection: $homepageId) {
|
||||
errors: shopErrors {
|
||||
...ShopErrorFragment
|
||||
}
|
||||
shop {
|
||||
homepageCollection {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
collectionUpdate(id: $id, input: $input) {
|
||||
collection {
|
||||
...CollectionDetailsFragment
|
||||
}
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useCollectionUpdateWithHomepageMutation = makeMutation<
|
||||
CollectionUpdateWithHomepage,
|
||||
CollectionUpdateWithHomepageVariables
|
||||
>(collectionUpdateWithHomepage);
|
||||
|
||||
const assignCollectionProduct = gql`
|
||||
${collectionProductFragment}
|
||||
${productErrorFragment}
|
||||
${collectionsErrorFragment}
|
||||
mutation CollectionAssignProduct(
|
||||
$collectionId: ID!
|
||||
$productIds: [ID!]!
|
||||
|
@ -123,8 +86,8 @@ const assignCollectionProduct = gql`
|
|||
}
|
||||
}
|
||||
}
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
errors: collectionErrors {
|
||||
...CollectionErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,14 +99,14 @@ export const useCollectionAssignProductMutation = makeMutation<
|
|||
|
||||
const createCollection = gql`
|
||||
${collectionDetailsFragment}
|
||||
${productErrorFragment}
|
||||
${collectionsErrorFragment}
|
||||
mutation CreateCollection($input: CollectionCreateInput!) {
|
||||
collectionCreate(input: $input) {
|
||||
collection {
|
||||
...CollectionDetailsFragment
|
||||
}
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
errors: collectionErrors {
|
||||
...CollectionErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,11 +117,11 @@ export const useCollectionCreateMutation = makeMutation<
|
|||
>(createCollection);
|
||||
|
||||
const removeCollection = gql`
|
||||
${productErrorFragment}
|
||||
${collectionsErrorFragment}
|
||||
mutation RemoveCollection($id: ID!) {
|
||||
collectionDelete(id: $id) {
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
errors: collectionErrors {
|
||||
...CollectionErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +132,7 @@ export const useCollectionRemoveMutation = makeMutation<
|
|||
>(removeCollection);
|
||||
|
||||
const unassignCollectionProduct = gql`
|
||||
${productErrorFragment}
|
||||
${collectionsErrorFragment}
|
||||
mutation UnassignCollectionProduct(
|
||||
$collectionId: ID!
|
||||
$productIds: [ID]!
|
||||
|
@ -188,7 +151,6 @@ const unassignCollectionProduct = gql`
|
|||
edges {
|
||||
node {
|
||||
id
|
||||
isPublished
|
||||
name
|
||||
productType {
|
||||
id
|
||||
|
@ -207,8 +169,8 @@ const unassignCollectionProduct = gql`
|
|||
}
|
||||
}
|
||||
}
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
errors: collectionErrors {
|
||||
...CollectionErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,17 +195,20 @@ export const useCollectionBulkDelete = makeMutation<
|
|||
CollectionBulkDeleteVariables
|
||||
>(collectionBulkDelete);
|
||||
|
||||
const collectionBulkPublish = gql`
|
||||
${productErrorFragment}
|
||||
mutation CollectionBulkPublish($ids: [ID]!, $isPublished: Boolean!) {
|
||||
collectionBulkPublish(ids: $ids, isPublished: $isPublished) {
|
||||
errors: productErrors {
|
||||
...ProductErrorFragment
|
||||
const collectionChannelListingUpdate = gql`
|
||||
${collectionChannelListingErrorFragment}
|
||||
mutation CollectionChannelListingUpdate(
|
||||
$id: ID!
|
||||
$input: CollectionChannelListingUpdateInput!
|
||||
) {
|
||||
collectionChannelListingUpdate(id: $id, input: $input) {
|
||||
errors: collectionChannelListingErrors {
|
||||
...CollectionChannelListingErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useCollectionBulkPublish = makeMutation<
|
||||
CollectionBulkPublish,
|
||||
CollectionBulkPublishVariables
|
||||
>(collectionBulkPublish);
|
||||
export const useCollectionChannelListingUpdate = makeMutation<
|
||||
CollectionChannelListingUpdate,
|
||||
CollectionChannelListingUpdateVariables
|
||||
>(collectionChannelListingUpdate);
|
||||
|
|
|
@ -82,11 +82,6 @@ export const collectionDetails = gql`
|
|||
}
|
||||
}
|
||||
}
|
||||
shop {
|
||||
homepageCollection {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionDetailsQuery = TypedQuery<
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: CollectionAssignProduct
|
||||
|
@ -19,13 +19,37 @@ export interface CollectionAssignProduct_collectionAddProducts_collection_produc
|
|||
url: string;
|
||||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings_discountedPrice {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings {
|
||||
__typename: "ProductChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
discountedPrice: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings_discountedPrice | null;
|
||||
isAvailableForPurchase: boolean | null;
|
||||
availableForPurchase: any | null;
|
||||
visibleInListings: boolean;
|
||||
channel: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
productType: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_productType;
|
||||
thumbnail: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_thumbnail | null;
|
||||
channelListings: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_channelListings[] | null;
|
||||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges {
|
||||
|
@ -54,8 +78,8 @@ export interface CollectionAssignProduct_collectionAddProducts_collection {
|
|||
}
|
||||
|
||||
export interface CollectionAssignProduct_collectionAddProducts_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
|
31
src/collections/types/CollectionChannelListingUpdate.ts
Normal file
31
src/collections/types/CollectionChannelListingUpdate.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CollectionChannelListingUpdateInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: CollectionChannelListingUpdate
|
||||
// ====================================================
|
||||
|
||||
export interface CollectionChannelListingUpdate_collectionChannelListingUpdate_errors {
|
||||
__typename: "CollectionChannelListingError";
|
||||
code: ProductErrorCode;
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
channels: string[] | null;
|
||||
}
|
||||
|
||||
export interface CollectionChannelListingUpdate_collectionChannelListingUpdate {
|
||||
__typename: "CollectionChannelListingUpdate";
|
||||
errors: CollectionChannelListingUpdate_collectionChannelListingUpdate_errors[];
|
||||
}
|
||||
|
||||
export interface CollectionChannelListingUpdate {
|
||||
collectionChannelListingUpdate: CollectionChannelListingUpdate_collectionChannelListingUpdate | null;
|
||||
}
|
||||
|
||||
export interface CollectionChannelListingUpdateVariables {
|
||||
id: string;
|
||||
input: CollectionChannelListingUpdateInput;
|
||||
}
|
|
@ -6,6 +6,19 @@
|
|||
// GraphQL query operation: CollectionDetails
|
||||
// ====================================================
|
||||
|
||||
export interface CollectionDetails_collection_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_channelListings {
|
||||
__typename: "CollectionChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
channel: CollectionDetails_collection_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_metadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
|
@ -35,13 +48,37 @@ export interface CollectionDetails_collection_products_edges_node_thumbnail {
|
|||
url: string;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_products_edges_node_channelListings_discountedPrice {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_products_edges_node_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_products_edges_node_channelListings {
|
||||
__typename: "ProductChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
discountedPrice: CollectionDetails_collection_products_edges_node_channelListings_discountedPrice | null;
|
||||
isAvailableForPurchase: boolean | null;
|
||||
availableForPurchase: any | null;
|
||||
visibleInListings: boolean;
|
||||
channel: CollectionDetails_collection_products_edges_node_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_products_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
productType: CollectionDetails_collection_products_edges_node_productType;
|
||||
thumbnail: CollectionDetails_collection_products_edges_node_thumbnail | null;
|
||||
channelListings: CollectionDetails_collection_products_edges_node_channelListings[] | null;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_collection_products_edges {
|
||||
|
@ -66,32 +103,20 @@ export interface CollectionDetails_collection_products {
|
|||
export interface CollectionDetails_collection {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
channelListings: CollectionDetails_collection_channelListings[] | null;
|
||||
metadata: (CollectionDetails_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||
slug: string;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
seoDescription: string | null;
|
||||
seoTitle: string | null;
|
||||
products: CollectionDetails_collection_products | null;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_shop_homepageCollection {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CollectionDetails_shop {
|
||||
__typename: "Shop";
|
||||
homepageCollection: CollectionDetails_shop_homepageCollection | null;
|
||||
}
|
||||
|
||||
export interface CollectionDetails {
|
||||
collection: CollectionDetails_collection | null;
|
||||
shop: CollectionDetails_shop;
|
||||
}
|
||||
|
||||
export interface CollectionDetailsVariables {
|
||||
|
|
|
@ -8,6 +8,19 @@ import { CollectionFilterInput, CollectionSortingInput } from "./../../types/glo
|
|||
// GraphQL query operation: CollectionList
|
||||
// ====================================================
|
||||
|
||||
export interface CollectionList_collections_edges_node_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CollectionList_collections_edges_node_channelListings {
|
||||
__typename: "CollectionChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
channel: CollectionList_collections_edges_node_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CollectionList_collections_edges_node_products {
|
||||
__typename: "ProductCountableConnection";
|
||||
totalCount: number | null;
|
||||
|
@ -16,8 +29,8 @@ export interface CollectionList_collections_edges_node_products {
|
|||
export interface CollectionList_collections_edges_node {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
channelListings: CollectionList_collections_edges_node_channelListings[] | null;
|
||||
products: CollectionList_collections_edges_node_products | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,25 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CollectionInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionInput, CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: CollectionUpdate
|
||||
// ====================================================
|
||||
|
||||
export interface CollectionUpdate_collectionUpdate_collection_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CollectionUpdate_collectionUpdate_collection_channelListings {
|
||||
__typename: "CollectionChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
channel: CollectionUpdate_collectionUpdate_collection_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CollectionUpdate_collectionUpdate_collection_metadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
|
@ -29,21 +42,20 @@ export interface CollectionUpdate_collectionUpdate_collection_backgroundImage {
|
|||
export interface CollectionUpdate_collectionUpdate_collection {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
channelListings: CollectionUpdate_collectionUpdate_collection_channelListings[] | null;
|
||||
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||
slug: string;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
seoDescription: string | null;
|
||||
seoTitle: string | null;
|
||||
}
|
||||
|
||||
export interface CollectionUpdate_collectionUpdate_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,32 +2,23 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CollectionInput, ShopErrorCode, ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionInput, CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: CollectionUpdateWithHomepage
|
||||
// ====================================================
|
||||
|
||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_errors {
|
||||
__typename: "ShopError";
|
||||
code: ShopErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_shop_homepageCollection {
|
||||
__typename: "Collection";
|
||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_shop {
|
||||
__typename: "Shop";
|
||||
homepageCollection: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop_homepageCollection | null;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate {
|
||||
__typename: "HomepageCollectionUpdate";
|
||||
errors: CollectionUpdateWithHomepage_homepageCollectionUpdate_errors[];
|
||||
shop: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop | null;
|
||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing {
|
||||
__typename: "CollectionChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
channel: CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing_channel;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_metadata {
|
||||
|
@ -51,21 +42,20 @@ export interface CollectionUpdateWithHomepage_collectionUpdate_collection_backgr
|
|||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
channelListings: CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing[] | null;
|
||||
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
||||
slug: string;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
seoDescription: string | null;
|
||||
seoTitle: string | null;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage_collectionUpdate_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
@ -76,12 +66,10 @@ export interface CollectionUpdateWithHomepage_collectionUpdate {
|
|||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepage {
|
||||
homepageCollectionUpdate: CollectionUpdateWithHomepage_homepageCollectionUpdate | null;
|
||||
collectionUpdate: CollectionUpdateWithHomepage_collectionUpdate | null;
|
||||
}
|
||||
|
||||
export interface CollectionUpdateWithHomepageVariables {
|
||||
id: string;
|
||||
input: CollectionInput;
|
||||
homepageId?: string | null;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,25 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CollectionCreateInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionCreateInput, CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: CreateCollection
|
||||
// ====================================================
|
||||
|
||||
export interface CreateCollection_collectionCreate_collection_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CreateCollection_collectionCreate_collection_channelListings {
|
||||
__typename: "CollectionChannelListing";
|
||||
isPublished: boolean;
|
||||
publicationDate: any | null;
|
||||
channel: CreateCollection_collectionCreate_collection_channelListings_channel;
|
||||
}
|
||||
|
||||
export interface CreateCollection_collectionCreate_collection_metadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
|
@ -29,21 +42,20 @@ export interface CreateCollection_collectionCreate_collection_backgroundImage {
|
|||
export interface CreateCollection_collectionCreate_collection {
|
||||
__typename: "Collection";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
channelListings: CreateCollection_collectionCreate_collection_channelListings[] | null;
|
||||
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
||||
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||
slug: string;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
seoDescription: string | null;
|
||||
seoTitle: string | null;
|
||||
}
|
||||
|
||||
export interface CreateCollection_collectionCreate_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: RemoveCollection
|
||||
// ====================================================
|
||||
|
||||
export interface RemoveCollection_collectionDelete_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: UnassignCollectionProduct
|
||||
|
@ -22,7 +22,6 @@ export interface UnassignCollectionProduct_collectionRemoveProducts_collection_p
|
|||
export interface UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
productType: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_productType;
|
||||
thumbnail: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_thumbnail | null;
|
||||
|
@ -54,8 +53,8 @@ export interface UnassignCollectionProduct_collectionRemoveProducts_collection {
|
|||
}
|
||||
|
||||
export interface UnassignCollectionProduct_collectionRemoveProducts_errors {
|
||||
__typename: "ProductError";
|
||||
code: ProductErrorCode;
|
||||
__typename: "CollectionError";
|
||||
code: CollectionErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,7 @@ export enum CollectionListUrlFiltersEnum {
|
|||
query = "query"
|
||||
}
|
||||
export type CollectionListUrlFilters = Filters<CollectionListUrlFiltersEnum>;
|
||||
export type CollectionListUrlDialog =
|
||||
| "publish"
|
||||
| "unpublish"
|
||||
| "remove"
|
||||
| TabActionDialog;
|
||||
export type CollectionListUrlDialog = "remove" | "settings" | TabActionDialog;
|
||||
export enum CollectionListUrlSortField {
|
||||
name = "name",
|
||||
available = "available",
|
||||
|
|
21
src/collections/utils.ts
Normal file
21
src/collections/utils.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||
|
||||
export const createChannelsChangeHandler = (
|
||||
channelListings: ChannelCollectionData[],
|
||||
updateChannels: (data: ChannelCollectionData[]) => void,
|
||||
triggerChange: () => void
|
||||
) => (id: string, data: Omit<ChannelCollectionData, "name" | "id">) => {
|
||||
const channelIndex = channelListings.findIndex(channel => channel.id === id);
|
||||
const channel = channelListings[channelIndex];
|
||||
|
||||
const updatedChannels = [
|
||||
...channelListings.slice(0, channelIndex),
|
||||
{
|
||||
...channel,
|
||||
...data
|
||||
},
|
||||
...channelListings.slice(channelIndex + 1)
|
||||
];
|
||||
updateChannels(updatedChannels);
|
||||
triggerChange();
|
||||
};
|
|
@ -1,8 +1,11 @@
|
|||
import { useChannelsList } from "@saleor/channels/queries";
|
||||
import { createCollectionChannels } from "@saleor/channels/utils";
|
||||
import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useChannels from "@saleor/hooks/useChannels";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||
import {
|
||||
useMetadataUpdate,
|
||||
|
@ -14,7 +17,10 @@ import { useIntl } from "react-intl";
|
|||
import { CollectionCreateInput } from "../../types/globalTypes";
|
||||
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
||||
import { CollectionCreateData } from "../components/CollectionCreatePage/form";
|
||||
import { useCollectionCreateMutation } from "../mutations";
|
||||
import {
|
||||
useCollectionChannelListingUpdate,
|
||||
useCollectionCreateMutation
|
||||
} from "../mutations";
|
||||
import { collectionListUrl, collectionUrl } from "../urls";
|
||||
|
||||
export const CollectionCreate: React.FC = () => {
|
||||
|
@ -24,6 +30,31 @@ export const CollectionCreate: React.FC = () => {
|
|||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const [
|
||||
updateChannels,
|
||||
updateChannelsOpts
|
||||
] = useCollectionChannelListingUpdate({});
|
||||
const { data: channelsData } = useChannelsList({});
|
||||
|
||||
const allChannels = createCollectionChannels(
|
||||
channelsData?.channels
|
||||
)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
|
||||
const {
|
||||
channelListElements,
|
||||
channelsToggle,
|
||||
currentChannels,
|
||||
handleChannelsConfirm,
|
||||
handleChannelsModalClose,
|
||||
handleChannelsModalOpen,
|
||||
isChannelSelected,
|
||||
isChannelsModalOpen,
|
||||
setCurrentChannels,
|
||||
toggleAllChannels
|
||||
} = useChannels(allChannels);
|
||||
|
||||
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.collectionCreate.errors.length === 0) {
|
||||
|
@ -58,13 +89,29 @@ export const CollectionCreate: React.FC = () => {
|
|||
seo: {
|
||||
description: formData.seoDescription,
|
||||
title: formData.seoTitle
|
||||
},
|
||||
...getPublicationData(formData)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result.data?.collectionCreate.collection?.id || null;
|
||||
const id = result.data?.collectionCreate.collection?.id || null;
|
||||
if (id) {
|
||||
updateChannels({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
addChannels: formData.channelListings.map(channel => ({
|
||||
channelId: channel.id,
|
||||
isPublished: channel.isPublished,
|
||||
publicationDate: channel.publicationDate
|
||||
})),
|
||||
removeChannels: []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
const handleSubmit = createMetadataCreateHandler(
|
||||
|
@ -81,10 +128,34 @@ export const CollectionCreate: React.FC = () => {
|
|||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
{!!allChannels?.length && (
|
||||
<ChannelsAvailabilityDialog
|
||||
isSelected={isChannelSelected}
|
||||
disabled={!channelListElements.length}
|
||||
channels={allChannels}
|
||||
onChange={channelsToggle}
|
||||
onClose={handleChannelsModalClose}
|
||||
open={isChannelsModalOpen}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Manage Collection Channel Availability"
|
||||
})}
|
||||
confirmButtonState="default"
|
||||
selected={channelListElements.length}
|
||||
onConfirm={handleChannelsConfirm}
|
||||
toggleAll={toggleAllChannels}
|
||||
/>
|
||||
)}
|
||||
<CollectionCreatePage
|
||||
errors={createCollectionOpts.data?.collectionCreate.errors || []}
|
||||
channelsErrors={
|
||||
updateChannelsOpts?.data?.collectionChannelListingUpdate.errors || []
|
||||
}
|
||||
currentChannels={currentChannels}
|
||||
channelsCount={channelsData?.channels?.length}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
onBack={() => navigate(collectionListUrl())}
|
||||
disabled={createCollectionOpts.loading}
|
||||
disabled={createCollectionOpts.loading || updateChannelsOpts.loading}
|
||||
onSubmit={handleSubmit}
|
||||
saveButtonBarState={createCollectionOpts.status}
|
||||
/>
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import { useChannelsList } from "@saleor/channels/queries";
|
||||
import {
|
||||
createCollectionChannels,
|
||||
createCollectionChannelsData
|
||||
} from "@saleor/channels/utils";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import AssignProductDialog from "@saleor/components/AssignProductDialog";
|
||||
import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useChannels from "@saleor/hooks/useChannels";
|
||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
|
@ -19,6 +27,7 @@ import {
|
|||
useMetadataUpdate,
|
||||
usePrivateMetadataUpdate
|
||||
} from "@saleor/utils/metadata/updateMetadata";
|
||||
import { diff } from "fast-array-diff";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -29,9 +38,9 @@ import CollectionDetailsPage from "../components/CollectionDetailsPage/Collectio
|
|||
import { CollectionUpdateData } from "../components/CollectionDetailsPage/form";
|
||||
import {
|
||||
useCollectionAssignProductMutation,
|
||||
useCollectionChannelListingUpdate,
|
||||
useCollectionRemoveMutation,
|
||||
useCollectionUpdateMutation,
|
||||
useCollectionUpdateWithHomepageMutation,
|
||||
useUnassignCollectionProductMutation
|
||||
} from "../mutations";
|
||||
import { TypedCollectionDetailsQuery } from "../queries";
|
||||
|
@ -65,6 +74,12 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const [
|
||||
updateChannels,
|
||||
updateChannelsOpts
|
||||
] = useCollectionChannelListingUpdate({});
|
||||
const { data: channelsData } = useChannelsList({});
|
||||
|
||||
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
||||
if (data.collectionUpdate.errors.length === 0) {
|
||||
notify({
|
||||
|
@ -88,17 +103,6 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
onCompleted: handleCollectionUpdate
|
||||
});
|
||||
|
||||
const [
|
||||
updateCollectionWithHomepage,
|
||||
updateCollectionWithHomepageOpts
|
||||
] = useCollectionUpdateWithHomepageMutation({
|
||||
onCompleted: data => {
|
||||
if (data.homepageCollectionUpdate.errors.length === 0) {
|
||||
handleCollectionUpdate(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [assignProduct, assignProductOpts] = useCollectionAssignProductMutation(
|
||||
{
|
||||
onCompleted: data => {
|
||||
|
@ -155,6 +159,8 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const handleBack = () => navigate(collectionListUrl());
|
||||
|
||||
const [selectedChannel] = useLocalStorage("collectionListChannel", "");
|
||||
|
||||
return (
|
||||
<TypedCollectionDetailsQuery
|
||||
displayLoader
|
||||
|
@ -162,50 +168,72 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
>
|
||||
{({ data, loading }) => {
|
||||
const collection = data?.collection;
|
||||
|
||||
if (collection === null) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
}
|
||||
const allChannels = createCollectionChannels(
|
||||
channelsData?.channels
|
||||
)?.sort((channel, nextChannel) =>
|
||||
channel.name.localeCompare(nextChannel.name)
|
||||
);
|
||||
const collectionChannelsChoices = createCollectionChannelsData(
|
||||
collection
|
||||
);
|
||||
const {
|
||||
channelListElements,
|
||||
channelsToggle,
|
||||
currentChannels,
|
||||
handleChannelsConfirm,
|
||||
handleChannelsModalClose,
|
||||
handleChannelsModalOpen,
|
||||
isChannelSelected,
|
||||
isChannelsModalOpen,
|
||||
setCurrentChannels,
|
||||
toggleAllChannels
|
||||
} = useChannels(collectionChannelsChoices);
|
||||
|
||||
const handleUpdate = async (formData: CollectionUpdateData) => {
|
||||
const input: CollectionInput = {
|
||||
backgroundImageAlt: formData.backgroundImageAlt,
|
||||
descriptionJson: JSON.stringify(formData.description),
|
||||
isPublished: formData.isPublished,
|
||||
name: formData.name,
|
||||
publicationDate: formData.publicationDate,
|
||||
seo: {
|
||||
description: formData.seoDescription,
|
||||
title: formData.seoTitle
|
||||
},
|
||||
slug: formData.slug
|
||||
};
|
||||
const isFeatured = data.shop.homepageCollection
|
||||
? data.shop.homepageCollection.id === data.collection.id
|
||||
: false;
|
||||
|
||||
if (formData.isFeatured !== isFeatured) {
|
||||
const result = await updateCollectionWithHomepage({
|
||||
variables: {
|
||||
homepageId: formData.isFeatured ? id : null,
|
||||
id,
|
||||
input
|
||||
}
|
||||
});
|
||||
return [
|
||||
...result.data.collectionUpdate.errors,
|
||||
...result.data.homepageCollectionUpdate.errors
|
||||
];
|
||||
} else {
|
||||
const result = await updateCollection({
|
||||
variables: {
|
||||
id,
|
||||
input
|
||||
}
|
||||
});
|
||||
const result = await updateCollection({
|
||||
variables: {
|
||||
id,
|
||||
input
|
||||
}
|
||||
});
|
||||
const diffChannels = diff(
|
||||
collectionChannelsChoices,
|
||||
formData.channelListings,
|
||||
(a, b) => a.id === b.id
|
||||
);
|
||||
|
||||
return result.data.collectionUpdate.errors;
|
||||
}
|
||||
updateChannels({
|
||||
variables: {
|
||||
id: collection.id,
|
||||
input: {
|
||||
addChannels: formData.channelListings.map(channel => ({
|
||||
channelId: channel.id,
|
||||
isPublished: channel.isPublished,
|
||||
publicationDate: channel.publicationDate
|
||||
})),
|
||||
removeChannels:
|
||||
diffChannels.removed?.map(
|
||||
removedChannel => removedChannel.id
|
||||
) || []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result.data.collectionUpdate.errors;
|
||||
};
|
||||
const handleSubmit = createMetadataUpdateHandler(
|
||||
data?.collection,
|
||||
|
@ -215,13 +243,9 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
);
|
||||
|
||||
const formTransitionState = getMutationState(
|
||||
updateCollectionOpts.called ||
|
||||
updateCollectionWithHomepageOpts.called,
|
||||
updateCollectionOpts.loading ||
|
||||
updateCollectionWithHomepageOpts.loading,
|
||||
updateCollectionOpts.data?.collectionUpdate.errors,
|
||||
updateCollectionWithHomepageOpts.data?.collectionUpdate.errors,
|
||||
updateCollectionWithHomepageOpts.data?.homepageCollectionUpdate.errors
|
||||
updateCollectionOpts.called,
|
||||
updateCollectionOpts.loading,
|
||||
updateCollectionOpts.data?.collectionUpdate.errors
|
||||
);
|
||||
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
|
@ -232,17 +256,34 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={maybe(() => data.collection.name)} />
|
||||
<WindowTitle title={data?.collection?.name} />
|
||||
{!!allChannels?.length && (
|
||||
<ChannelsAvailabilityDialog
|
||||
isSelected={isChannelSelected}
|
||||
disabled={!channelListElements.length}
|
||||
channels={allChannels}
|
||||
onChange={channelsToggle}
|
||||
onClose={handleChannelsModalClose}
|
||||
open={isChannelsModalOpen}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Manage Collection Channel Availability"
|
||||
})}
|
||||
confirmButtonState="default"
|
||||
selected={channelListElements.length}
|
||||
onConfirm={handleChannelsConfirm}
|
||||
toggleAll={toggleAllChannels}
|
||||
/>
|
||||
)}
|
||||
<CollectionDetailsPage
|
||||
onAdd={() => openModal("assign")}
|
||||
onBack={handleBack}
|
||||
disabled={loading}
|
||||
disabled={loading || updateChannelsOpts.loading}
|
||||
collection={data?.collection}
|
||||
channelsErrors={
|
||||
updateChannelsOpts?.data?.collectionChannelListingUpdate
|
||||
.errors || []
|
||||
}
|
||||
errors={updateCollectionOpts?.data?.collectionUpdate.errors || []}
|
||||
isFeatured={maybe(
|
||||
() => data.shop.homepageCollection.id === data.collection.id,
|
||||
false
|
||||
)}
|
||||
onCollectionRemove={() => openModal("remove")}
|
||||
onImageDelete={() => openModal("removeImage")}
|
||||
onImageUpload={file =>
|
||||
|
@ -290,6 +331,14 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
currentChannels={currentChannels}
|
||||
hasChannelChanged={
|
||||
collectionChannelsChoices?.length !== currentChannels?.length
|
||||
}
|
||||
channelsCount={channelsData?.channels?.length}
|
||||
selectedChannel={selectedChannel}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
/>
|
||||
<AssignProductDialog
|
||||
confirmButtonState={assignProductOpts.status}
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import ChannelSettingsDialog from "@saleor/channels/components/ChannelSettingsDialog";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||
import SaveFilterTabDialog, {
|
||||
SaveFilterTabDialogFormData
|
||||
} from "@saleor/components/SaveFilterTabDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useChannelsSettings from "@saleor/hooks/useChannelsSettings";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import useShop from "@saleor/hooks/useShop";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
||||
import {
|
||||
useCollectionBulkDelete,
|
||||
useCollectionBulkPublish
|
||||
} from "../../mutations";
|
||||
import { useCollectionBulkDelete } from "../../mutations";
|
||||
import { useCollectionListQuery } from "../../queries";
|
||||
import {
|
||||
collectionAddUrl,
|
||||
|
@ -42,8 +38,6 @@ import {
|
|||
areFiltersApplied,
|
||||
deleteFilterTab,
|
||||
getActiveFilters,
|
||||
getFilterOpts,
|
||||
getFilterQueryParam,
|
||||
getFilterTabs,
|
||||
getFilterVariables,
|
||||
saveFilterTab
|
||||
|
@ -58,7 +52,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const paginate = usePaginator();
|
||||
const shop = useShop();
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
|
@ -97,24 +90,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
collectionBulkPublish,
|
||||
collectionBulkPublishOpts
|
||||
] = useCollectionBulkPublish({
|
||||
onCompleted: data => {
|
||||
if (data.collectionBulkPublish.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -124,23 +99,27 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
: 0
|
||||
: parseInt(params.activeTab, 0);
|
||||
|
||||
const [
|
||||
changeFilters,
|
||||
resetFilters,
|
||||
handleSearchChange
|
||||
] = createFilterHandlers({
|
||||
cleanupFn: reset,
|
||||
createUrl: collectionListUrl,
|
||||
getFilterQueryParam,
|
||||
navigate,
|
||||
params
|
||||
});
|
||||
const handleSearchChange = (query: string) => {
|
||||
navigate(
|
||||
collectionListUrl({
|
||||
...getActiveFilters(params),
|
||||
activeTab: undefined,
|
||||
query
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
CollectionListUrlDialog,
|
||||
CollectionListUrlQueryParams
|
||||
>(navigate, collectionListUrl, params);
|
||||
|
||||
const {
|
||||
channelChoices,
|
||||
handleChannelSelectConfirm,
|
||||
selectedChannel
|
||||
} = useChannelsSettings("collectionListChannel", { closeModal, openModal });
|
||||
|
||||
const handleTabChange = (tab: number) => {
|
||||
reset();
|
||||
navigate(
|
||||
|
@ -169,19 +148,25 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
);
|
||||
|
||||
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!channelChoices?.length && (
|
||||
<ChannelSettingsDialog
|
||||
channelsChoices={channelChoices}
|
||||
defaultChoice={selectedChannel}
|
||||
open={params.action === "settings"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onConfirm={handleChannelSelectConfirm}
|
||||
/>
|
||||
)}
|
||||
<CollectionListPage
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
filterOpts={getFilterOpts(params)}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={handleSearchChange}
|
||||
onFilterChange={changeFilters}
|
||||
onAdd={() => navigate(collectionAddUrl)}
|
||||
onAll={resetFilters}
|
||||
onAll={() => navigate(collectionListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
|
@ -197,108 +182,27 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
sort={getSortParams(params)}
|
||||
onRowClick={id => () => navigate(collectionUrl(id))}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("unpublish", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish collections"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("publish", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish collections"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("remove", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("remove", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
channelsCount={channelChoices?.length}
|
||||
selectedChannel={selectedChannel}
|
||||
onSettingsOpen={
|
||||
!!channelChoices?.length ? () => openModal("settings") : undefined
|
||||
}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "publish" && maybe(() => params.ids.length > 0)}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={collectionBulkPublishOpts.status}
|
||||
onConfirm={() =>
|
||||
collectionBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: true
|
||||
}
|
||||
})
|
||||
}
|
||||
variant="default"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Publish collections",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="{counter,plural,one{Are you sure you want to publish this collection?} other{Are you sure you want to publish {displayQuantity} collections?}}"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={
|
||||
params.action === "unpublish" && maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={collectionBulkPublishOpts.status}
|
||||
onConfirm={() =>
|
||||
collectionBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: false
|
||||
}
|
||||
})
|
||||
}
|
||||
variant="default"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unpublish collections",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="{counter,plural,one{Are you sure you want to unpublish this collection?} other{Are you sure you want to unpublish {displayQuantity} collections?}}"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||
onClose={closeModal}
|
||||
|
|
|
@ -31,6 +31,7 @@ interface ActionDialogProps extends DialogProps {
|
|||
children?: React.ReactNode;
|
||||
confirmButtonLabel?: string;
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
disabled?: boolean;
|
||||
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
|
||||
title: string;
|
||||
variant?: "default" | "delete" | "info";
|
||||
|
@ -42,6 +43,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
|||
children,
|
||||
confirmButtonLabel,
|
||||
confirmButtonState,
|
||||
disabled,
|
||||
open,
|
||||
title,
|
||||
variant,
|
||||
|
@ -58,11 +60,12 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
|||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<Button data-test="back" onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</Button>
|
||||
{variant !== "info" && (
|
||||
<ConfirmButton
|
||||
disabled={disabled}
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
|
@ -70,6 +73,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
|||
className={classNames({
|
||||
[classes.deleteButton]: variant === "delete"
|
||||
})}
|
||||
data-test="submit"
|
||||
>
|
||||
{confirmButtonLabel ||
|
||||
(variant === "delete"
|
||||
|
|
|
@ -238,7 +238,7 @@ const useStyles = makeStyles(
|
|||
padding: 25
|
||||
},
|
||||
popover: {
|
||||
zIndex: 1
|
||||
zIndex: 2
|
||||
},
|
||||
root: {
|
||||
width: `100%`
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import { createChannelsDataFromProduct } from "@saleor/channels/utils";
|
||||
import { product } from "@saleor/products/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
const productChannels = createChannelsDataFromProduct(product(""));
|
||||
|
||||
import AvailabilityCard from "./AvailabilityCard";
|
||||
const props = {
|
||||
data: {
|
||||
availableForPurchase: "",
|
||||
isAvailableForPurchase: false,
|
||||
isPublished: true,
|
||||
publicationDate: "",
|
||||
visibleInListings: true
|
||||
},
|
||||
allChannelsCount: 4,
|
||||
channels: productChannels,
|
||||
errors: [],
|
||||
messages: {
|
||||
hiddenLabel: "Not published",
|
||||
hiddenSecondLabel: "hidden label",
|
||||
visibleLabel: "Published"
|
||||
},
|
||||
onChange: () => undefined
|
||||
onChange: () => undefined,
|
||||
openModal: () => undefined,
|
||||
selectedChannelsCount: 3
|
||||
};
|
||||
|
||||
storiesOf("Generics / AvailabilityCard", module)
|
||||
|
|
|
@ -1,51 +1,92 @@
|
|||
import VisibilityCard, {
|
||||
VisibilityCardProps
|
||||
} from "@saleor/components/VisibilityCard";
|
||||
import ChannelsAvailability, {
|
||||
ChannelsAvailabilityProps,
|
||||
Message
|
||||
} from "@saleor/components/ChannelsAvailability";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
interface AvailabilityCardProps extends VisibilityCardProps {
|
||||
data: {
|
||||
availableForPurchase: string;
|
||||
isAvailableForPurchase: boolean;
|
||||
isPublished: boolean;
|
||||
publicationDate: string;
|
||||
visibleInListings: boolean;
|
||||
};
|
||||
interface AvailabilityCardProps {
|
||||
messages: Message;
|
||||
}
|
||||
|
||||
export const AvailabilityCard: React.FC<AvailabilityCardProps> = props => {
|
||||
export const AvailabilityCard: React.FC<AvailabilityCardProps &
|
||||
Omit<ChannelsAvailabilityProps, "channelsMessages">> = props => {
|
||||
const intl = useIntl();
|
||||
const localizeDate = useDateLocalize();
|
||||
|
||||
return (
|
||||
<VisibilityCard
|
||||
<ChannelsAvailability
|
||||
{...props}
|
||||
messages={{
|
||||
...props.messages,
|
||||
availableLabel: intl.formatMessage({
|
||||
defaultMessage: "Available for purchase",
|
||||
description: "product availability"
|
||||
}),
|
||||
availableSecondLabel: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will become available on {date}",
|
||||
description: "product available for purchase date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(props.data.availableForPurchase, "L")
|
||||
channelsMessages={props.channels.reduce(
|
||||
(prevVal, currVal) => ({
|
||||
...prevVal,
|
||||
[currVal.id]: {
|
||||
...props.messages,
|
||||
availableDateText:
|
||||
currVal.publicationDate && !currVal.isPublished
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Will become available on {date}",
|
||||
description: "channel publication date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(currVal.publicationDate, "L")
|
||||
}
|
||||
)
|
||||
: currVal.publicationDate
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Visible since {date}",
|
||||
description: "channel publication date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(currVal.publicationDate, "L")
|
||||
}
|
||||
)
|
||||
: currVal.isPublished
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Visible",
|
||||
description: "channel publication status"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Hidden",
|
||||
description: "channel publication status"
|
||||
}),
|
||||
availableLabel: intl.formatMessage({
|
||||
defaultMessage: "Available for purchase",
|
||||
description: "product availability"
|
||||
}),
|
||||
availableSecondLabel: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will become available on {date}",
|
||||
description: "product available for purchase date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(currVal.availableForPurchase, "L")
|
||||
}
|
||||
),
|
||||
hiddenSecondLabel: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will become published on {date}",
|
||||
description: "product publication date label"
|
||||
},
|
||||
{
|
||||
date: localizeDate(currVal.publicationDate, "L")
|
||||
}
|
||||
),
|
||||
setAvailabilityDateLabel: intl.formatMessage({
|
||||
defaultMessage: "Set availability date",
|
||||
description: "product availability date label"
|
||||
}),
|
||||
unavailableLabel: intl.formatMessage({
|
||||
defaultMessage: "Unavailable for purchase",
|
||||
description: "product unavailability"
|
||||
})
|
||||
}
|
||||
),
|
||||
setAvailabilityDateLabel: intl.formatMessage({
|
||||
defaultMessage: "Set availability date",
|
||||
description: "product availability date label"
|
||||
}),
|
||||
unavailableLabel: intl.formatMessage({
|
||||
defaultMessage: "Unavailable for purchase",
|
||||
description: "product unavailability"
|
||||
})
|
||||
}}
|
||||
{}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,6 +26,9 @@ export interface CardMenuProps {
|
|||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
container: {
|
||||
zIndex: 1
|
||||
},
|
||||
iconButton: {
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: "100%",
|
||||
|
@ -98,6 +101,7 @@ const CardMenu: React.FC<CardMenuProps> = props => {
|
|||
</IconButton>
|
||||
<Popper
|
||||
placement="bottom-end"
|
||||
className={classes.container}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
transition
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { createChannelsDataFromProduct } from "@saleor/channels/utils";
|
||||
import { product } from "@saleor/products/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ChannelsAvailability, {
|
||||
ChannelsAvailabilityProps
|
||||
} from "./ChannelsAvailability";
|
||||
|
||||
const productChannels = createChannelsDataFromProduct(product(""));
|
||||
|
||||
const props: ChannelsAvailabilityProps = {
|
||||
allChannelsCount: 4,
|
||||
channelsList: productChannels.map(channel => ({
|
||||
id: channel.id,
|
||||
name: channel.name
|
||||
})),
|
||||
errors: [],
|
||||
onChange: () => undefined,
|
||||
openModal: () => undefined,
|
||||
selectedChannelsCount: 3
|
||||
};
|
||||
|
||||
storiesOf("Generics / ChannelsAvailability", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelsAvailability {...props} />)
|
||||
.add("with onChange", () => (
|
||||
<ChannelsAvailability
|
||||
{...props}
|
||||
channelsList={undefined}
|
||||
channels={productChannels}
|
||||
channelsMessages={productChannels.reduce(
|
||||
(prevVal, currVal) => ({
|
||||
...prevVal,
|
||||
[currVal.id]: {
|
||||
availableLabel: "Available",
|
||||
availableSecondLabel: "Will become available",
|
||||
hiddenSecondLabel: "Will become published"
|
||||
}
|
||||
}),
|
||||
{}
|
||||
)}
|
||||
/>
|
||||
));
|
461
src/components/ChannelsAvailability/ChannelsAvailability.tsx
Normal file
461
src/components/ChannelsAvailability/ChannelsAvailability.tsx
Normal file
|
@ -0,0 +1,461 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Channel as ChannelList } from "@saleor/channels/utils";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import RadioSwitchField from "@saleor/components/RadioSwitchField";
|
||||
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||
import { ProductChannelListingErrorFragment } from "@saleor/fragments/types/ProductChannelListingErrorFragment";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import { RequireOnlyOne } from "@saleor/misc";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import classNames from "classnames";
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { DateContext } from "../Date/DateContext";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface ChannelData {
|
||||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
publicationDate: string | null;
|
||||
availableForPurchase?: string;
|
||||
isAvailableForPurchase?: boolean;
|
||||
visibleInListings?: boolean;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
visibleLabel: string;
|
||||
hiddenLabel: string;
|
||||
visibleSecondLabel?: string;
|
||||
hiddenSecondLabel?: string;
|
||||
availableDateText?: string;
|
||||
availableLabel?: string;
|
||||
unavailableLabel?: string;
|
||||
availableSecondLabel?: string;
|
||||
setAvailabilityDateLabel?: string;
|
||||
}
|
||||
|
||||
type Error =
|
||||
| ProductChannelListingErrorFragment
|
||||
| CollectionChannelListingErrorFragment;
|
||||
|
||||
interface Value {
|
||||
availableForPurchase?: string;
|
||||
isAvailableForPurchase?: boolean;
|
||||
isPublished: boolean;
|
||||
publicationDate: string | null;
|
||||
visibleInListings?: boolean;
|
||||
}
|
||||
interface ChannelsAvailability {
|
||||
channels: ChannelData[];
|
||||
channelsList: ChannelList[];
|
||||
channelsMessages?: { [id: string]: Message };
|
||||
errors: Error[];
|
||||
selectedChannelsCount: number;
|
||||
allChannelsCount: number;
|
||||
disabled?: boolean;
|
||||
onChange?: (id: string, data: Value) => void;
|
||||
openModal: () => void;
|
||||
}
|
||||
export type ChannelsAvailabilityProps = RequireOnlyOne<
|
||||
ChannelsAvailability,
|
||||
"channels" | "channelsList"
|
||||
>;
|
||||
|
||||
interface ChannelProps {
|
||||
disabled?: boolean;
|
||||
data: ChannelData;
|
||||
errors: Error[];
|
||||
messages: Message;
|
||||
onChange: (id: string, data: Value) => void;
|
||||
}
|
||||
|
||||
const Channel: React.FC<ChannelProps> = ({
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
messages,
|
||||
onChange
|
||||
}) => {
|
||||
const {
|
||||
availableForPurchase,
|
||||
isAvailableForPurchase: isAvailable,
|
||||
isPublished,
|
||||
publicationDate,
|
||||
visibleInListings,
|
||||
id,
|
||||
name
|
||||
} = data;
|
||||
const formData = {
|
||||
...(availableForPurchase !== undefined ? { availableForPurchase } : {}),
|
||||
...(isAvailable !== undefined
|
||||
? { isAvailableForPurchase: isAvailable }
|
||||
: {}),
|
||||
isPublished,
|
||||
publicationDate,
|
||||
...(visibleInListings !== undefined ? { visibleInListings } : {})
|
||||
};
|
||||
const dateNow = React.useContext(DateContext);
|
||||
const localizeDate = useDateLocalize();
|
||||
const hasAvailableProps =
|
||||
isAvailable !== undefined && availableForPurchase !== undefined;
|
||||
|
||||
const [isPublicationDate, setPublicationDate] = useState(
|
||||
publicationDate === null ? true : false
|
||||
);
|
||||
const [isAvailableDate, setAvailableDate] = useState(false);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
const todayDate = localizeDate(new Date(dateNow).toString(), "YYYY-MM-DD");
|
||||
|
||||
const visibleMessage = (date: string) =>
|
||||
intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "since {date}",
|
||||
description: "date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(date, "L")
|
||||
}
|
||||
);
|
||||
|
||||
const formErrors = getFormErrors(
|
||||
["availableForPurchaseDate", "publicationDate"],
|
||||
errors
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.channelItem}>
|
||||
<div
|
||||
data-test="channel-availability-item"
|
||||
role="button"
|
||||
className={classes.channelBtn}
|
||||
onClick={() => setOpen(open => !open)}
|
||||
>
|
||||
<div className={classes.channelName}>
|
||||
<Typography>{name}</Typography>
|
||||
<ArrowDropdown
|
||||
className={classNames(classes.arrow, {
|
||||
[classes.rotate]: isOpen
|
||||
})}
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="caption">
|
||||
{messages.availableDateText}
|
||||
</Typography>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<>
|
||||
<RadioSwitchField
|
||||
disabled={disabled}
|
||||
firstOptionLabel={
|
||||
<>
|
||||
<p className={classes.label}>{messages.visibleLabel}</p>
|
||||
{isPublished &&
|
||||
publicationDate &&
|
||||
Date.parse(publicationDate) < dateNow && (
|
||||
<span className={classes.secondLabel}>
|
||||
{messages.visibleSecondLabel ||
|
||||
visibleMessage(publicationDate)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
name="isPublished"
|
||||
secondOptionLabel={
|
||||
<>
|
||||
<p className={classes.label}>{messages.hiddenLabel}</p>
|
||||
{publicationDate &&
|
||||
!isPublished &&
|
||||
Date.parse(publicationDate) >= dateNow && (
|
||||
<span className={classes.secondLabel}>
|
||||
{messages.hiddenSecondLabel}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
value={isPublished}
|
||||
onChange={() => {
|
||||
onChange(id, {
|
||||
...formData,
|
||||
isPublished: !isPublished,
|
||||
publicationDate:
|
||||
!isPublished && !publicationDate
|
||||
? todayDate
|
||||
: publicationDate
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{!isPublished && (
|
||||
<>
|
||||
<Typography
|
||||
className={classes.setPublicationDate}
|
||||
onClick={() => setPublicationDate(!isPublicationDate)}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Set publication date"
|
||||
})}
|
||||
</Typography>
|
||||
{isPublicationDate && (
|
||||
<TextField
|
||||
error={!!formErrors.publicationDate}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Publish on",
|
||||
description: "publish on date"
|
||||
})}
|
||||
name={`channel:publicationDate:${id}`}
|
||||
type="date"
|
||||
fullWidth={true}
|
||||
helperText={
|
||||
formErrors.publicationDate
|
||||
? getProductErrorMessage(
|
||||
formErrors.publicationDate,
|
||||
intl
|
||||
)
|
||||
: ""
|
||||
}
|
||||
value={publicationDate ? publicationDate : ""}
|
||||
onChange={e =>
|
||||
onChange(id, {
|
||||
...formData,
|
||||
publicationDate: e.target.value
|
||||
})
|
||||
}
|
||||
className={classes.date}
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{hasAvailableProps && (
|
||||
<>
|
||||
<Hr />
|
||||
<RadioSwitchField
|
||||
disabled={disabled}
|
||||
firstOptionLabel={
|
||||
<>
|
||||
<p className={classes.label}>{messages.availableLabel}</p>
|
||||
{isAvailable &&
|
||||
availableForPurchase &&
|
||||
Date.parse(availableForPurchase) < dateNow && (
|
||||
<span className={classes.secondLabel}>
|
||||
{visibleMessage(availableForPurchase)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
name={`channel:isAvailableForPurchase:${id}`}
|
||||
secondOptionLabel={
|
||||
<>
|
||||
<p className={classes.label}>
|
||||
{messages.unavailableLabel}
|
||||
</p>
|
||||
{availableForPurchase && !isAvailable && (
|
||||
<span className={classes.secondLabel}>
|
||||
{messages.availableSecondLabel}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
value={isAvailable}
|
||||
onChange={e => {
|
||||
const { value } = e.target;
|
||||
return onChange(id, {
|
||||
...formData,
|
||||
availableForPurchase: !value
|
||||
? null
|
||||
: availableForPurchase,
|
||||
isAvailableForPurchase: value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{!isAvailable && (
|
||||
<>
|
||||
<Typography
|
||||
className={classes.setPublicationDate}
|
||||
onClick={() => setAvailableDate(!isAvailableDate)}
|
||||
>
|
||||
{messages.setAvailabilityDateLabel}
|
||||
</Typography>
|
||||
{isAvailableDate && (
|
||||
<TextField
|
||||
error={!!formErrors.availableForPurchaseDate}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Set available on",
|
||||
description: "available on date"
|
||||
})}
|
||||
name={`channel:availableForPurchase:${id}`}
|
||||
type="date"
|
||||
fullWidth={true}
|
||||
helperText={
|
||||
formErrors.availableForPurchaseDate
|
||||
? getProductErrorMessage(
|
||||
formErrors.availableForPurchaseDate,
|
||||
intl
|
||||
)
|
||||
: ""
|
||||
}
|
||||
value={availableForPurchase ? availableForPurchase : ""}
|
||||
onChange={e =>
|
||||
onChange(id, {
|
||||
...formData,
|
||||
availableForPurchase: e.target.value
|
||||
})
|
||||
}
|
||||
className={classes.date}
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{visibleInListings !== undefined && (
|
||||
<>
|
||||
<Hr />
|
||||
<ControlledCheckbox
|
||||
className={classes.checkbox}
|
||||
name={`channel:visibleInListings:${id}`}
|
||||
checked={visibleInListings}
|
||||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
<p
|
||||
className={classNames(
|
||||
classes.label,
|
||||
classes.listingLabel
|
||||
)}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Show in product listings"
|
||||
})}
|
||||
</p>
|
||||
|
||||
<span className={classes.secondLabel}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Disabling this checkbox will remove product from search and category pages. It will be available on collection pages."
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
onChange={e =>
|
||||
onChange(id, {
|
||||
...formData,
|
||||
visibleInListings: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Hr className={classes.hr} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChannelsAvailability: React.FC<ChannelsAvailabilityProps> = props => {
|
||||
const {
|
||||
channelsList,
|
||||
errors,
|
||||
selectedChannelsCount,
|
||||
allChannelsCount,
|
||||
channels,
|
||||
channelsMessages,
|
||||
openModal,
|
||||
onChange
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
const channelsAvailabilityText = intl.formatMessage(
|
||||
{
|
||||
defaultMessage:
|
||||
"Available at {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}",
|
||||
|
||||
description: "channels availability text"
|
||||
},
|
||||
{
|
||||
allChannelsCount,
|
||||
selectedChannelsCount
|
||||
}
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Availability",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={openModal}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Manage",
|
||||
description: "section header button"
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CardContent className={classes.card}>
|
||||
{!!channelsAvailabilityText && (
|
||||
<>
|
||||
<Typography className={classes.channelInfo}>
|
||||
{channelsAvailabilityText}
|
||||
</Typography>
|
||||
<Hr className={classes.hr} />
|
||||
</>
|
||||
)}
|
||||
{channels
|
||||
? channels.map(data => {
|
||||
const channelErrors =
|
||||
errors?.filter(error => error.channels.includes(data.id)) ||
|
||||
[];
|
||||
return (
|
||||
<Channel
|
||||
key={data.id}
|
||||
data={data}
|
||||
errors={channelErrors}
|
||||
onChange={onChange}
|
||||
messages={channelsMessages[data.id]}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: channelsList
|
||||
? channelsList.map(data => (
|
||||
<React.Fragment key={data.id}>
|
||||
<div className={classes.channelItem}>
|
||||
<div className={classes.channelName}>
|
||||
<Typography>{data.name}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Hr className={classes.hr} />
|
||||
</React.Fragment>
|
||||
))
|
||||
: null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
ChannelsAvailability.displayName = "ChannelsAvailability";
|
||||
export default ChannelsAvailability;
|
2
src/components/ChannelsAvailability/index.ts
Normal file
2
src/components/ChannelsAvailability/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelsAvailability";
|
||||
export { default } from "./ChannelsAvailability";
|
78
src/components/ChannelsAvailability/styles.ts
Normal file
78
src/components/ChannelsAvailability/styles.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
arrow: {
|
||||
transition: theme.transitions.duration.short + "ms"
|
||||
},
|
||||
card: {
|
||||
"&:last-child": {
|
||||
paddingBottom: 0
|
||||
},
|
||||
paddingTop: 0
|
||||
},
|
||||
channelBtn: {
|
||||
"&:focus": {
|
||||
outline: "none"
|
||||
},
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
textAlign: "left"
|
||||
},
|
||||
channelInfo: {
|
||||
fontSize: 14,
|
||||
padding: theme.spacing(2, 0)
|
||||
},
|
||||
channelItem: {
|
||||
"&:last-child hr": {
|
||||
display: "none"
|
||||
},
|
||||
padding: theme.spacing(2, 0)
|
||||
},
|
||||
channelName: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(0.5)
|
||||
},
|
||||
checkbox: {
|
||||
alignItems: "flex-start",
|
||||
marginTop: 10
|
||||
},
|
||||
date: {
|
||||
"& svg": {
|
||||
fill: theme.palette.primary.main
|
||||
},
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
hr: {
|
||||
left: theme.spacing(-3),
|
||||
position: "relative",
|
||||
width: `calc(100% + ${theme.spacing(6)}px)`
|
||||
},
|
||||
label: {
|
||||
lineHeight: 1.2,
|
||||
marginBottom: 5,
|
||||
marginTop: 0
|
||||
},
|
||||
listingLabel: {
|
||||
marginTop: 9
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
},
|
||||
secondLabel: {
|
||||
color: theme.palette.text.hint,
|
||||
fontSize: 12
|
||||
},
|
||||
setPublicationDate: {
|
||||
color: theme.palette.primary.main,
|
||||
cursor: "pointer",
|
||||
fontSize: 14,
|
||||
paddingBottom: 10,
|
||||
paddingTop: 0
|
||||
}
|
||||
}),
|
||||
{ name: "ChannelsAvailabilityCard" }
|
||||
);
|
|
@ -0,0 +1,108 @@
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Channel } from "@saleor/channels/utils";
|
||||
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { filter } from "fuzzaldrin";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface ChannelsAvailabilityContentProps {
|
||||
isSelected: (option: Channel) => boolean;
|
||||
channels: Channel[];
|
||||
contentType?: string;
|
||||
disabled: boolean;
|
||||
onChange: (option: Channel) => void;
|
||||
selected?: number;
|
||||
toggleAllText?: string;
|
||||
toggleAll?: (items: Channel[], selected: number) => void;
|
||||
}
|
||||
|
||||
export const ChannelsAvailabilityContent: React.FC<ChannelsAvailabilityContentProps> = ({
|
||||
isSelected,
|
||||
channels,
|
||||
contentType = "",
|
||||
onChange,
|
||||
selected = 0,
|
||||
toggleAll,
|
||||
toggleAllText
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const searchText = intl.formatMessage({
|
||||
defaultMessage: "Search through channels"
|
||||
});
|
||||
const [query, onQueryChange] = React.useState("");
|
||||
const filteredChannels = filter(channels, query, { key: "name" });
|
||||
|
||||
return (
|
||||
<div className={classes.content}>
|
||||
{!!contentType && (
|
||||
<Typography className={classes.text} variant="caption">
|
||||
<FormattedMessage
|
||||
defaultMessage="Select channels you want for {contentType} to be available on"
|
||||
values={{ contentType }}
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
className={classes.input}
|
||||
onChange={e => onQueryChange(e.target.value)}
|
||||
label={searchText}
|
||||
placeholder={searchText}
|
||||
fullWidth
|
||||
/>
|
||||
<div className={classes.dialog}>
|
||||
{!!toggleAll && (
|
||||
<>
|
||||
<ControlledCheckbox
|
||||
checked={selected !== 0}
|
||||
name="allchannels"
|
||||
label={
|
||||
toggleAllText || (
|
||||
<Typography className={classes.label}>
|
||||
<FormattedMessage defaultMessage="Available at all channels" />
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
onChange={() => toggleAll(channels, selected)}
|
||||
/>
|
||||
<Hr />
|
||||
</>
|
||||
)}
|
||||
<Typography className={classes.contentTitle}>
|
||||
<FormattedMessage defaultMessage="Channels A to Z" />
|
||||
</Typography>
|
||||
<div className={classes.scrollArea}>
|
||||
{filteredChannels?.length ? (
|
||||
filteredChannels.map(option => (
|
||||
<div key={option.id} className={classes.option}>
|
||||
<ControlledCheckbox
|
||||
checked={isSelected(option)}
|
||||
name={option.name}
|
||||
label={
|
||||
<Typography className={classes.label}>
|
||||
{option.name}
|
||||
</Typography>
|
||||
}
|
||||
onChange={() => onChange(option)}
|
||||
/>
|
||||
<Hr />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className={classes.notFound}>
|
||||
<FormattedMessage defaultMessage="No Channels found" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ChannelsAvailabilityContent.displayName = "ChannelsAvailabilityContent";
|
||||
export default ChannelsAvailabilityContent;
|
2
src/components/ChannelsAvailabilityContent/index.ts
Normal file
2
src/components/ChannelsAvailabilityContent/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelsAvailabilityContent";
|
||||
export { default } from "./ChannelsAvailabilityContent";
|
47
src/components/ChannelsAvailabilityContent/styles.ts
Normal file
47
src/components/ChannelsAvailabilityContent/styles.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
content: {
|
||||
"& hr": {
|
||||
left: -24,
|
||||
position: "relative",
|
||||
width: "calc(100% + 48px)"
|
||||
}
|
||||
},
|
||||
contentTitle: {
|
||||
margin: theme.spacing(1, 0)
|
||||
},
|
||||
dialog: {
|
||||
marginBottom: -30,
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
input: {
|
||||
"& label": {
|
||||
overflowX: "inherit"
|
||||
}
|
||||
},
|
||||
label: {
|
||||
fontSize: 14
|
||||
},
|
||||
notFound: {
|
||||
paddingBottom: theme.spacing(2)
|
||||
},
|
||||
option: {
|
||||
"&:last-child": {
|
||||
"& hr": {
|
||||
display: "none"
|
||||
}
|
||||
},
|
||||
margin: theme.spacing(1, 0)
|
||||
},
|
||||
scrollArea: {
|
||||
maxHeight: 400,
|
||||
overflowY: "scroll"
|
||||
},
|
||||
text: {
|
||||
marginBottom: 5
|
||||
}
|
||||
}),
|
||||
{ name: "ChannelsAvailabilityContent" }
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
import { channelsList } from "@saleor/channels/fixtures";
|
||||
import { createChannelsData } from "@saleor/channels/utils";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ChannelsAvailabilityDialog, {
|
||||
ChannelsAvailabilityDialogProps
|
||||
} from "./ChannelsAvailabilityDialog";
|
||||
|
||||
const props: ChannelsAvailabilityDialogProps = {
|
||||
channels: createChannelsData(channelsList),
|
||||
confirmButtonState: "default",
|
||||
disabled: false,
|
||||
isSelected: () => undefined,
|
||||
onChange: () => undefined,
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true,
|
||||
title: "Channels",
|
||||
toggleAll: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Generics / ChannelsAvailabilityDialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelsAvailabilityDialog {...props} />)
|
||||
.add("with text", () => (
|
||||
<ChannelsAvailabilityDialog {...props} contentType="order" />
|
||||
))
|
||||
.add("disabled", () => (
|
||||
<ChannelsAvailabilityDialog {...props} disabled={true} />
|
||||
));
|
|
@ -0,0 +1,56 @@
|
|||
import { Channel } from "@saleor/channels/utils";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ChannelsAvailabilityContent } from "@saleor/components/ChannelsAvailabilityContent";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import React from "react";
|
||||
|
||||
export interface ChannelsAvailabilityDialogProps {
|
||||
isSelected: (option: Channel) => boolean;
|
||||
channels: Channel[];
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
contentType?: string;
|
||||
disabled: boolean;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onChange: (option: Channel) => void;
|
||||
onConfirm: () => void;
|
||||
selected?: number;
|
||||
title: string;
|
||||
toggleAll?: (items: Channel[], selected: number) => void;
|
||||
}
|
||||
|
||||
export const ChannelsAvailabilityDialog: React.FC<ChannelsAvailabilityDialogProps> = ({
|
||||
isSelected,
|
||||
channels,
|
||||
confirmButtonState,
|
||||
contentType = "",
|
||||
disabled,
|
||||
open,
|
||||
onClose,
|
||||
onChange,
|
||||
onConfirm,
|
||||
selected = 0,
|
||||
title,
|
||||
toggleAll
|
||||
}) => (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ChannelsAvailabilityContent
|
||||
channels={channels}
|
||||
disabled={disabled}
|
||||
contentType={contentType}
|
||||
isSelected={isSelected}
|
||||
selected={selected}
|
||||
toggleAll={toggleAll}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</ActionDialog>
|
||||
);
|
||||
ChannelsAvailabilityDialog.displayName = "ChannelsAvailabilityDialog";
|
||||
export default ChannelsAvailabilityDialog;
|
2
src/components/ChannelsAvailabilityDialog/index.ts
Normal file
2
src/components/ChannelsAvailabilityDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ChannelsAvailabilityDialog";
|
||||
export { default } from "./ChannelsAvailabilityDialog";
|
|
@ -0,0 +1,18 @@
|
|||
import { productChannels } from "@saleor/channels/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ChannelsAvailabilityDropdown, {
|
||||
ChannelsAvailabilityDropdownProps
|
||||
} from "./ChannelsAvailabilityDropdown";
|
||||
|
||||
const props: ChannelsAvailabilityDropdownProps = {
|
||||
allChannelsCount: 6,
|
||||
channels: productChannels,
|
||||
currentChannel: productChannels[0]
|
||||
};
|
||||
|
||||
storiesOf("Generics / ChannelsAvailabilityDropdown", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ChannelsAvailabilityDropdown {...props} />);
|
|
@ -0,0 +1,138 @@
|
|||
import Menu from "@material-ui/core/Menu";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { CollectionList_collections_edges_node_channelListings } from "@saleor/collections/types/CollectionList";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import { ProductList_products_edges_node_channelListings } from "@saleor/products/types/ProductList";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
type Channels =
|
||||
| ProductList_products_edges_node_channelListings
|
||||
| CollectionList_collections_edges_node_channelListings;
|
||||
|
||||
export interface ChannelsAvailabilityDropdownProps {
|
||||
allChannelsCount: number;
|
||||
channels: Channels[];
|
||||
currentChannel: Channels;
|
||||
}
|
||||
|
||||
const isActive = (channelData: Channels) => channelData?.isPublished;
|
||||
|
||||
export const ChannelsAvailabilityDropdown: React.FC<ChannelsAvailabilityDropdownProps> = ({
|
||||
allChannelsCount,
|
||||
channels,
|
||||
currentChannel
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
const localizeDate = useDateLocalize();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
const handleClick = event => setAnchorEl(event.currentTarget);
|
||||
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
return (
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<div
|
||||
aria-controls="availability-menu"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<StatusLabel
|
||||
label={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Available in {count}/{allCount}",
|
||||
description: "product status title"
|
||||
},
|
||||
{
|
||||
allCount: allChannelsCount,
|
||||
count: channels.length
|
||||
}
|
||||
)}
|
||||
status={isActive(currentChannel) ? "success" : "error"}
|
||||
/>
|
||||
</div>
|
||||
<Menu
|
||||
id="availability-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
elevation={3}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
getContentAnchorEl={null}
|
||||
anchorOrigin={{
|
||||
horizontal: "center",
|
||||
vertical: "bottom"
|
||||
}}
|
||||
transformOrigin={{
|
||||
horizontal: "center",
|
||||
vertical: "top"
|
||||
}}
|
||||
>
|
||||
<Typography className={classes.title}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Available in {count} out of {allCount, plural, one {# channel} other {# channels}}"
|
||||
description="product status"
|
||||
values={{
|
||||
allCount: allChannelsCount,
|
||||
count: channels.length
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
<Hr className={classes.hr} />
|
||||
{channels.map(channelData => {
|
||||
const notPublishedText = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Will become available on {date}",
|
||||
description: "product channel publication date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(channelData.publicationDate, "L")
|
||||
}
|
||||
);
|
||||
|
||||
const publishedText = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "published since {date}",
|
||||
description: "product channel publication date"
|
||||
},
|
||||
{
|
||||
date: localizeDate(channelData.publicationDate, "L")
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItem key={channelData.channel.id} className={classes.menuItem}>
|
||||
<StatusLabel
|
||||
label={channelData.channel.name}
|
||||
status={isActive(channelData) ? "success" : "error"}
|
||||
/>
|
||||
<div>
|
||||
<Typography variant="caption" className={classes.caption}>
|
||||
{channelData.isPublished && channelData.publicationDate
|
||||
? publishedText
|
||||
: channelData.publicationDate && !channelData.isPublished
|
||||
? notPublishedText
|
||||
: channelData.isPublished
|
||||
? ""
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "hidden",
|
||||
description: "product channel publication status"
|
||||
})}
|
||||
</Typography>
|
||||
</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ChannelsAvailabilityDropdown.displayName = "ChannelsAvailabilityDropdown";
|
||||
export default ChannelsAvailabilityDropdown;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue