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",
|
firstCategoryItem: "#downshift-0-item-0",
|
||||||
visibleRadioBtn: "[name='isPublished']",
|
visibleRadioBtn: "[name='isPublished']",
|
||||||
saveBtn: "[data-test='button-bar-confirm']",
|
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 { LEFT_MENU_SELECTORS } from "../elements/account/left-menu/left-menu-selectors";
|
||||||
import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors";
|
import { PRODUCTS_SELECTORS } from "../elements/catalog/product-selectors";
|
||||||
|
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
|
||||||
|
|
||||||
// <reference types="cypress" />
|
// <reference types="cypress" />
|
||||||
describe("Products", () => {
|
describe("Products", () => {
|
||||||
|
@ -13,6 +14,8 @@ describe("Products", () => {
|
||||||
.click()
|
.click()
|
||||||
.get(PRODUCTS_SELECTORS.products)
|
.get(PRODUCTS_SELECTORS.products)
|
||||||
.click()
|
.click()
|
||||||
|
.get(BUTTON_SELECTORS.submit)
|
||||||
|
.click()
|
||||||
.get(PRODUCTS_SELECTORS.createProductBtn)
|
.get(PRODUCTS_SELECTORS.createProductBtn)
|
||||||
.click()
|
.click()
|
||||||
.get(PRODUCTS_SELECTORS.productNameInput)
|
.get(PRODUCTS_SELECTORS.productNameInput)
|
||||||
|
@ -33,6 +36,9 @@ describe("Products", () => {
|
||||||
.get(PRODUCTS_SELECTORS.categoryItem)
|
.get(PRODUCTS_SELECTORS.categoryItem)
|
||||||
.first()
|
.first()
|
||||||
.click()
|
.click()
|
||||||
|
.get(PRODUCTS_SELECTORS.channelAvailabilityItem)
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
.get(PRODUCTS_SELECTORS.visibleRadioBtn)
|
.get(PRODUCTS_SELECTORS.visibleRadioBtn)
|
||||||
.first()
|
.first()
|
||||||
.click()
|
.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=",
|
"integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=",
|
||||||
"dev": true
|
"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": {
|
"cyclist": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/find-with-regex/-/find-with-regex-1.1.3.tgz",
|
||||||
"integrity": "sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw=="
|
"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": {
|
"flat-cache": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
||||||
|
@ -12566,8 +12580,7 @@
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
@ -12585,13 +12598,11 @@
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -12604,18 +12615,15 @@
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -12718,8 +12726,7 @@
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -12729,7 +12736,6 @@
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -12742,20 +12748,17 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
@ -12772,7 +12775,6 @@
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -12845,8 +12847,7 @@
|
||||||
},
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -12856,7 +12857,6 @@
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -12932,8 +12932,7 @@
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
@ -12963,7 +12962,6 @@
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -12981,7 +12979,6 @@
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -13020,13 +13017,11 @@
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17570,6 +17565,11 @@
|
||||||
"boolbase": "~1.0.0"
|
"boolbase": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nub": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nub/-/nub-0.0.0.tgz",
|
||||||
|
"integrity": "sha1-s2m9Mr3eZq9ZYFw7BSC8IZ3MwE8="
|
||||||
|
},
|
||||||
"num2fraction": {
|
"num2fraction": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"apollo-upload-client": "^9.1.0",
|
"apollo-upload-client": "^9.1.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"crc-32": "^1.2.0",
|
"crc-32": "^1.2.0",
|
||||||
|
"currency-codes": "^2.1.0",
|
||||||
"downshift": "^1.31.16",
|
"downshift": "^1.31.16",
|
||||||
"draft-js": "^0.10.5",
|
"draft-js": "^0.10.5",
|
||||||
"draftail": "^1.2.1",
|
"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 navigate = useNavigator();
|
||||||
const { updateListSettings, settings } = useListSettings(ListViews.APPS_LIST);
|
const { updateListSettings, settings } = useListSettings(ListViews.APPS_LIST);
|
||||||
const paginate = usePaginator();
|
const paginate = usePaginator();
|
||||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
const paginationState = createPaginationState(settings?.rowNumber, params);
|
||||||
const queryVariables = {
|
const queryVariables = {
|
||||||
sort: {
|
sort: {
|
||||||
direction: OrderDirection.DESC,
|
direction: OrderDirection.DESC,
|
||||||
|
|
|
@ -35,7 +35,6 @@ export interface AttributeListPageProps
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
currencySymbol,
|
|
||||||
filterOpts,
|
filterOpts,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
onAdd,
|
onAdd,
|
||||||
|
@ -73,7 +72,6 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
defaultMessage: "All Attributes",
|
defaultMessage: "All Attributes",
|
||||||
description: "tab name"
|
description: "tab name"
|
||||||
})}
|
})}
|
||||||
currencySymbol={currencySymbol}
|
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
filterStructure={structure}
|
filterStructure={structure}
|
||||||
initialSearch={initialSearch}
|
initialSearch={initialSearch}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
createPaginationState
|
createPaginationState
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||||
|
@ -52,7 +51,6 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const paginate = usePaginator();
|
const paginate = usePaginator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const shop = useShop();
|
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||||
params.ids
|
params.ids
|
||||||
);
|
);
|
||||||
|
@ -145,13 +143,11 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
||||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AttributeListPage
|
<AttributeListPage
|
||||||
attributes={maybe(() => data.attributes.edges.map(edge => edge.node))}
|
attributes={maybe(() => data.attributes.edges.map(edge => edge.node))}
|
||||||
currencySymbol={currencySymbol}
|
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||||
filterOpts={getFilterOpts(params)}
|
filterOpts={getFilterOpts(params)}
|
||||||
|
|
|
@ -3,11 +3,11 @@ import TableBody from "@material-ui/core/TableBody";
|
||||||
import TableCell from "@material-ui/core/TableCell";
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
import TableFooter from "@material-ui/core/TableFooter";
|
import TableFooter from "@material-ui/core/TableFooter";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
import Money from "@saleor/components/Money";
|
import Money from "@saleor/components/Money";
|
||||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import StatusLabel from "@saleor/components/StatusLabel";
|
|
||||||
import TableCellAvatar, {
|
import TableCellAvatar, {
|
||||||
AVATAR_MARGIN
|
AVATAR_MARGIN
|
||||||
} from "@saleor/components/TableCellAvatar";
|
} from "@saleor/components/TableCellAvatar";
|
||||||
|
@ -16,12 +16,9 @@ import TablePagination from "@saleor/components/TablePagination";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
import { ListActions, ListProps } from "@saleor/types";
|
import { ListActions, ListProps } from "@saleor/types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import {
|
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||||
CategoryDetails_category_products_edges_node,
|
|
||||||
CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted
|
|
||||||
} from "../../types/CategoryDetails";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
|
@ -74,11 +71,14 @@ const useStyles = makeStyles(
|
||||||
);
|
);
|
||||||
|
|
||||||
interface CategoryProductListProps extends ListProps, ListActions {
|
interface CategoryProductListProps extends ListProps, ListActions {
|
||||||
|
channelsCount: number;
|
||||||
|
selectedChannel: string;
|
||||||
products: CategoryDetails_category_products_edges_node[];
|
products: CategoryDetails_category_products_edges_node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CategoryProductList: React.FC<CategoryProductListProps> = props => {
|
export const CategoryProductList: React.FC<CategoryProductListProps> = props => {
|
||||||
const {
|
const {
|
||||||
|
channelsCount,
|
||||||
disabled,
|
disabled,
|
||||||
isChecked,
|
isChecked,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
|
@ -89,59 +89,14 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
||||||
toolbar,
|
toolbar,
|
||||||
onNextPage,
|
onNextPage,
|
||||||
onPreviousPage,
|
onPreviousPage,
|
||||||
onRowClick
|
onRowClick,
|
||||||
|
selectedChannel
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const numberOfColumns = 5;
|
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 (
|
return (
|
||||||
<div className={classes.tableContainer}>
|
<div className={classes.tableContainer}>
|
||||||
<ResponsiveTable className={classes.table}>
|
<ResponsiveTable className={classes.table}>
|
||||||
|
@ -173,8 +128,8 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Published"
|
defaultMessage="Availability"
|
||||||
description="product status"
|
description="availability status"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPrice}>
|
<TableCell className={classes.colPrice}>
|
||||||
|
@ -202,6 +157,9 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
||||||
products,
|
products,
|
||||||
product => {
|
product => {
|
||||||
const isSelected = product ? isChecked(product.id) : false;
|
const isSelected = product ? isChecked(product.id) : false;
|
||||||
|
const channel = product?.channelListings.find(
|
||||||
|
listing => listing.channel.id === selectedChannel
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -233,30 +191,21 @@ export const CategoryProductList: React.FC<CategoryProductListProps> = props =>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
{product &&
|
{product && !product?.channelListings?.length ? (
|
||||||
maybe(() => product.isAvailable !== undefined) ? (
|
"-"
|
||||||
<StatusLabel
|
) : product?.channelListings !== undefined ? (
|
||||||
label={
|
<ChannelsAvailabilityDropdown
|
||||||
product.isAvailable
|
allChannelsCount={channelsCount}
|
||||||
? intl.formatMessage({
|
currentChannel={channel || product?.channelListings[0]}
|
||||||
defaultMessage: "Published",
|
channels={product?.channelListings}
|
||||||
description: "product",
|
|
||||||
id: "productStatusLabel"
|
|
||||||
})
|
|
||||||
: intl.formatMessage({
|
|
||||||
defaultMessage: "Not published",
|
|
||||||
description: "product"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
status={product.isAvailable ? "success" : "error"}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPrice}>
|
<TableCell className={classes.colPrice}>
|
||||||
{product?.pricing?.priceRangeUndiscounted ? (
|
{product?.channelListings ? (
|
||||||
getProductPrice(product?.pricing?.priceRangeUndiscounted)
|
<Money money={channel?.discountedPrice} />
|
||||||
) : (
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import Card from "@material-ui/core/Card";
|
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 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 React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -10,10 +15,23 @@ import CategoryProductList from "../CategoryProductList";
|
||||||
|
|
||||||
interface CategoryProductsProps extends PageListProps, ListActions {
|
interface CategoryProductsProps extends PageListProps, ListActions {
|
||||||
products: CategoryDetails_category_products_edges_node[];
|
products: CategoryDetails_category_products_edges_node[];
|
||||||
|
channelChoices: SingleAutocompleteChoiceType[];
|
||||||
|
channelsCount: number;
|
||||||
categoryName: string;
|
categoryName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
channelsSelectContainer: {
|
||||||
|
paddingTop: theme.spacing(2)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "CategoryProducts" }
|
||||||
|
);
|
||||||
|
|
||||||
export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
||||||
|
channelChoices,
|
||||||
|
channelsCount,
|
||||||
products,
|
products,
|
||||||
disabled,
|
disabled,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
|
@ -29,6 +47,11 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
||||||
toolbar
|
toolbar
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
const [channelChoice, setChannelChoice] = useStateFromProps(
|
||||||
|
channelChoices?.length ? channelChoices[0]?.value : ""
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -49,7 +72,16 @@ export const CategoryProducts: React.FC<CategoryProductsProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<CardContent className={classes.channelsSelectContainer}>
|
||||||
|
<ChannelsSelect
|
||||||
|
channelChoice={channelChoice}
|
||||||
|
channelChoices={channelChoices}
|
||||||
|
setChannelChoice={setChannelChoice}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
<CategoryProductList
|
<CategoryProductList
|
||||||
|
channelsCount={channelsCount}
|
||||||
|
selectedChannel={channelChoice}
|
||||||
products={products}
|
products={products}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
@ -48,6 +49,8 @@ export interface CategoryUpdatePageProps
|
||||||
hasPreviousPage: boolean;
|
hasPreviousPage: boolean;
|
||||||
};
|
};
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
|
channelChoices: SingleAutocompleteChoiceType[];
|
||||||
|
channelsCount: number;
|
||||||
onImageDelete: () => void;
|
onImageDelete: () => void;
|
||||||
onSubmit: (data: CategoryUpdateData) => SubmitPromise;
|
onSubmit: (data: CategoryUpdateData) => SubmitPromise;
|
||||||
onImageUpload(file: File);
|
onImageUpload(file: File);
|
||||||
|
@ -66,6 +69,8 @@ const ProductsTab = Tab(CategoryPageTab.products);
|
||||||
|
|
||||||
export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
changeTab,
|
changeTab,
|
||||||
|
channelChoices,
|
||||||
|
channelsCount,
|
||||||
currentTab,
|
currentTab,
|
||||||
category,
|
category,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -198,6 +203,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
)}
|
)}
|
||||||
{currentTab === CategoryPageTab.products && (
|
{currentTab === CategoryPageTab.products && (
|
||||||
<CategoryProducts
|
<CategoryProducts
|
||||||
|
channelsCount={channelsCount}
|
||||||
|
channelChoices={channelChoices}
|
||||||
categoryName={category?.name}
|
categoryName={category?.name}
|
||||||
products={products}
|
products={products}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -128,31 +128,50 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "123",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
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==",
|
id: "UHJvZHVjdDoyMQ==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Gardner-Schultz",
|
name: "Gardner-Schultz",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -166,31 +185,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "523",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyMg==",
|
id: "UHJvZHVjdDoyMg==",
|
||||||
isAvailable: true,
|
|
||||||
name: "James, Martinez and Murray",
|
name: "James, Martinez and Murray",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -204,31 +222,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjI=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjI=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1234",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyMw==",
|
id: "UHJvZHVjdDoyMw==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Curtis, Joyce and Turner",
|
name: "Curtis, Joyce and Turner",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -242,31 +259,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjM=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjM=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1235",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyNA==",
|
id: "UHJvZHVjdDoyNA==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Davis, Brown and Ray",
|
name: "Davis, Brown and Ray",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -280,31 +296,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjQ=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjQ=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1236",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyNQ==",
|
id: "UHJvZHVjdDoyNQ==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Gallegos Ltd",
|
name: "Gallegos Ltd",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -318,31 +333,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjU=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjU=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1237",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyNg==",
|
id: "UHJvZHVjdDoyNg==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Franklin Inc",
|
name: "Franklin Inc",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -356,31 +370,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjY=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjY=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1238",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyNw==",
|
id: "UHJvZHVjdDoyNw==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Williams-Taylor",
|
name: "Williams-Taylor",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -394,31 +407,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjc=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjc=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "1239",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyOA==",
|
id: "UHJvZHVjdDoyOA==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Riddle, Evans and Hicks",
|
name: "Riddle, Evans and Hicks",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -432,31 +444,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjg=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjg=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "223",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDoyOQ==",
|
id: "UHJvZHVjdDoyOQ==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Hebert-Sherman",
|
name: "Hebert-Sherman",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
@ -470,31 +481,30 @@ export const category: (
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjk=",
|
cursor: "YXJyYXljb25uZWN0aW9uOjk=",
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "ProductChannelListing",
|
||||||
|
availableForPurchase: null,
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
currencyCode: "USD",
|
||||||
|
id: "323",
|
||||||
|
name: "Channel1"
|
||||||
|
},
|
||||||
|
currency: "USD",
|
||||||
|
discountedPrice: {
|
||||||
|
__typename: "Money",
|
||||||
|
amount: 1,
|
||||||
|
currency: "USD"
|
||||||
|
},
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null,
|
||||||
|
visibleInListings: false
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "UHJvZHVjdDozMA==",
|
id: "UHJvZHVjdDozMA==",
|
||||||
isAvailable: true,
|
|
||||||
name: "Carter and Sons",
|
name: "Carter and Sons",
|
||||||
pricing: {
|
|
||||||
__typename: "ProductPricingInfo",
|
|
||||||
priceRangeUndiscounted: {
|
|
||||||
__typename: "TaxedMoneyRange",
|
|
||||||
start: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 3,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop: {
|
|
||||||
__typename: "TaxedMoney",
|
|
||||||
gross: {
|
|
||||||
__typename: "Money",
|
|
||||||
amount: 8,
|
|
||||||
currency: "USD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
id: "UHJvZHVjdFR5cGU6Mw==",
|
id: "UHJvZHVjdFR5cGU6Mw==",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
categoryFragment
|
categoryFragment
|
||||||
} from "@saleor/fragments/categories";
|
} from "@saleor/fragments/categories";
|
||||||
import { pageInfoFragment } from "@saleor/fragments/pageInfo";
|
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 makeQuery from "@saleor/hooks/makeQuery";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export const useRootCategoriesQuery = makeQuery<RootCategories, {}>(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const categoryDetails = gql`
|
export const categoryDetails = gql`
|
||||||
${fragmentMoney}
|
${channelListingProductFragment}
|
||||||
${categoryFragment}
|
${categoryFragment}
|
||||||
${categoryDetailsFragment}
|
${categoryDetailsFragment}
|
||||||
${pageInfoFragment}
|
${pageInfoFragment}
|
||||||
|
@ -81,7 +81,6 @@ export const categoryDetails = gql`
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
isAvailable
|
|
||||||
thumbnail {
|
thumbnail {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
@ -89,19 +88,8 @@ export const categoryDetails = gql`
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
pricing {
|
channelListings {
|
||||||
priceRangeUndiscounted {
|
...ChannelListingProductFragment
|
||||||
start {
|
|
||||||
gross {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stop {
|
|
||||||
gross {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,47 +85,37 @@ export interface CategoryDetails_category_products_edges_node_productType {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start_gross {
|
export interface CategoryDetails_category_products_edges_node_channelListings_discountedPrice {
|
||||||
__typename: "Money";
|
__typename: "Money";
|
||||||
amount: number;
|
amount: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start {
|
export interface CategoryDetails_category_products_edges_node_channelListings_channel {
|
||||||
__typename: "TaxedMoney";
|
__typename: "Channel";
|
||||||
gross: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_start_gross;
|
id: string;
|
||||||
|
name: string;
|
||||||
|
currencyCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop_gross {
|
export interface CategoryDetails_category_products_edges_node_channelListings {
|
||||||
__typename: "Money";
|
__typename: "ProductChannelListing";
|
||||||
amount: number;
|
isPublished: boolean;
|
||||||
currency: string;
|
publicationDate: any | null;
|
||||||
}
|
discountedPrice: CategoryDetails_category_products_edges_node_channelListings_discountedPrice | null;
|
||||||
|
isAvailableForPurchase: boolean | null;
|
||||||
export interface CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop {
|
availableForPurchase: any | null;
|
||||||
__typename: "TaxedMoney";
|
visibleInListings: boolean;
|
||||||
gross: CategoryDetails_category_products_edges_node_pricing_priceRangeUndiscounted_stop_gross;
|
channel: CategoryDetails_category_products_edges_node_channelListings_channel;
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
export interface CategoryDetails_category_products_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
isAvailable: boolean | null;
|
|
||||||
thumbnail: CategoryDetails_category_products_edges_node_thumbnail | null;
|
thumbnail: CategoryDetails_category_products_edges_node_thumbnail | null;
|
||||||
productType: CategoryDetails_category_products_edges_node_productType;
|
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 {
|
export interface CategoryDetails_category_products_edges {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import { useChannelsList } from "@saleor/channels/queries";
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
|
@ -78,6 +79,13 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
variables: { ...paginationState, id }
|
variables: { ...paginationState, id }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: channelsData } = useChannelsList({});
|
||||||
|
|
||||||
|
const channelChoices = channelsData?.channels?.map(channel => ({
|
||||||
|
label: channel.name,
|
||||||
|
value: channel.id
|
||||||
|
}));
|
||||||
|
|
||||||
const category = data?.category;
|
const category = data?.category;
|
||||||
|
|
||||||
if (category === null) {
|
if (category === null) {
|
||||||
|
@ -205,6 +213,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
<>
|
<>
|
||||||
<WindowTitle title={maybe(() => data.category.name)} />
|
<WindowTitle title={maybe(() => data.category.name)} />
|
||||||
<CategoryUpdatePage
|
<CategoryUpdatePage
|
||||||
|
channelsCount={channelsData?.channels?.length}
|
||||||
|
channelChoices={channelChoices}
|
||||||
changeTab={changeTab}
|
changeTab={changeTab}
|
||||||
currentTab={params.activeTab}
|
currentTab={params.activeTab}
|
||||||
category={maybe(() => data.category)}
|
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 AppHeader from "@saleor/components/AppHeader";
|
||||||
|
import { AvailabilityCard } from "@saleor/components/AvailabilityCard";
|
||||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import { Container } from "@saleor/components/Container";
|
import { Container } from "@saleor/components/Container";
|
||||||
|
@ -7,9 +9,8 @@ import Metadata from "@saleor/components/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -20,25 +21,38 @@ import { CollectionImage } from "../CollectionImage/CollectionImage";
|
||||||
import CollectionCreateForm, { CollectionCreateData } from "./form";
|
import CollectionCreateForm, { CollectionCreateData } from "./form";
|
||||||
|
|
||||||
export interface CollectionCreatePageProps {
|
export interface CollectionCreatePageProps {
|
||||||
|
channelsCount: number;
|
||||||
|
channelsErrors: CollectionChannelListingErrorFragment[];
|
||||||
|
currentChannels: ChannelCollectionData[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: ProductErrorFragment[];
|
errors: CollectionErrorFragment[];
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
||||||
|
onChannelsChange: (data: ChannelCollectionData[]) => void;
|
||||||
|
openChannelsModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
|
channelsCount,
|
||||||
|
channelsErrors,
|
||||||
|
currentChannels = [],
|
||||||
disabled,
|
disabled,
|
||||||
errors,
|
errors,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
onBack,
|
onBack,
|
||||||
|
onChannelsChange,
|
||||||
|
openChannelsModal,
|
||||||
onSubmit
|
onSubmit
|
||||||
}: CollectionCreatePageProps) => {
|
}: CollectionCreatePageProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollectionCreateForm onSubmit={onSubmit}>
|
<CollectionCreateForm
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
currentChannels={currentChannels}
|
||||||
|
setChannels={onChannelsChange}
|
||||||
|
>
|
||||||
{({ change, data, handlers, hasChanged, submit }) => (
|
{({ change, data, handlers, hasChanged, submit }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<AppHeader onBack={onBack}>
|
<AppHeader onBack={onBack}>
|
||||||
|
@ -115,30 +129,25 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<VisibilityCard
|
<AvailabilityCard
|
||||||
data={data}
|
|
||||||
errors={errors}
|
|
||||||
disabled={disabled}
|
|
||||||
messages={{
|
messages={{
|
||||||
hiddenLabel: intl.formatMessage({
|
hiddenLabel: intl.formatMessage({
|
||||||
defaultMessage: "Hidden",
|
defaultMessage: "Hidden",
|
||||||
description: "collection label"
|
description: "collection label"
|
||||||
}),
|
}),
|
||||||
hiddenSecondLabel: intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "will be visible from {date}",
|
|
||||||
description: "collection"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(data.publicationDate, "L")
|
|
||||||
}
|
|
||||||
),
|
|
||||||
visibleLabel: intl.formatMessage({
|
visibleLabel: intl.formatMessage({
|
||||||
defaultMessage: "Visible",
|
defaultMessage: "Visible",
|
||||||
description: "collection label"
|
description: "collection label"
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onChange={change}
|
errors={channelsErrors}
|
||||||
|
selectedChannelsCount={data.channelListings.length}
|
||||||
|
allChannelsCount={channelsCount}
|
||||||
|
channels={data.channelListings}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handlers.changeChannels}
|
||||||
|
openModal={openChannelsModal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
|
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||||
|
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
@ -13,10 +15,9 @@ export interface CollectionCreateFormData extends MetadataFormData {
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
|
channelListings: ChannelCollectionData[];
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
publicationDate: string;
|
|
||||||
isPublished: boolean;
|
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +28,10 @@ export interface CollectionCreateData extends CollectionCreateFormData {
|
||||||
interface CollectionCreateHandlers {
|
interface CollectionCreateHandlers {
|
||||||
changeMetadata: FormChange;
|
changeMetadata: FormChange;
|
||||||
changeDescription: RichTextEditorChange;
|
changeDescription: RichTextEditorChange;
|
||||||
|
changeChannels: (
|
||||||
|
id: string,
|
||||||
|
data: Omit<ChannelCollectionData, "name" | "id">
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
export interface UseCollectionCreateFormResult {
|
export interface UseCollectionCreateFormResult {
|
||||||
change: FormChange;
|
change: FormChange;
|
||||||
|
@ -37,11 +42,15 @@ export interface UseCollectionCreateFormResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreateFormProps {
|
export interface CollectionCreateFormProps {
|
||||||
|
currentChannels: ChannelCollectionData[];
|
||||||
|
setChannels: (data: ChannelCollectionData[]) => void;
|
||||||
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
|
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
|
||||||
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCollectionCreateForm(
|
function useCollectionCreateForm(
|
||||||
|
currentChannels: ChannelCollectionData[],
|
||||||
|
setChannels: (data: ChannelCollectionData[]) => void,
|
||||||
onSubmit: (data: CollectionCreateData) => SubmitPromise
|
onSubmit: (data: CollectionCreateData) => SubmitPromise
|
||||||
): UseCollectionCreateFormResult {
|
): UseCollectionCreateFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
const [changed, setChanged] = React.useState(false);
|
||||||
|
@ -53,11 +62,10 @@ function useCollectionCreateForm(
|
||||||
value: null
|
value: null
|
||||||
},
|
},
|
||||||
backgroundImageAlt: "",
|
backgroundImageAlt: "",
|
||||||
isPublished: false,
|
channelListings: currentChannels,
|
||||||
metadata: [],
|
metadata: [],
|
||||||
name: "",
|
name: "",
|
||||||
privateMetadata: [],
|
privateMetadata: [],
|
||||||
publicationDate: "",
|
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: "",
|
seoTitle: "",
|
||||||
slug: ""
|
slug: ""
|
||||||
|
@ -83,12 +91,19 @@ function useCollectionCreateForm(
|
||||||
description: description.current
|
description: description.current
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleChannelChange = createChannelsChangeHandler(
|
||||||
|
currentChannels,
|
||||||
|
setChannels,
|
||||||
|
triggerChange
|
||||||
|
);
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
data: getData(),
|
data: getData(),
|
||||||
handlers: {
|
handlers: {
|
||||||
|
changeChannels: handleChannelChange,
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
|
@ -98,10 +113,12 @@ function useCollectionCreateForm(
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
|
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
|
||||||
|
currentChannels,
|
||||||
|
setChannels,
|
||||||
children,
|
children,
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const props = useCollectionCreateForm(onSubmit);
|
const props = useCollectionCreateForm(currentChannels, setChannels, onSubmit);
|
||||||
|
|
||||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import RichTextEditor, {
|
import RichTextEditor, {
|
||||||
RichTextEditorChange
|
RichTextEditorChange
|
||||||
} from "@saleor/components/RichTextEditor";
|
} from "@saleor/components/RichTextEditor";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -19,7 +19,7 @@ export interface CollectionDetailsProps {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: ProductErrorFragment[];
|
errors: CollectionErrorFragment[];
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
onDescriptionChange: RichTextEditorChange;
|
onDescriptionChange: RichTextEditorChange;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
|
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
|
import { AvailabilityCard } from "@saleor/components/AvailabilityCard";
|
||||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import { Container } from "@saleor/components/Container";
|
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 Grid from "@saleor/components/Grid";
|
||||||
import Hr from "@saleor/components/Hr";
|
|
||||||
import Metadata from "@saleor/components/Metadata/Metadata";
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { CollectionErrorFragment } from "@saleor/fragments/types/CollectionErrorFragment";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe } from "../../../misc";
|
|
||||||
import { ListActions, PageListProps } from "../../../types";
|
import { ListActions, PageListProps } from "../../../types";
|
||||||
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
||||||
import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
||||||
|
@ -27,38 +24,50 @@ import CollectionProducts from "../CollectionProducts/CollectionProducts";
|
||||||
import CollectionUpdateForm, { CollectionUpdateData } from "./form";
|
import CollectionUpdateForm, { CollectionUpdateData } from "./form";
|
||||||
|
|
||||||
export interface CollectionDetailsPageProps extends PageListProps, ListActions {
|
export interface CollectionDetailsPageProps extends PageListProps, ListActions {
|
||||||
|
channelsCount: number;
|
||||||
|
channelsErrors: CollectionChannelListingErrorFragment[];
|
||||||
collection: CollectionDetails_collection;
|
collection: CollectionDetails_collection;
|
||||||
errors: ProductErrorFragment[];
|
currentChannels: ChannelCollectionData[];
|
||||||
isFeatured: boolean;
|
errors: CollectionErrorFragment[];
|
||||||
|
hasChannelChanged: boolean;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
|
selectedChannel: string;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onCollectionRemove: () => void;
|
onCollectionRemove: () => void;
|
||||||
onImageDelete: () => void;
|
onImageDelete: () => void;
|
||||||
onImageUpload: (file: File) => void;
|
onImageUpload: (file: File) => void;
|
||||||
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
||||||
onSubmit: (data: CollectionUpdateData) => SubmitPromise;
|
onSubmit: (data: CollectionUpdateData) => SubmitPromise;
|
||||||
|
onChannelsChange: (data: ChannelCollectionData[]) => void;
|
||||||
|
openChannelsModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
|
channelsCount,
|
||||||
|
channelsErrors,
|
||||||
collection,
|
collection,
|
||||||
|
currentChannels = [],
|
||||||
disabled,
|
disabled,
|
||||||
errors,
|
errors,
|
||||||
isFeatured,
|
hasChannelChanged,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
|
selectedChannel,
|
||||||
onBack,
|
onBack,
|
||||||
onCollectionRemove,
|
onCollectionRemove,
|
||||||
onImageDelete,
|
onImageDelete,
|
||||||
onImageUpload,
|
onImageUpload,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
onChannelsChange,
|
||||||
|
openChannelsModal,
|
||||||
...collectionProductsProps
|
...collectionProductsProps
|
||||||
}: CollectionDetailsPageProps) => {
|
}: CollectionDetailsPageProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollectionUpdateForm
|
<CollectionUpdateForm
|
||||||
collection={collection}
|
collection={collection}
|
||||||
isFeatured={isFeatured}
|
currentChannels={currentChannels}
|
||||||
|
setChannels={onChannelsChange}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({ change, data, handlers, hasChanged, submit }) => (
|
{({ change, data, handlers, hasChanged, submit }) => (
|
||||||
|
@ -66,7 +75,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
<AppHeader onBack={onBack}>
|
<AppHeader onBack={onBack}>
|
||||||
{intl.formatMessage(sectionNames.collections)}
|
{intl.formatMessage(sectionNames.collections)}
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
<PageHeader title={maybe(() => collection.name)} />
|
<PageHeader title={collection?.name} />
|
||||||
<Grid>
|
<Grid>
|
||||||
<div>
|
<div>
|
||||||
<CollectionDetails
|
<CollectionDetails
|
||||||
|
@ -79,7 +88,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<CollectionImage
|
<CollectionImage
|
||||||
data={data}
|
data={data}
|
||||||
image={maybe(() => collection.backgroundImage)}
|
image={collection?.backgroundImage}
|
||||||
onImageDelete={onImageDelete}
|
onImageDelete={onImageDelete}
|
||||||
onImageUpload={onImageUpload}
|
onImageUpload={onImageUpload}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
@ -89,6 +98,8 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<CollectionProducts
|
<CollectionProducts
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
channelsCount={channelsCount}
|
||||||
|
selectedChannel={selectedChannel}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
{...collectionProductsProps}
|
{...collectionProductsProps}
|
||||||
/>
|
/>
|
||||||
|
@ -105,55 +116,38 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
slug={data.slug}
|
slug={data.slug}
|
||||||
slugPlaceholder={data.name}
|
slugPlaceholder={data.name}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={maybe(() => collection.name)}
|
titlePlaceholder={collection?.name}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<VisibilityCard
|
<AvailabilityCard
|
||||||
data={data}
|
|
||||||
errors={errors}
|
|
||||||
messages={{
|
messages={{
|
||||||
hiddenLabel: intl.formatMessage({
|
hiddenLabel: intl.formatMessage({
|
||||||
defaultMessage: "Hidden",
|
defaultMessage: "Hidden",
|
||||||
description: "collection label"
|
description: "collection label"
|
||||||
}),
|
}),
|
||||||
hiddenSecondLabel: intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "will be visible from {date}",
|
|
||||||
description: "collection"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(data.publicationDate, "L")
|
|
||||||
}
|
|
||||||
),
|
|
||||||
visibleLabel: intl.formatMessage({
|
visibleLabel: intl.formatMessage({
|
||||||
defaultMessage: "Visible",
|
defaultMessage: "Visible",
|
||||||
description: "collection label"
|
description: "collection label"
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onChange={change}
|
errors={channelsErrors}
|
||||||
>
|
selectedChannelsCount={data.channelListings.length}
|
||||||
<FormSpacer />
|
allChannelsCount={channelsCount}
|
||||||
<Hr />
|
channels={data.channelListings}
|
||||||
<ControlledCheckbox
|
|
||||||
name={"isFeatured" as keyof CollectionUpdateData}
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Feature on Homepage",
|
|
||||||
description: "switch button"
|
|
||||||
})}
|
|
||||||
checked={data.isFeatured}
|
|
||||||
onChange={change}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
onChange={handlers.changeChannels}
|
||||||
|
openModal={openChannelsModal}
|
||||||
/>
|
/>
|
||||||
</VisibilityCard>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<SaveButtonBar
|
<SaveButtonBar
|
||||||
state={saveButtonBarState}
|
state={saveButtonBarState}
|
||||||
disabled={disabled || !hasChanged}
|
disabled={disabled || (!hasChanged && !hasChannelChanged)}
|
||||||
onCancel={onBack}
|
onCancel={onBack}
|
||||||
onDelete={onCollectionRemove}
|
onDelete={onCollectionRemove}
|
||||||
onSave={submit}
|
onSave={submit}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
|
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||||
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
||||||
|
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
||||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||||
|
@ -13,13 +14,11 @@ import React from "react";
|
||||||
|
|
||||||
export interface CollectionUpdateFormData extends MetadataFormData {
|
export interface CollectionUpdateFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
|
channelListings: ChannelCollectionData[];
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
publicationDate: string;
|
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
isFeatured: boolean;
|
|
||||||
isPublished: boolean;
|
|
||||||
}
|
}
|
||||||
export interface CollectionUpdateData extends CollectionUpdateFormData {
|
export interface CollectionUpdateData extends CollectionUpdateFormData {
|
||||||
description: OutputData;
|
description: OutputData;
|
||||||
|
@ -28,6 +27,10 @@ export interface CollectionUpdateData extends CollectionUpdateFormData {
|
||||||
interface CollectionUpdateHandlers {
|
interface CollectionUpdateHandlers {
|
||||||
changeMetadata: FormChange;
|
changeMetadata: FormChange;
|
||||||
changeDescription: RichTextEditorChange;
|
changeDescription: RichTextEditorChange;
|
||||||
|
changeChannels: (
|
||||||
|
id: string,
|
||||||
|
data: Omit<ChannelCollectionData, "name" | "id">
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
export interface UseCollectionUpdateFormResult {
|
export interface UseCollectionUpdateFormResult {
|
||||||
change: FormChange;
|
change: FormChange;
|
||||||
|
@ -40,26 +43,26 @@ export interface UseCollectionUpdateFormResult {
|
||||||
export interface CollectionUpdateFormProps {
|
export interface CollectionUpdateFormProps {
|
||||||
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
|
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
|
||||||
collection: CollectionDetails_collection;
|
collection: CollectionDetails_collection;
|
||||||
isFeatured: boolean;
|
currentChannels: ChannelCollectionData[];
|
||||||
|
setChannels: (data: ChannelCollectionData[]) => void;
|
||||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
|
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCollectionUpdateForm(
|
function useCollectionUpdateForm(
|
||||||
collection: CollectionDetails_collection,
|
collection: CollectionDetails_collection,
|
||||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>,
|
currentChannels: ChannelCollectionData[],
|
||||||
isFeatured: boolean
|
setChannels: (data: ChannelCollectionData[]) => void,
|
||||||
|
onSubmit: (data: CollectionUpdateData) => Promise<any[]>
|
||||||
): UseCollectionUpdateFormResult {
|
): UseCollectionUpdateFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
const [changed, setChanged] = React.useState(false);
|
||||||
const triggerChange = () => setChanged(true);
|
const triggerChange = () => setChanged(true);
|
||||||
|
|
||||||
const form = useForm<CollectionUpdateFormData>({
|
const form = useForm<CollectionUpdateFormData>({
|
||||||
backgroundImageAlt: collection?.backgroundImage?.alt || "",
|
backgroundImageAlt: collection?.backgroundImage?.alt || "",
|
||||||
isFeatured,
|
channelListings: currentChannels,
|
||||||
isPublished: !!collection?.isPublished,
|
|
||||||
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: collection?.name || "",
|
name: collection?.name || "",
|
||||||
privateMetadata: collection?.privateMetadata?.map(mapMetadataItemToInput),
|
privateMetadata: collection?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
publicationDate: collection?.publicationDate || "",
|
|
||||||
seoDescription: collection?.seoDescription || "",
|
seoDescription: collection?.seoDescription || "",
|
||||||
seoTitle: collection?.seoTitle || "",
|
seoTitle: collection?.seoTitle || "",
|
||||||
slug: collection?.slug || ""
|
slug: collection?.slug || ""
|
||||||
|
@ -89,16 +92,22 @@ function useCollectionUpdateForm(
|
||||||
|
|
||||||
const getSubmitData = (): CollectionUpdateData => ({
|
const getSubmitData = (): CollectionUpdateData => ({
|
||||||
...getData(),
|
...getData(),
|
||||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
|
||||||
...getPublicationData(form.data)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleChannelChange = createChannelsChangeHandler(
|
||||||
|
currentChannels,
|
||||||
|
setChannels,
|
||||||
|
triggerChange
|
||||||
|
);
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
data: getData(),
|
data: getData(),
|
||||||
handlers: {
|
handlers: {
|
||||||
|
changeChannels: handleChannelChange,
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
|
@ -108,12 +117,18 @@ function useCollectionUpdateForm(
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionUpdateForm: React.FC<CollectionUpdateFormProps> = ({
|
const CollectionUpdateForm: React.FC<CollectionUpdateFormProps> = ({
|
||||||
children,
|
|
||||||
collection,
|
collection,
|
||||||
isFeatured,
|
currentChannels,
|
||||||
|
setChannels,
|
||||||
|
children,
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const props = useCollectionUpdateForm(collection, onSubmit, isFeatured);
|
const props = useCollectionUpdateForm(
|
||||||
|
collection,
|
||||||
|
currentChannels,
|
||||||
|
setChannels,
|
||||||
|
onSubmit
|
||||||
|
);
|
||||||
|
|
||||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
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 TableFooter from "@material-ui/core/TableFooter";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||||
|
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import StatusLabel from "@saleor/components/StatusLabel";
|
|
||||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
|
@ -15,7 +15,7 @@ import { maybe, renderCollection } from "@saleor/misc";
|
||||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||||
import { getArrowDirection } from "@saleor/utils/sort";
|
import { getArrowDirection } from "@saleor/utils/sort";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||||
|
|
||||||
|
@ -49,12 +49,15 @@ interface CollectionListProps
|
||||||
ListActions,
|
ListActions,
|
||||||
SortPage<CollectionListUrlSortField> {
|
SortPage<CollectionListUrlSortField> {
|
||||||
collections: CollectionList_collections_edges_node[];
|
collections: CollectionList_collections_edges_node[];
|
||||||
|
channelsCount: number;
|
||||||
|
selectedChannel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numberOfColumns = 4;
|
const numberOfColumns = 4;
|
||||||
|
|
||||||
const CollectionList: React.FC<CollectionListProps> = props => {
|
const CollectionList: React.FC<CollectionListProps> = props => {
|
||||||
const {
|
const {
|
||||||
|
channelsCount,
|
||||||
collections,
|
collections,
|
||||||
disabled,
|
disabled,
|
||||||
settings,
|
settings,
|
||||||
|
@ -67,13 +70,13 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
||||||
pageInfo,
|
pageInfo,
|
||||||
isChecked,
|
isChecked,
|
||||||
selected,
|
selected,
|
||||||
|
selectedChannel,
|
||||||
toggle,
|
toggle,
|
||||||
toggleAll,
|
toggleAll,
|
||||||
toolbar
|
toolbar
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable>
|
<ResponsiveTable>
|
||||||
|
@ -143,6 +146,9 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
||||||
collections,
|
collections,
|
||||||
collection => {
|
collection => {
|
||||||
const isSelected = collection ? isChecked(collection.id) : false;
|
const isSelected = collection ? isChecked(collection.id) : false;
|
||||||
|
const channel = collection?.channelListings.find(
|
||||||
|
listing => listing.channel.id === selectedChannel
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={classes.tableRow}
|
className={classes.tableRow}
|
||||||
|
@ -172,26 +178,18 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
className={classes.colAvailability}
|
className={classes.colAvailability}
|
||||||
data-test="published"
|
data-test="availability"
|
||||||
data-test-published={maybe(() => collection.isPublished)}
|
data-test-availability={!!collection?.channelListings?.length}
|
||||||
>
|
>
|
||||||
{maybe(
|
{collection && !collection?.channelListings?.length ? (
|
||||||
() => (
|
"-"
|
||||||
<StatusLabel
|
) : collection?.channelListings !== undefined ? (
|
||||||
status={collection.isPublished ? "success" : "error"}
|
<ChannelsAvailabilityDropdown
|
||||||
label={
|
allChannelsCount={channelsCount}
|
||||||
collection.isPublished
|
currentChannel={channel}
|
||||||
? intl.formatMessage({
|
channels={collection?.channelListings}
|
||||||
defaultMessage: "Published",
|
|
||||||
description: "collection is published"
|
|
||||||
})
|
|
||||||
: intl.formatMessage({
|
|
||||||
defaultMessage: "Not published",
|
|
||||||
description: "collection is not published"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
),
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import Card from "@material-ui/core/Card";
|
import Card from "@material-ui/core/Card";
|
||||||
|
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||||
|
import CardMenu from "@saleor/components/CardMenu";
|
||||||
import { Container } from "@saleor/components/Container";
|
import { Container } from "@saleor/components/Container";
|
||||||
import FilterBar from "@saleor/components/FilterBar";
|
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
|
import SearchBar from "@saleor/components/SearchBar";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import {
|
import {
|
||||||
FilterPageProps,
|
|
||||||
ListActions,
|
ListActions,
|
||||||
PageListProps,
|
PageListProps,
|
||||||
|
SearchPageProps,
|
||||||
SortPage,
|
SortPage,
|
||||||
TabPageProps
|
TabPageProps
|
||||||
} from "@saleor/types";
|
} from "@saleor/types";
|
||||||
|
@ -17,44 +19,64 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||||
import CollectionList from "../CollectionList/CollectionList";
|
import CollectionList from "../CollectionList/CollectionList";
|
||||||
import {
|
|
||||||
CollectionFilterKeys,
|
|
||||||
CollectionListFilterOpts,
|
|
||||||
createFilterStructure
|
|
||||||
} from "./filters";
|
|
||||||
|
|
||||||
export interface CollectionListPageProps
|
export interface CollectionListPageProps
|
||||||
extends PageListProps,
|
extends PageListProps,
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<CollectionFilterKeys, CollectionListFilterOpts>,
|
SearchPageProps,
|
||||||
SortPage<CollectionListUrlSortField>,
|
SortPage<CollectionListUrlSortField>,
|
||||||
TabPageProps {
|
TabPageProps {
|
||||||
collections: CollectionList_collections_edges_node[];
|
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> = ({
|
const CollectionListPage: React.FC<CollectionListPageProps> = ({
|
||||||
currencySymbol,
|
channelsCount,
|
||||||
currentTab,
|
currentTab,
|
||||||
disabled,
|
disabled,
|
||||||
filterOpts,
|
|
||||||
initialSearch,
|
initialSearch,
|
||||||
onAdd,
|
onAdd,
|
||||||
onAll,
|
onAll,
|
||||||
onFilterChange,
|
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
|
onSettingsOpen,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onTabDelete,
|
onTabDelete,
|
||||||
onTabSave,
|
onTabSave,
|
||||||
|
selectedChannel,
|
||||||
tabs,
|
tabs,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const classes = useStyles({});
|
||||||
const structure = createFilterStructure(intl, filterOpts);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<PageHeader title={intl.formatMessage(sectionNames.collections)}>
|
<PageHeader title={intl.formatMessage(sectionNames.collections)}>
|
||||||
|
{!!onSettingsOpen && (
|
||||||
|
<CardMenu
|
||||||
|
className={classes.settings}
|
||||||
|
menuItems={[
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
defaultMessage: "Settings",
|
||||||
|
description: "button"
|
||||||
|
}),
|
||||||
|
onSelect: onSettingsOpen
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -68,27 +90,29 @@ const CollectionListPage: React.FC<CollectionListPageProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Card>
|
<Card>
|
||||||
<FilterBar
|
<SearchBar
|
||||||
allTabLabel={intl.formatMessage({
|
allTabLabel={intl.formatMessage({
|
||||||
defaultMessage: "All Collections",
|
defaultMessage: "All Collections",
|
||||||
description: "tab name"
|
description: "tab name"
|
||||||
})}
|
})}
|
||||||
currencySymbol={currencySymbol}
|
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
filterStructure={structure}
|
|
||||||
initialSearch={initialSearch}
|
initialSearch={initialSearch}
|
||||||
searchPlaceholder={intl.formatMessage({
|
searchPlaceholder={intl.formatMessage({
|
||||||
defaultMessage: "Search Collection"
|
defaultMessage: "Search Collection"
|
||||||
})}
|
})}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
onAll={onAll}
|
onAll={onAll}
|
||||||
onFilterChange={onFilterChange}
|
|
||||||
onSearchChange={onSearchChange}
|
onSearchChange={onSearchChange}
|
||||||
onTabChange={onTabChange}
|
onTabChange={onTabChange}
|
||||||
onTabDelete={onTabDelete}
|
onTabDelete={onTabDelete}
|
||||||
onTabSave={onTabSave}
|
onTabSave={onTabSave}
|
||||||
/>
|
/>
|
||||||
<CollectionList disabled={disabled} {...listProps} />
|
<CollectionList
|
||||||
|
disabled={disabled}
|
||||||
|
channelsCount={channelsCount}
|
||||||
|
selectedChannel={selectedChannel}
|
||||||
|
{...listProps}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,10 +8,10 @@ import TableFooter from "@material-ui/core/TableFooter";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
|
import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailabilityDropdown";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import StatusLabel from "@saleor/components/StatusLabel";
|
|
||||||
import TableCellAvatar, {
|
import TableCellAvatar, {
|
||||||
AVATAR_MARGIN
|
AVATAR_MARGIN
|
||||||
} from "@saleor/components/TableCellAvatar";
|
} from "@saleor/components/TableCellAvatar";
|
||||||
|
@ -57,6 +57,8 @@ const useStyles = makeStyles(
|
||||||
|
|
||||||
export interface CollectionProductsProps extends PageListProps, ListActions {
|
export interface CollectionProductsProps extends PageListProps, ListActions {
|
||||||
collection: CollectionDetails_collection;
|
collection: CollectionDetails_collection;
|
||||||
|
channelsCount: number;
|
||||||
|
selectedChannel: string;
|
||||||
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +66,7 @@ const numberOfColumns = 5;
|
||||||
|
|
||||||
const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
const {
|
const {
|
||||||
|
channelsCount,
|
||||||
collection,
|
collection,
|
||||||
disabled,
|
disabled,
|
||||||
onAdd,
|
onAdd,
|
||||||
|
@ -72,6 +75,7 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
onProductUnassign,
|
onProductUnassign,
|
||||||
onRowClick,
|
onRowClick,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
|
selectedChannel,
|
||||||
isChecked,
|
isChecked,
|
||||||
selected,
|
selected,
|
||||||
toggle,
|
toggle,
|
||||||
|
@ -139,8 +143,8 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Published"
|
defaultMessage="Availability"
|
||||||
description="product is published"
|
description="product availability"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colActions} />
|
<TableCell className={classes.colActions} />
|
||||||
|
@ -149,9 +153,9 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
colSpan={numberOfColumns}
|
colSpan={numberOfColumns}
|
||||||
hasNextPage={maybe(() => pageInfo.hasNextPage)}
|
hasNextPage={pageInfo?.hasNextPage}
|
||||||
onNextPage={onNextPage}
|
onNextPage={onNextPage}
|
||||||
hasPreviousPage={maybe(() => pageInfo.hasPreviousPage)}
|
hasPreviousPage={pageInfo?.hasPreviousPage}
|
||||||
onPreviousPage={onPreviousPage}
|
onPreviousPage={onPreviousPage}
|
||||||
/>
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
@ -161,6 +165,10 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
maybe(() => collection.products.edges.map(edge => edge.node)),
|
maybe(() => collection.products.edges.map(edge => edge.node)),
|
||||||
product => {
|
product => {
|
||||||
const isSelected = product ? isChecked(product.id) : false;
|
const isSelected = product ? isChecked(product.id) : false;
|
||||||
|
const channel =
|
||||||
|
product?.channelListings.find(
|
||||||
|
listing => listing.channel.id === selectedChannel
|
||||||
|
) || product?.channelListings[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -190,24 +198,16 @@ const CollectionProducts: React.FC<CollectionProductsProps> = props => {
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colType}>
|
||||||
{maybe(
|
{product && !product?.channelListings?.length ? (
|
||||||
() => (
|
"-"
|
||||||
<StatusLabel
|
) : product?.channelListings !== undefined ? (
|
||||||
label={
|
<ChannelsAvailabilityDropdown
|
||||||
product.isPublished
|
allChannelsCount={channelsCount}
|
||||||
? intl.formatMessage({
|
currentChannel={channel}
|
||||||
defaultMessage: "Published",
|
channels={product?.channelListings}
|
||||||
description: "product is published"
|
|
||||||
})
|
|
||||||
: intl.formatMessage({
|
|
||||||
defaultMessage: "Not published",
|
|
||||||
description: "product is not published"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
status={product.isPublished ? "success" : "error"}
|
|
||||||
/>
|
/>
|
||||||
),
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
|
@ -7,8 +7,19 @@ const content = richTextEditorFixtures.richTextEditor;
|
||||||
export const collections: CollectionList_collections_edges_node[] = [
|
export const collections: CollectionList_collections_edges_node[] = [
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "123",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjox",
|
id: "Q29sbGVjdGlvbjox",
|
||||||
isPublished: true,
|
|
||||||
name: "Summer collection",
|
name: "Summer collection",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -17,8 +28,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "124",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjoy",
|
id: "Q29sbGVjdGlvbjoy",
|
||||||
isPublished: true,
|
|
||||||
name: "Winter sale",
|
name: "Winter sale",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -27,8 +49,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "125",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjoz",
|
id: "Q29sbGVjdGlvbjoz",
|
||||||
isPublished: true,
|
|
||||||
name: "Vintage vibes",
|
name: "Vintage vibes",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -37,8 +70,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "126",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjoa",
|
id: "Q29sbGVjdGlvbjoa",
|
||||||
isPublished: true,
|
|
||||||
name: "Merry Christmas",
|
name: "Merry Christmas",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -47,8 +91,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "127",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjob",
|
id: "Q29sbGVjdGlvbjob",
|
||||||
isPublished: true,
|
|
||||||
name: "80s Miami",
|
name: "80s Miami",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -57,8 +112,19 @@ export const collections: CollectionList_collections_edges_node[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "128",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
id: "Q29sbGVjdGlvbjoc",
|
id: "Q29sbGVjdGlvbjoc",
|
||||||
isPublished: true,
|
|
||||||
name: "Yellow Submarine 2019",
|
name: "Yellow Submarine 2019",
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
|
@ -79,9 +145,20 @@ export const collection: (
|
||||||
alt: "Alt text",
|
alt: "Alt text",
|
||||||
url: placeholderCollectionImage
|
url: placeholderCollectionImage
|
||||||
},
|
},
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
__typename: "CollectionChannelListing",
|
||||||
|
channel: {
|
||||||
|
__typename: "Channel",
|
||||||
|
id: "223",
|
||||||
|
name: "Channel"
|
||||||
|
},
|
||||||
|
isPublished: false,
|
||||||
|
publicationDate: null
|
||||||
|
}
|
||||||
|
],
|
||||||
descriptionJson: JSON.stringify(content),
|
descriptionJson: JSON.stringify(content),
|
||||||
id: "Q29sbGVjdGlvbjox",
|
id: "Q29sbGVjdGlvbjox",
|
||||||
isPublished: true,
|
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
__typename: "MetadataItem",
|
__typename: "MetadataItem",
|
||||||
|
@ -96,11 +173,49 @@ export const collection: (
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
__typename: "ProductCountableEdge",
|
__typename: "ProductCountableEdge",
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjA=",
|
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
id: "UHJvZHVjdDoxNw==",
|
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,
|
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==",
|
||||||
name: "Murray Inc",
|
name: "Murray Inc",
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
|
@ -112,11 +227,49 @@ export const collection: (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "ProductCountableEdge",
|
__typename: "ProductCountableEdge",
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjE=",
|
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
id: "UHJvZHVjdDoyNw==",
|
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,
|
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==",
|
||||||
name: "Williams-Taylor",
|
name: "Williams-Taylor",
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
|
@ -128,11 +281,49 @@ export const collection: (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "ProductCountableEdge",
|
__typename: "ProductCountableEdge",
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjI=",
|
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
id: "UHJvZHVjdDoyOQ==",
|
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,
|
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==",
|
||||||
name: "Hebert-Sherman",
|
name: "Hebert-Sherman",
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
|
@ -144,11 +335,49 @@ export const collection: (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "ProductCountableEdge",
|
__typename: "ProductCountableEdge",
|
||||||
cursor: "YXJyYXljb25uZWN0aW9uOjM=",
|
|
||||||
node: {
|
node: {
|
||||||
__typename: "Product",
|
__typename: "Product",
|
||||||
id: "UHJvZHVjdDo1Mw==",
|
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,
|
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==",
|
||||||
name: "Estes, Johnson and Graham",
|
name: "Estes, Johnson and Graham",
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
|
@ -167,7 +396,6 @@ export const collection: (
|
||||||
startCursor: ""
|
startCursor: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
publicationDate: "2018-08-25T18:45:54.125Z",
|
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: "",
|
seoTitle: "",
|
||||||
slug: "summer-collection"
|
slug: "summer-collection"
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
import {
|
||||||
|
CollectionChannelListingUpdate,
|
||||||
|
CollectionChannelListingUpdateVariables
|
||||||
|
} from "@saleor/collections/types/CollectionChannelListingUpdate";
|
||||||
import {
|
import {
|
||||||
collectionDetailsFragment,
|
collectionDetailsFragment,
|
||||||
collectionProductFragment
|
collectionProductFragment
|
||||||
} from "@saleor/fragments/collections";
|
} from "@saleor/fragments/collections";
|
||||||
import {
|
import {
|
||||||
productErrorFragment,
|
collectionChannelListingErrorFragment,
|
||||||
shopErrorFragment
|
collectionsErrorFragment,
|
||||||
|
productErrorFragment
|
||||||
} from "@saleor/fragments/errors";
|
} from "@saleor/fragments/errors";
|
||||||
import makeMutation from "@saleor/hooks/makeMutation";
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
@ -17,18 +22,10 @@ import {
|
||||||
CollectionBulkDelete,
|
CollectionBulkDelete,
|
||||||
CollectionBulkDeleteVariables
|
CollectionBulkDeleteVariables
|
||||||
} from "./types/CollectionBulkDelete";
|
} from "./types/CollectionBulkDelete";
|
||||||
import {
|
|
||||||
CollectionBulkPublish,
|
|
||||||
CollectionBulkPublishVariables
|
|
||||||
} from "./types/CollectionBulkPublish";
|
|
||||||
import {
|
import {
|
||||||
CollectionUpdate,
|
CollectionUpdate,
|
||||||
CollectionUpdateVariables
|
CollectionUpdateVariables
|
||||||
} from "./types/CollectionUpdate";
|
} from "./types/CollectionUpdate";
|
||||||
import {
|
|
||||||
CollectionUpdateWithHomepage,
|
|
||||||
CollectionUpdateWithHomepageVariables
|
|
||||||
} from "./types/CollectionUpdateWithHomepage";
|
|
||||||
import {
|
import {
|
||||||
CreateCollection,
|
CreateCollection,
|
||||||
CreateCollectionVariables
|
CreateCollectionVariables
|
||||||
|
@ -44,14 +41,14 @@ import {
|
||||||
|
|
||||||
const collectionUpdate = gql`
|
const collectionUpdate = gql`
|
||||||
${collectionDetailsFragment}
|
${collectionDetailsFragment}
|
||||||
${productErrorFragment}
|
${collectionsErrorFragment}
|
||||||
mutation CollectionUpdate($id: ID!, $input: CollectionInput!) {
|
mutation CollectionUpdate($id: ID!, $input: CollectionInput!) {
|
||||||
collectionUpdate(id: $id, input: $input) {
|
collectionUpdate(id: $id, input: $input) {
|
||||||
collection {
|
collection {
|
||||||
...CollectionDetailsFragment
|
...CollectionDetailsFragment
|
||||||
}
|
}
|
||||||
errors: productErrors {
|
errors: collectionErrors {
|
||||||
...ProductErrorFragment
|
...CollectionErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,43 +58,9 @@ export const useCollectionUpdateMutation = makeMutation<
|
||||||
CollectionUpdateVariables
|
CollectionUpdateVariables
|
||||||
>(collectionUpdate);
|
>(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`
|
const assignCollectionProduct = gql`
|
||||||
${collectionProductFragment}
|
${collectionProductFragment}
|
||||||
${productErrorFragment}
|
${collectionsErrorFragment}
|
||||||
mutation CollectionAssignProduct(
|
mutation CollectionAssignProduct(
|
||||||
$collectionId: ID!
|
$collectionId: ID!
|
||||||
$productIds: [ID!]!
|
$productIds: [ID!]!
|
||||||
|
@ -123,8 +86,8 @@ const assignCollectionProduct = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors: productErrors {
|
errors: collectionErrors {
|
||||||
...ProductErrorFragment
|
...CollectionErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,14 +99,14 @@ export const useCollectionAssignProductMutation = makeMutation<
|
||||||
|
|
||||||
const createCollection = gql`
|
const createCollection = gql`
|
||||||
${collectionDetailsFragment}
|
${collectionDetailsFragment}
|
||||||
${productErrorFragment}
|
${collectionsErrorFragment}
|
||||||
mutation CreateCollection($input: CollectionCreateInput!) {
|
mutation CreateCollection($input: CollectionCreateInput!) {
|
||||||
collectionCreate(input: $input) {
|
collectionCreate(input: $input) {
|
||||||
collection {
|
collection {
|
||||||
...CollectionDetailsFragment
|
...CollectionDetailsFragment
|
||||||
}
|
}
|
||||||
errors: productErrors {
|
errors: collectionErrors {
|
||||||
...ProductErrorFragment
|
...CollectionErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,11 +117,11 @@ export const useCollectionCreateMutation = makeMutation<
|
||||||
>(createCollection);
|
>(createCollection);
|
||||||
|
|
||||||
const removeCollection = gql`
|
const removeCollection = gql`
|
||||||
${productErrorFragment}
|
${collectionsErrorFragment}
|
||||||
mutation RemoveCollection($id: ID!) {
|
mutation RemoveCollection($id: ID!) {
|
||||||
collectionDelete(id: $id) {
|
collectionDelete(id: $id) {
|
||||||
errors: productErrors {
|
errors: collectionErrors {
|
||||||
...ProductErrorFragment
|
...CollectionErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,7 +132,7 @@ export const useCollectionRemoveMutation = makeMutation<
|
||||||
>(removeCollection);
|
>(removeCollection);
|
||||||
|
|
||||||
const unassignCollectionProduct = gql`
|
const unassignCollectionProduct = gql`
|
||||||
${productErrorFragment}
|
${collectionsErrorFragment}
|
||||||
mutation UnassignCollectionProduct(
|
mutation UnassignCollectionProduct(
|
||||||
$collectionId: ID!
|
$collectionId: ID!
|
||||||
$productIds: [ID]!
|
$productIds: [ID]!
|
||||||
|
@ -188,7 +151,6 @@ const unassignCollectionProduct = gql`
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
isPublished
|
|
||||||
name
|
name
|
||||||
productType {
|
productType {
|
||||||
id
|
id
|
||||||
|
@ -207,8 +169,8 @@ const unassignCollectionProduct = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors: productErrors {
|
errors: collectionErrors {
|
||||||
...ProductErrorFragment
|
...CollectionErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,17 +195,20 @@ export const useCollectionBulkDelete = makeMutation<
|
||||||
CollectionBulkDeleteVariables
|
CollectionBulkDeleteVariables
|
||||||
>(collectionBulkDelete);
|
>(collectionBulkDelete);
|
||||||
|
|
||||||
const collectionBulkPublish = gql`
|
const collectionChannelListingUpdate = gql`
|
||||||
${productErrorFragment}
|
${collectionChannelListingErrorFragment}
|
||||||
mutation CollectionBulkPublish($ids: [ID]!, $isPublished: Boolean!) {
|
mutation CollectionChannelListingUpdate(
|
||||||
collectionBulkPublish(ids: $ids, isPublished: $isPublished) {
|
$id: ID!
|
||||||
errors: productErrors {
|
$input: CollectionChannelListingUpdateInput!
|
||||||
...ProductErrorFragment
|
) {
|
||||||
|
collectionChannelListingUpdate(id: $id, input: $input) {
|
||||||
|
errors: collectionChannelListingErrors {
|
||||||
|
...CollectionChannelListingErrorFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const useCollectionBulkPublish = makeMutation<
|
export const useCollectionChannelListingUpdate = makeMutation<
|
||||||
CollectionBulkPublish,
|
CollectionChannelListingUpdate,
|
||||||
CollectionBulkPublishVariables
|
CollectionChannelListingUpdateVariables
|
||||||
>(collectionBulkPublish);
|
>(collectionChannelListingUpdate);
|
||||||
|
|
|
@ -82,11 +82,6 @@ export const collectionDetails = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shop {
|
|
||||||
homepageCollection {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionDetailsQuery = TypedQuery<
|
export const TypedCollectionDetailsQuery = TypedQuery<
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: CollectionAssignProduct
|
// GraphQL mutation operation: CollectionAssignProduct
|
||||||
|
@ -19,13 +19,37 @@ export interface CollectionAssignProduct_collectionAddProducts_collection_produc
|
||||||
url: string;
|
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 {
|
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
productType: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_productType;
|
productType: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_productType;
|
||||||
thumbnail: CollectionAssignProduct_collectionAddProducts_collection_products_edges_node_thumbnail | null;
|
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 {
|
export interface CollectionAssignProduct_collectionAddProducts_collection_products_edges {
|
||||||
|
@ -54,8 +78,8 @@ export interface CollectionAssignProduct_collectionAddProducts_collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionAssignProduct_collectionAddProducts_errors {
|
export interface CollectionAssignProduct_collectionAddProducts_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
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
|
// 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 {
|
export interface CollectionDetails_collection_metadata {
|
||||||
__typename: "MetadataItem";
|
__typename: "MetadataItem";
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -35,13 +48,37 @@ export interface CollectionDetails_collection_products_edges_node_thumbnail {
|
||||||
url: string;
|
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 {
|
export interface CollectionDetails_collection_products_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
productType: CollectionDetails_collection_products_edges_node_productType;
|
productType: CollectionDetails_collection_products_edges_node_productType;
|
||||||
thumbnail: CollectionDetails_collection_products_edges_node_thumbnail | null;
|
thumbnail: CollectionDetails_collection_products_edges_node_thumbnail | null;
|
||||||
|
channelListings: CollectionDetails_collection_products_edges_node_channelListings[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionDetails_collection_products_edges {
|
export interface CollectionDetails_collection_products_edges {
|
||||||
|
@ -66,32 +103,20 @@ export interface CollectionDetails_collection_products {
|
||||||
export interface CollectionDetails_collection {
|
export interface CollectionDetails_collection {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
channelListings: CollectionDetails_collection_channelListings[] | null;
|
||||||
metadata: (CollectionDetails_collection_metadata | null)[];
|
metadata: (CollectionDetails_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
products: CollectionDetails_collection_products | 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 {
|
export interface CollectionDetails {
|
||||||
collection: CollectionDetails_collection | null;
|
collection: CollectionDetails_collection | null;
|
||||||
shop: CollectionDetails_shop;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionDetailsVariables {
|
export interface CollectionDetailsVariables {
|
||||||
|
|
|
@ -8,6 +8,19 @@ import { CollectionFilterInput, CollectionSortingInput } from "./../../types/glo
|
||||||
// GraphQL query operation: CollectionList
|
// 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 {
|
export interface CollectionList_collections_edges_node_products {
|
||||||
__typename: "ProductCountableConnection";
|
__typename: "ProductCountableConnection";
|
||||||
totalCount: number | null;
|
totalCount: number | null;
|
||||||
|
@ -16,8 +29,8 @@ export interface CollectionList_collections_edges_node_products {
|
||||||
export interface CollectionList_collections_edges_node {
|
export interface CollectionList_collections_edges_node {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
channelListings: CollectionList_collections_edges_node_channelListings[] | null;
|
||||||
products: CollectionList_collections_edges_node_products | null;
|
products: CollectionList_collections_edges_node_products | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,25 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// 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
|
// 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 {
|
export interface CollectionUpdate_collectionUpdate_collection_metadata {
|
||||||
__typename: "MetadataItem";
|
__typename: "MetadataItem";
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -29,21 +42,20 @@ export interface CollectionUpdate_collectionUpdate_collection_backgroundImage {
|
||||||
export interface CollectionUpdate_collectionUpdate_collection {
|
export interface CollectionUpdate_collectionUpdate_collection {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
channelListings: CollectionUpdate_collectionUpdate_collection_channelListings[] | null;
|
||||||
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdate_collectionUpdate_errors {
|
export interface CollectionUpdate_collectionUpdate_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,32 +2,23 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// 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
|
// GraphQL mutation operation: CollectionUpdateWithHomepage
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_errors {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing_channel {
|
||||||
__typename: "ShopError";
|
__typename: "Channel";
|
||||||
code: ShopErrorCode;
|
|
||||||
field: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_shop_homepageCollection {
|
|
||||||
__typename: "Collection";
|
|
||||||
id: string;
|
id: string;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate_shop {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing {
|
||||||
__typename: "Shop";
|
__typename: "CollectionChannelListing";
|
||||||
homepageCollection: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop_homepageCollection | null;
|
isPublished: boolean;
|
||||||
}
|
publicationDate: any | null;
|
||||||
|
channel: CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing_channel;
|
||||||
export interface CollectionUpdateWithHomepage_homepageCollectionUpdate {
|
|
||||||
__typename: "HomepageCollectionUpdate";
|
|
||||||
errors: CollectionUpdateWithHomepage_homepageCollectionUpdate_errors[];
|
|
||||||
shop: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_metadata {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_metadata {
|
||||||
|
@ -51,21 +42,20 @@ export interface CollectionUpdateWithHomepage_collectionUpdate_collection_backgr
|
||||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
channelListings: CollectionUpdateWithHomepage_collectionUpdate_collection_channelListing[] | null;
|
||||||
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_collectionUpdate_errors {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,12 +66,10 @@ export interface CollectionUpdateWithHomepage_collectionUpdate {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage {
|
export interface CollectionUpdateWithHomepage {
|
||||||
homepageCollectionUpdate: CollectionUpdateWithHomepage_homepageCollectionUpdate | null;
|
|
||||||
collectionUpdate: CollectionUpdateWithHomepage_collectionUpdate | null;
|
collectionUpdate: CollectionUpdateWithHomepage_collectionUpdate | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepageVariables {
|
export interface CollectionUpdateWithHomepageVariables {
|
||||||
id: string;
|
id: string;
|
||||||
input: CollectionInput;
|
input: CollectionInput;
|
||||||
homepageId?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,25 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// 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
|
// 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 {
|
export interface CreateCollection_collectionCreate_collection_metadata {
|
||||||
__typename: "MetadataItem";
|
__typename: "MetadataItem";
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -29,21 +42,20 @@ export interface CreateCollection_collectionCreate_collection_backgroundImage {
|
||||||
export interface CreateCollection_collectionCreate_collection {
|
export interface CreateCollection_collectionCreate_collection {
|
||||||
__typename: "Collection";
|
__typename: "Collection";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
channelListings: CreateCollection_collectionCreate_collection_channelListings[] | null;
|
||||||
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
||||||
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateCollection_collectionCreate_errors {
|
export interface CreateCollection_collectionCreate_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: RemoveCollection
|
// GraphQL mutation operation: RemoveCollection
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface RemoveCollection_collectionDelete_errors {
|
export interface RemoveCollection_collectionDelete_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { ProductErrorCode } from "./../../types/globalTypes";
|
import { CollectionErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: UnassignCollectionProduct
|
// GraphQL mutation operation: UnassignCollectionProduct
|
||||||
|
@ -22,7 +22,6 @@ export interface UnassignCollectionProduct_collectionRemoveProducts_collection_p
|
||||||
export interface UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node {
|
export interface UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
productType: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_productType;
|
productType: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_productType;
|
||||||
thumbnail: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_thumbnail | null;
|
thumbnail: UnassignCollectionProduct_collectionRemoveProducts_collection_products_edges_node_thumbnail | null;
|
||||||
|
@ -54,8 +53,8 @@ export interface UnassignCollectionProduct_collectionRemoveProducts_collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnassignCollectionProduct_collectionRemoveProducts_errors {
|
export interface UnassignCollectionProduct_collectionRemoveProducts_errors {
|
||||||
__typename: "ProductError";
|
__typename: "CollectionError";
|
||||||
code: ProductErrorCode;
|
code: CollectionErrorCode;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,7 @@ export enum CollectionListUrlFiltersEnum {
|
||||||
query = "query"
|
query = "query"
|
||||||
}
|
}
|
||||||
export type CollectionListUrlFilters = Filters<CollectionListUrlFiltersEnum>;
|
export type CollectionListUrlFilters = Filters<CollectionListUrlFiltersEnum>;
|
||||||
export type CollectionListUrlDialog =
|
export type CollectionListUrlDialog = "remove" | "settings" | TabActionDialog;
|
||||||
| "publish"
|
|
||||||
| "unpublish"
|
|
||||||
| "remove"
|
|
||||||
| TabActionDialog;
|
|
||||||
export enum CollectionListUrlSortField {
|
export enum CollectionListUrlSortField {
|
||||||
name = "name",
|
name = "name",
|
||||||
available = "available",
|
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 { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
|
import useChannels from "@saleor/hooks/useChannels";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
|
||||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||||
import {
|
import {
|
||||||
useMetadataUpdate,
|
useMetadataUpdate,
|
||||||
|
@ -14,7 +17,10 @@ import { useIntl } from "react-intl";
|
||||||
import { CollectionCreateInput } from "../../types/globalTypes";
|
import { CollectionCreateInput } from "../../types/globalTypes";
|
||||||
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
||||||
import { CollectionCreateData } from "../components/CollectionCreatePage/form";
|
import { CollectionCreateData } from "../components/CollectionCreatePage/form";
|
||||||
import { useCollectionCreateMutation } from "../mutations";
|
import {
|
||||||
|
useCollectionChannelListingUpdate,
|
||||||
|
useCollectionCreateMutation
|
||||||
|
} from "../mutations";
|
||||||
import { collectionListUrl, collectionUrl } from "../urls";
|
import { collectionListUrl, collectionUrl } from "../urls";
|
||||||
|
|
||||||
export const CollectionCreate: React.FC = () => {
|
export const CollectionCreate: React.FC = () => {
|
||||||
|
@ -24,6 +30,31 @@ export const CollectionCreate: React.FC = () => {
|
||||||
const [updateMetadata] = useMetadataUpdate({});
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
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({
|
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.collectionCreate.errors.length === 0) {
|
if (data.collectionCreate.errors.length === 0) {
|
||||||
|
@ -58,13 +89,29 @@ export const CollectionCreate: React.FC = () => {
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
title: formData.seoTitle
|
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(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
|
@ -81,10 +128,34 @@ export const CollectionCreate: React.FC = () => {
|
||||||
description: "window title"
|
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
|
<CollectionCreatePage
|
||||||
errors={createCollectionOpts.data?.collectionCreate.errors || []}
|
errors={createCollectionOpts.data?.collectionCreate.errors || []}
|
||||||
|
channelsErrors={
|
||||||
|
updateChannelsOpts?.data?.collectionChannelListingUpdate.errors || []
|
||||||
|
}
|
||||||
|
currentChannels={currentChannels}
|
||||||
|
channelsCount={channelsData?.channels?.length}
|
||||||
|
openChannelsModal={handleChannelsModalOpen}
|
||||||
|
onChannelsChange={setCurrentChannels}
|
||||||
onBack={() => navigate(collectionListUrl())}
|
onBack={() => navigate(collectionListUrl())}
|
||||||
disabled={createCollectionOpts.loading}
|
disabled={createCollectionOpts.loading || updateChannelsOpts.loading}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
saveButtonBarState={createCollectionOpts.status}
|
saveButtonBarState={createCollectionOpts.status}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
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 ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import AssignProductDialog from "@saleor/components/AssignProductDialog";
|
import AssignProductDialog from "@saleor/components/AssignProductDialog";
|
||||||
|
import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
|
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
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 useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
|
@ -19,6 +27,7 @@ import {
|
||||||
useMetadataUpdate,
|
useMetadataUpdate,
|
||||||
usePrivateMetadataUpdate
|
usePrivateMetadataUpdate
|
||||||
} from "@saleor/utils/metadata/updateMetadata";
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
|
import { diff } from "fast-array-diff";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -29,9 +38,9 @@ import CollectionDetailsPage from "../components/CollectionDetailsPage/Collectio
|
||||||
import { CollectionUpdateData } from "../components/CollectionDetailsPage/form";
|
import { CollectionUpdateData } from "../components/CollectionDetailsPage/form";
|
||||||
import {
|
import {
|
||||||
useCollectionAssignProductMutation,
|
useCollectionAssignProductMutation,
|
||||||
|
useCollectionChannelListingUpdate,
|
||||||
useCollectionRemoveMutation,
|
useCollectionRemoveMutation,
|
||||||
useCollectionUpdateMutation,
|
useCollectionUpdateMutation,
|
||||||
useCollectionUpdateWithHomepageMutation,
|
|
||||||
useUnassignCollectionProductMutation
|
useUnassignCollectionProductMutation
|
||||||
} from "../mutations";
|
} from "../mutations";
|
||||||
import { TypedCollectionDetailsQuery } from "../queries";
|
import { TypedCollectionDetailsQuery } from "../queries";
|
||||||
|
@ -65,6 +74,12 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
const [updateMetadata] = useMetadataUpdate({});
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
|
const [
|
||||||
|
updateChannels,
|
||||||
|
updateChannelsOpts
|
||||||
|
] = useCollectionChannelListingUpdate({});
|
||||||
|
const { data: channelsData } = useChannelsList({});
|
||||||
|
|
||||||
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
||||||
if (data.collectionUpdate.errors.length === 0) {
|
if (data.collectionUpdate.errors.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
|
@ -88,17 +103,6 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
onCompleted: handleCollectionUpdate
|
onCompleted: handleCollectionUpdate
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
|
||||||
updateCollectionWithHomepage,
|
|
||||||
updateCollectionWithHomepageOpts
|
|
||||||
] = useCollectionUpdateWithHomepageMutation({
|
|
||||||
onCompleted: data => {
|
|
||||||
if (data.homepageCollectionUpdate.errors.length === 0) {
|
|
||||||
handleCollectionUpdate(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const [assignProduct, assignProductOpts] = useCollectionAssignProductMutation(
|
const [assignProduct, assignProductOpts] = useCollectionAssignProductMutation(
|
||||||
{
|
{
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
|
@ -155,6 +159,8 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||||
const handleBack = () => navigate(collectionListUrl());
|
const handleBack = () => navigate(collectionListUrl());
|
||||||
|
|
||||||
|
const [selectedChannel] = useLocalStorage("collectionListChannel", "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedCollectionDetailsQuery
|
<TypedCollectionDetailsQuery
|
||||||
displayLoader
|
displayLoader
|
||||||
|
@ -162,50 +168,72 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
>
|
>
|
||||||
{({ data, loading }) => {
|
{({ data, loading }) => {
|
||||||
const collection = data?.collection;
|
const collection = data?.collection;
|
||||||
|
|
||||||
if (collection === null) {
|
if (collection === null) {
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
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 handleUpdate = async (formData: CollectionUpdateData) => {
|
||||||
const input: CollectionInput = {
|
const input: CollectionInput = {
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
isPublished: formData.isPublished,
|
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
publicationDate: formData.publicationDate,
|
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
title: formData.seoTitle
|
title: formData.seoTitle
|
||||||
},
|
},
|
||||||
slug: formData.slug
|
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({
|
const result = await updateCollection({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const diffChannels = diff(
|
||||||
|
collectionChannelsChoices,
|
||||||
|
formData.channelListings,
|
||||||
|
(a, b) => a.id === b.id
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
return result.data.collectionUpdate.errors;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
data?.collection,
|
data?.collection,
|
||||||
|
@ -215,13 +243,9 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const formTransitionState = getMutationState(
|
const formTransitionState = getMutationState(
|
||||||
updateCollectionOpts.called ||
|
updateCollectionOpts.called,
|
||||||
updateCollectionWithHomepageOpts.called,
|
updateCollectionOpts.loading,
|
||||||
updateCollectionOpts.loading ||
|
updateCollectionOpts.data?.collectionUpdate.errors
|
||||||
updateCollectionWithHomepageOpts.loading,
|
|
||||||
updateCollectionOpts.data?.collectionUpdate.errors,
|
|
||||||
updateCollectionWithHomepageOpts.data?.collectionUpdate.errors,
|
|
||||||
updateCollectionWithHomepageOpts.data?.homepageCollectionUpdate.errors
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
|
@ -232,17 +256,34 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
|
|
||||||
return (
|
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
|
<CollectionDetailsPage
|
||||||
onAdd={() => openModal("assign")}
|
onAdd={() => openModal("assign")}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
disabled={loading}
|
disabled={loading || updateChannelsOpts.loading}
|
||||||
collection={data?.collection}
|
collection={data?.collection}
|
||||||
|
channelsErrors={
|
||||||
|
updateChannelsOpts?.data?.collectionChannelListingUpdate
|
||||||
|
.errors || []
|
||||||
|
}
|
||||||
errors={updateCollectionOpts?.data?.collectionUpdate.errors || []}
|
errors={updateCollectionOpts?.data?.collectionUpdate.errors || []}
|
||||||
isFeatured={maybe(
|
|
||||||
() => data.shop.homepageCollection.id === data.collection.id,
|
|
||||||
false
|
|
||||||
)}
|
|
||||||
onCollectionRemove={() => openModal("remove")}
|
onCollectionRemove={() => openModal("remove")}
|
||||||
onImageDelete={() => openModal("removeImage")}
|
onImageDelete={() => openModal("removeImage")}
|
||||||
onImageUpload={file =>
|
onImageUpload={file =>
|
||||||
|
@ -290,6 +331,14 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
selected={listElements.length}
|
selected={listElements.length}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
|
currentChannels={currentChannels}
|
||||||
|
hasChannelChanged={
|
||||||
|
collectionChannelsChoices?.length !== currentChannels?.length
|
||||||
|
}
|
||||||
|
channelsCount={channelsData?.channels?.length}
|
||||||
|
selectedChannel={selectedChannel}
|
||||||
|
openChannelsModal={handleChannelsModalOpen}
|
||||||
|
onChannelsChange={setCurrentChannels}
|
||||||
/>
|
/>
|
||||||
<AssignProductDialog
|
<AssignProductDialog
|
||||||
confirmButtonState={assignProductOpts.status}
|
confirmButtonState={assignProductOpts.status}
|
||||||
|
|
|
@ -1,35 +1,31 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import ChannelSettingsDialog from "@saleor/channels/components/ChannelSettingsDialog";
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||||
import SaveFilterTabDialog, {
|
import SaveFilterTabDialog, {
|
||||||
SaveFilterTabDialogFormData
|
SaveFilterTabDialogFormData
|
||||||
} from "@saleor/components/SaveFilterTabDialog";
|
} from "@saleor/components/SaveFilterTabDialog";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
|
import useChannelsSettings from "@saleor/hooks/useChannelsSettings";
|
||||||
import useListSettings from "@saleor/hooks/useListSettings";
|
import useListSettings from "@saleor/hooks/useListSettings";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
createPaginationState
|
createPaginationState
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
|
||||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||||
import { getSortParams } from "@saleor/utils/sort";
|
import { getSortParams } from "@saleor/utils/sort";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
||||||
import {
|
import { useCollectionBulkDelete } from "../../mutations";
|
||||||
useCollectionBulkDelete,
|
|
||||||
useCollectionBulkPublish
|
|
||||||
} from "../../mutations";
|
|
||||||
import { useCollectionListQuery } from "../../queries";
|
import { useCollectionListQuery } from "../../queries";
|
||||||
import {
|
import {
|
||||||
collectionAddUrl,
|
collectionAddUrl,
|
||||||
|
@ -42,8 +38,6 @@ import {
|
||||||
areFiltersApplied,
|
areFiltersApplied,
|
||||||
deleteFilterTab,
|
deleteFilterTab,
|
||||||
getActiveFilters,
|
getActiveFilters,
|
||||||
getFilterOpts,
|
|
||||||
getFilterQueryParam,
|
|
||||||
getFilterTabs,
|
getFilterTabs,
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab
|
saveFilterTab
|
||||||
|
@ -58,7 +52,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const paginate = usePaginator();
|
const paginate = usePaginator();
|
||||||
const shop = useShop();
|
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||||
params.ids
|
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 tabs = getFilterTabs();
|
||||||
|
|
||||||
const currentTab =
|
const currentTab =
|
||||||
|
@ -124,23 +99,27 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
: 0
|
: 0
|
||||||
: parseInt(params.activeTab, 0);
|
: parseInt(params.activeTab, 0);
|
||||||
|
|
||||||
const [
|
const handleSearchChange = (query: string) => {
|
||||||
changeFilters,
|
navigate(
|
||||||
resetFilters,
|
collectionListUrl({
|
||||||
handleSearchChange
|
...getActiveFilters(params),
|
||||||
] = createFilterHandlers({
|
activeTab: undefined,
|
||||||
cleanupFn: reset,
|
query
|
||||||
createUrl: collectionListUrl,
|
})
|
||||||
getFilterQueryParam,
|
);
|
||||||
navigate,
|
};
|
||||||
params
|
|
||||||
});
|
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
CollectionListUrlDialog,
|
CollectionListUrlDialog,
|
||||||
CollectionListUrlQueryParams
|
CollectionListUrlQueryParams
|
||||||
>(navigate, collectionListUrl, params);
|
>(navigate, collectionListUrl, params);
|
||||||
|
|
||||||
|
const {
|
||||||
|
channelChoices,
|
||||||
|
handleChannelSelectConfirm,
|
||||||
|
selectedChannel
|
||||||
|
} = useChannelsSettings("collectionListChannel", { closeModal, openModal });
|
||||||
|
|
||||||
const handleTabChange = (tab: number) => {
|
const handleTabChange = (tab: number) => {
|
||||||
reset();
|
reset();
|
||||||
navigate(
|
navigate(
|
||||||
|
@ -169,19 +148,25 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
||||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{!!channelChoices?.length && (
|
||||||
|
<ChannelSettingsDialog
|
||||||
|
channelsChoices={channelChoices}
|
||||||
|
defaultChoice={selectedChannel}
|
||||||
|
open={params.action === "settings"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={handleChannelSelectConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<CollectionListPage
|
<CollectionListPage
|
||||||
currencySymbol={currencySymbol}
|
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
filterOpts={getFilterOpts(params)}
|
|
||||||
initialSearch={params.query || ""}
|
initialSearch={params.query || ""}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
onFilterChange={changeFilters}
|
|
||||||
onAdd={() => navigate(collectionAddUrl)}
|
onAdd={() => navigate(collectionAddUrl)}
|
||||||
onAll={resetFilters}
|
onAll={() => navigate(collectionListUrl())}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
onTabDelete={() => openModal("delete-search")}
|
onTabDelete={() => openModal("delete-search")}
|
||||||
onTabSave={() => openModal("save-search")}
|
onTabSave={() => openModal("save-search")}
|
||||||
|
@ -197,33 +182,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
onRowClick={id => () => navigate(collectionUrl(id))}
|
onRowClick={id => () => navigate(collectionUrl(id))}
|
||||||
toolbar={
|
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
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -234,71 +192,17 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
isChecked={isSelected}
|
isChecked={isSelected}
|
||||||
selected={listElements.length}
|
selected={listElements.length}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
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
|
<ActionDialog
|
||||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
|
|
|
@ -31,6 +31,7 @@ interface ActionDialogProps extends DialogProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
confirmButtonLabel?: string;
|
confirmButtonLabel?: string;
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
|
disabled?: boolean;
|
||||||
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
|
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
|
||||||
title: string;
|
title: string;
|
||||||
variant?: "default" | "delete" | "info";
|
variant?: "default" | "delete" | "info";
|
||||||
|
@ -42,6 +43,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
||||||
children,
|
children,
|
||||||
confirmButtonLabel,
|
confirmButtonLabel,
|
||||||
confirmButtonState,
|
confirmButtonState,
|
||||||
|
disabled,
|
||||||
open,
|
open,
|
||||||
title,
|
title,
|
||||||
variant,
|
variant,
|
||||||
|
@ -58,11 +60,12 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogContent>{children}</DialogContent>
|
<DialogContent>{children}</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>
|
<Button data-test="back" onClick={onClose}>
|
||||||
<FormattedMessage {...buttonMessages.back} />
|
<FormattedMessage {...buttonMessages.back} />
|
||||||
</Button>
|
</Button>
|
||||||
{variant !== "info" && (
|
{variant !== "info" && (
|
||||||
<ConfirmButton
|
<ConfirmButton
|
||||||
|
disabled={disabled}
|
||||||
transitionState={confirmButtonState}
|
transitionState={confirmButtonState}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -70,6 +73,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
||||||
className={classNames({
|
className={classNames({
|
||||||
[classes.deleteButton]: variant === "delete"
|
[classes.deleteButton]: variant === "delete"
|
||||||
})}
|
})}
|
||||||
|
data-test="submit"
|
||||||
>
|
>
|
||||||
{confirmButtonLabel ||
|
{confirmButtonLabel ||
|
||||||
(variant === "delete"
|
(variant === "delete"
|
||||||
|
|
|
@ -238,7 +238,7 @@ const useStyles = makeStyles(
|
||||||
padding: 25
|
padding: 25
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
zIndex: 1
|
zIndex: 2
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
width: `100%`
|
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 Decorator from "@saleor/storybook/Decorator";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
const productChannels = createChannelsDataFromProduct(product(""));
|
||||||
|
|
||||||
import AvailabilityCard from "./AvailabilityCard";
|
import AvailabilityCard from "./AvailabilityCard";
|
||||||
const props = {
|
const props = {
|
||||||
data: {
|
allChannelsCount: 4,
|
||||||
availableForPurchase: "",
|
channels: productChannels,
|
||||||
isAvailableForPurchase: false,
|
|
||||||
isPublished: true,
|
|
||||||
publicationDate: "",
|
|
||||||
visibleInListings: true
|
|
||||||
},
|
|
||||||
errors: [],
|
errors: [],
|
||||||
messages: {
|
messages: {
|
||||||
hiddenLabel: "Not published",
|
hiddenLabel: "Not published",
|
||||||
hiddenSecondLabel: "hidden label",
|
hiddenSecondLabel: "hidden label",
|
||||||
visibleLabel: "Published"
|
visibleLabel: "Published"
|
||||||
},
|
},
|
||||||
onChange: () => undefined
|
onChange: () => undefined,
|
||||||
|
openModal: () => undefined,
|
||||||
|
selectedChannelsCount: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Generics / AvailabilityCard", module)
|
storiesOf("Generics / AvailabilityCard", module)
|
||||||
|
|
|
@ -1,29 +1,58 @@
|
||||||
import VisibilityCard, {
|
import ChannelsAvailability, {
|
||||||
VisibilityCardProps
|
ChannelsAvailabilityProps,
|
||||||
} from "@saleor/components/VisibilityCard";
|
Message
|
||||||
|
} from "@saleor/components/ChannelsAvailability";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
interface AvailabilityCardProps extends VisibilityCardProps {
|
interface AvailabilityCardProps {
|
||||||
data: {
|
messages: Message;
|
||||||
availableForPurchase: string;
|
|
||||||
isAvailableForPurchase: boolean;
|
|
||||||
isPublished: boolean;
|
|
||||||
publicationDate: string;
|
|
||||||
visibleInListings: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvailabilityCard: React.FC<AvailabilityCardProps> = props => {
|
export const AvailabilityCard: React.FC<AvailabilityCardProps &
|
||||||
|
Omit<ChannelsAvailabilityProps, "channelsMessages">> = props => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
const localizeDate = useDateLocalize();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VisibilityCard
|
<ChannelsAvailability
|
||||||
{...props}
|
{...props}
|
||||||
messages={{
|
channelsMessages={props.channels.reduce(
|
||||||
|
(prevVal, currVal) => ({
|
||||||
|
...prevVal,
|
||||||
|
[currVal.id]: {
|
||||||
...props.messages,
|
...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({
|
availableLabel: intl.formatMessage({
|
||||||
defaultMessage: "Available for purchase",
|
defaultMessage: "Available for purchase",
|
||||||
description: "product availability"
|
description: "product availability"
|
||||||
|
@ -34,7 +63,16 @@ export const AvailabilityCard: React.FC<AvailabilityCardProps> = props => {
|
||||||
description: "product available for purchase date"
|
description: "product available for purchase date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: localizeDate(props.data.availableForPurchase, "L")
|
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({
|
setAvailabilityDateLabel: intl.formatMessage({
|
||||||
|
@ -45,7 +83,10 @@ export const AvailabilityCard: React.FC<AvailabilityCardProps> = props => {
|
||||||
defaultMessage: "Unavailable for purchase",
|
defaultMessage: "Unavailable for purchase",
|
||||||
description: "product unavailability"
|
description: "product unavailability"
|
||||||
})
|
})
|
||||||
}}
|
}
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,9 @@ export interface CardMenuProps {
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
|
container: {
|
||||||
|
zIndex: 1
|
||||||
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
borderRadius: "100%",
|
borderRadius: "100%",
|
||||||
|
@ -98,6 +101,7 @@ const CardMenu: React.FC<CardMenuProps> = props => {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Popper
|
<Popper
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
|
className={classes.container}
|
||||||
open={open}
|
open={open}
|
||||||
anchorEl={anchorRef.current}
|
anchorEl={anchorRef.current}
|
||||||
transition
|
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