Add channel shipping zones (#1015)
* Add naked input option to SingleAutocompleteSelectField and update it's stories * Add new icons - chevron up, down & trash * Add deletable item component and stories * Add card add items footer component to be used in warehouses and product stocks assign * Update schema and types * Add shipping zones card components * Update channel details page form to also include shipping zones * Update makeTopLevelSearch hook files directory and add getSearchFetchMoreProps function to avoid extracting it manually every time * Update channels types & fragments * Move getDefaultNotifierSuccessErrorData function to useNotifier utils, update dir etc., also make order discount provider use it from the new dir * Add shippinh zone to channel update and create and add shipping zone search * Update messages * Fix types * Fix lint, types etc * Small refactor from review and quick fix styles of shipping zones card * Refactor a bit and update snapshots * Refactor a bit and update snapshots * Addd / refactor channels availability components * Add useChannelsWithProductVariants hook with utils and types * Add / refactor more channels availability components * Move avatar from table cell avatar to separate component for it to be usable outside of tables * Add channels with variants logic to product create and update pages & views * Refactor components to use updated channels availability components * Remove unnecessary comments * Update storybook * Update types * Update messages * Fix prices for variants / simple product not uodating properly * Post merge cleanup, update schema, types, etc. * Change shipping zone details warehouses card into settings card and add ability to assign channels to shipping zone * Update types * Update snapshots * Fix selecting / deselecting all channels in channels with variants modal * Fixes after review, some types changes etc. * Update snapshots * Small types fixes * Make price rates views use parent shipping method channels instead of all * Make price rates views use parent shipping method channels instead of all * Update types * Fix bugs * Fixes after review * Fix channels availability data submission * Fix lint * Fix variant pricing card showing not related channels * Fixes after review * Fix types * Hide unaviable variants in add products to draft order dialog * Fix channels with variants availability modal showing confirm button as enabled when it shouldn't * Fix types * Update semi checked icon to match old designs * Update types * Update channels icon in channels with variants availability * Fix product cypress test after product channels mutation changed * Fix trash and chevron down colors in dark mode * Fix shipping zones card footer not updating query after click away * Fix types in schema, add condition not to display shipping zones select in channel details if all zones have already been selected * Fix products adding in order draft dialog * Fix simple productupdate * Update snapshots after merge with master * Update messages * Fix product api request for cypress * Add missing test id * Fix selecting if product is simple -> form being submitted with empty data sometimes * Update snapshots, messages and add fix for invalid date at product update * Remove unnecessary imports * Fix failing test in saleor 2552 (#1061) * fix * fix * fix Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com> Co-authored-by: Karolina <rakoczy.karolina@gmail.com>
This commit is contained in:
parent
07bc522af1
commit
8fe66a3bde
170 changed files with 14601 additions and 4469 deletions
|
@ -42,6 +42,8 @@ export function updateProduct(productId, input) {
|
||||||
export function updateChannelInProduct({
|
export function updateChannelInProduct({
|
||||||
productId,
|
productId,
|
||||||
channelId,
|
channelId,
|
||||||
|
variantsIdsToAdd = "[]",
|
||||||
|
variantsIdsToRemove = "[]",
|
||||||
isPublished = true,
|
isPublished = true,
|
||||||
isAvailableForPurchase = true,
|
isAvailableForPurchase = true,
|
||||||
visibleInListings = true
|
visibleInListings = true
|
||||||
|
@ -49,11 +51,13 @@ export function updateChannelInProduct({
|
||||||
const mutation = `mutation{
|
const mutation = `mutation{
|
||||||
productChannelListingUpdate(id:"${productId}",
|
productChannelListingUpdate(id:"${productId}",
|
||||||
input:{
|
input:{
|
||||||
addChannels:{
|
updateChannels:{
|
||||||
channelId:"${channelId}"
|
channelId:"${channelId}"
|
||||||
isPublished:${isPublished}
|
isPublished:${isPublished}
|
||||||
isAvailableForPurchase:${isAvailableForPurchase}
|
isAvailableForPurchase:${isAvailableForPurchase}
|
||||||
visibleInListings:${visibleInListings}
|
visibleInListings:${visibleInListings}
|
||||||
|
addVariants:${variantsIdsToAdd}
|
||||||
|
removeVariants:${variantsIdsToRemove}
|
||||||
}
|
}
|
||||||
}){
|
}){
|
||||||
product{
|
product{
|
||||||
|
|
|
@ -13,11 +13,12 @@ export function createShippingRate(name, shippingZone) {
|
||||||
return cy.sendRequestWithQuery(mutation);
|
return cy.sendRequestWithQuery(mutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createShippingZone(name, country) {
|
export function createShippingZone(name, country, channelId) {
|
||||||
const mutation = `mutation{
|
const mutation = `mutation{
|
||||||
shippingZoneCreate(input:{
|
shippingZoneCreate(input:{
|
||||||
name: "${name}"
|
name: "${name}"
|
||||||
countries: "${country}"
|
countries: "${country}"
|
||||||
|
addChannels:["${channelId}"]
|
||||||
}){
|
}){
|
||||||
shippingZone{
|
shippingZone{
|
||||||
id
|
id
|
||||||
|
@ -27,7 +28,19 @@ export function createShippingZone(name, country) {
|
||||||
}`;
|
}`;
|
||||||
return cy.sendRequestWithQuery(mutation);
|
return cy.sendRequestWithQuery(mutation);
|
||||||
}
|
}
|
||||||
|
export function addChannelToShippingZone(shippingZoneId, channelId) {
|
||||||
|
const mutation = `mutation addCh{
|
||||||
|
shippingZoneUpdate(id:"${shippingZoneId}", input:{
|
||||||
|
addChannels:["${channelId}"]
|
||||||
|
}){
|
||||||
|
shippingErrors{
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
return cy.sendRequestWithQuery(mutation);
|
||||||
|
}
|
||||||
export function addChannelToShippingMethod(shippingRateId, channelId, price) {
|
export function addChannelToShippingMethod(shippingRateId, channelId, price) {
|
||||||
const mutation = `mutation{
|
const mutation = `mutation{
|
||||||
shippingMethodChannelListingUpdate(id:"${shippingRateId}", input:{
|
shippingMethodChannelListingUpdate(id:"${shippingRateId}", input:{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const AVAILABLE_CHANNELS_FORM = {
|
export const AVAILABLE_CHANNELS_FORM = {
|
||||||
menageChannelsButton: "[data-test-id='channels-availiability-manage-button']",
|
menageChannelsButton: "[data-test-id='channels-availiability-manage-button']",
|
||||||
assignedChannels: "[data-test='channel-availability-item']",
|
assignedChannels: "[class*=expandIcon]",
|
||||||
publishedRadioButtons: "[name*='isPublished']",
|
publishedRadioButtons: "[name*='isPublished']",
|
||||||
availableForPurchaseRadioButtons: "[name*='isAvailableForPurchase']",
|
availableForPurchaseRadioButtons: "[name*='isAvailableForPurchase']",
|
||||||
radioButtonsValueTrue: "[value='true']",
|
radioButtonsValueTrue: "[value='true']",
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const SHIPPING_ZONE_DETAILS = {
|
||||||
addWeightRateButton: "[data-test-id='add-weight-rate']",
|
addWeightRateButton: "[data-test-id='add-weight-rate']",
|
||||||
nameInput: "[name='name']",
|
nameInput: "[name='name']",
|
||||||
shippingRatePriceTableCell: "[data-test-id='shipping-rate-price']",
|
shippingRatePriceTableCell: "[data-test-id='shipping-rate-price']",
|
||||||
warehouseOption: "[data-test='multiautocomplete-select-option']",
|
option: "[data-test='multiautocomplete-select-option']",
|
||||||
warehouseSelector: "[placeholder*='Warehouse']"
|
warehouseSelector: "[placeholder*='Warehouse']",
|
||||||
|
channelSelector: "[placeholder*='Channel']"
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// <reference types="cypress" />
|
// <reference types="cypress" />
|
||||||
import faker from "faker";
|
import faker from "faker";
|
||||||
|
|
||||||
import { addChannelToShippingMethod } from "../../apiRequests/ShippingMethod";
|
import {
|
||||||
|
addChannelToShippingMethod,
|
||||||
|
addChannelToShippingZone
|
||||||
|
} from "../../apiRequests/ShippingMethod";
|
||||||
import { SHIPPING_ZONE_DETAILS } from "../../elements/shipping/shipping-zone-details";
|
import { SHIPPING_ZONE_DETAILS } from "../../elements/shipping/shipping-zone-details";
|
||||||
import { selectChannelInHeader } from "../../steps/channelsSteps";
|
import { selectChannelInHeader } from "../../steps/channelsSteps";
|
||||||
import {
|
import {
|
||||||
|
@ -104,12 +107,16 @@ describe("Shipping methods", () => {
|
||||||
}) => {
|
}) => {
|
||||||
shippingZone = shippingZoneResp;
|
shippingZone = shippingZoneResp;
|
||||||
shippingMethod = shippingMethodResp;
|
shippingMethod = shippingMethodResp;
|
||||||
|
addChannelToShippingZone(shippingZone.id, createdChannel.id).then(
|
||||||
|
() => {
|
||||||
addChannelToShippingMethod(
|
addChannelToShippingMethod(
|
||||||
shippingMethod.id,
|
shippingMethod.id,
|
||||||
createdChannel.id,
|
createdChannel.id,
|
||||||
createdChannelPrice
|
createdChannelPrice
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.addAliasToGraphRequest("ShippingZone");
|
cy.addAliasToGraphRequest("ShippingZone");
|
||||||
|
@ -143,7 +150,12 @@ describe("Shipping methods", () => {
|
||||||
it("should create price based shipping method", () => {
|
it("should create price based shipping method", () => {
|
||||||
const shippingName = `${startsWith}${faker.random.number()}`;
|
const shippingName = `${startsWith}${faker.random.number()}`;
|
||||||
|
|
||||||
createShippingZone(shippingName, warehouse.name, plAddress.countryFullName);
|
createShippingZone(
|
||||||
|
shippingName,
|
||||||
|
warehouse.name,
|
||||||
|
plAddress.countryFullName,
|
||||||
|
defaultChannel.name
|
||||||
|
);
|
||||||
createShippingRate(shippingName, price, rateOptions.PRICE_OPTION);
|
createShippingRate(shippingName, price, rateOptions.PRICE_OPTION);
|
||||||
|
|
||||||
createCheckout({
|
createCheckout({
|
||||||
|
@ -164,7 +176,12 @@ describe("Shipping methods", () => {
|
||||||
it("should create weight based shipping method", () => {
|
it("should create weight based shipping method", () => {
|
||||||
const shippingName = `${startsWith}${faker.random.number()}`;
|
const shippingName = `${startsWith}${faker.random.number()}`;
|
||||||
|
|
||||||
createShippingZone(shippingName, warehouse.name, plAddress.countryFullName);
|
createShippingZone(
|
||||||
|
shippingName,
|
||||||
|
warehouse.name,
|
||||||
|
plAddress.countryFullName,
|
||||||
|
defaultChannel.name
|
||||||
|
);
|
||||||
createShippingRate(shippingName, price, rateOptions.WEIGHT_OPTION);
|
createShippingRate(shippingName, price, rateOptions.WEIGHT_OPTION);
|
||||||
createCheckout({
|
createCheckout({
|
||||||
channelSlug: defaultChannel.slug,
|
channelSlug: defaultChannel.slug,
|
||||||
|
|
|
@ -93,7 +93,7 @@ describe("Products", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should filter products out of stock", () => {
|
xit("should filter products out of stock", () => {
|
||||||
const productOutOfStock = `${startsWith}${faker.random.number()}`;
|
const productOutOfStock = `${startsWith}${faker.random.number()}`;
|
||||||
createProductInChannel({
|
createProductInChannel({
|
||||||
name: productOutOfStock,
|
name: productOutOfStock,
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
|
@ -1,5 +1,6 @@
|
||||||
import { COLLECTION_SELECTORS } from "../elements/catalog/collection-selectors";
|
import { COLLECTION_SELECTORS } from "../elements/catalog/collection-selectors";
|
||||||
import { ASSIGN_PRODUCTS_SELECTORS } from "../elements/catalog/products/assign-products-selectors";
|
import { ASSIGN_PRODUCTS_SELECTORS } from "../elements/catalog/products/assign-products-selectors";
|
||||||
|
import { PRODUCT_DETAILS } from "../elements/catalog/products/product-details";
|
||||||
import { AVAILABLE_CHANNELS_FORM } from "../elements/channels/available-channels-form";
|
import { AVAILABLE_CHANNELS_FORM } from "../elements/channels/available-channels-form";
|
||||||
import { SELECT_CHANNELS_TO_ASSIGN } from "../elements/channels/select-channels-to-assign";
|
import { SELECT_CHANNELS_TO_ASSIGN } from "../elements/channels/select-channels-to-assign";
|
||||||
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
|
import { BUTTON_SELECTORS } from "../elements/shared/button-selectors";
|
||||||
|
@ -28,6 +29,7 @@ export function createCollection(collectionName, isPublished, channel) {
|
||||||
.click();
|
.click();
|
||||||
cy.addAliasToGraphRequest("CreateCollection");
|
cy.addAliasToGraphRequest("CreateCollection");
|
||||||
cy.get(COLLECTION_SELECTORS.saveButton).click();
|
cy.get(COLLECTION_SELECTORS.saveButton).click();
|
||||||
|
cy.get(PRODUCT_DETAILS.confirmationMsg).should("be.visible");
|
||||||
return cy
|
return cy
|
||||||
.wait("@CreateCollection")
|
.wait("@CreateCollection")
|
||||||
.its("response.body.data.collectionCreate.collection");
|
.its("response.body.data.collectionCreate.collection");
|
||||||
|
|
|
@ -3,7 +3,12 @@ import { SHIPPING_RATE_DETAILS } from "../elements/shipping/shipping-rate-detail
|
||||||
import { SHIPPING_ZONE_DETAILS } from "../elements/shipping/shipping-zone-details";
|
import { SHIPPING_ZONE_DETAILS } from "../elements/shipping/shipping-zone-details";
|
||||||
import { SHIPPING_ZONES_LIST } from "../elements/shipping/shipping-zones-list";
|
import { SHIPPING_ZONES_LIST } from "../elements/shipping/shipping-zones-list";
|
||||||
|
|
||||||
export function createShippingZone(shippingName, warehouseName, country) {
|
export function createShippingZone(
|
||||||
|
shippingName,
|
||||||
|
warehouseName,
|
||||||
|
country,
|
||||||
|
channelName
|
||||||
|
) {
|
||||||
cy.get(SHIPPING_ZONES_LIST.addShippingZone)
|
cy.get(SHIPPING_ZONES_LIST.addShippingZone)
|
||||||
.click()
|
.click()
|
||||||
.get(SHIPPING_ZONE_DETAILS.nameInput)
|
.get(SHIPPING_ZONE_DETAILS.nameInput)
|
||||||
|
@ -21,8 +26,13 @@ export function createShippingZone(shippingName, warehouseName, country) {
|
||||||
.click()
|
.click()
|
||||||
.get(SHIPPING_ZONE_DETAILS.warehouseSelector)
|
.get(SHIPPING_ZONE_DETAILS.warehouseSelector)
|
||||||
.click()
|
.click()
|
||||||
.get(SHIPPING_ZONE_DETAILS.warehouseOption)
|
.get(SHIPPING_ZONE_DETAILS.option)
|
||||||
.contains(warehouseName)
|
.contains(warehouseName)
|
||||||
|
.click()
|
||||||
|
.get(SHIPPING_ZONE_DETAILS.channelSelector)
|
||||||
|
.click()
|
||||||
|
.get(SHIPPING_ZONE_DETAILS.option)
|
||||||
|
.contains(channelName)
|
||||||
.click();
|
.click();
|
||||||
cy.addAliasToGraphRequest("UpdateShippingZone");
|
cy.addAliasToGraphRequest("UpdateShippingZone");
|
||||||
cy.get(BUTTON_SELECTORS.confirm).click();
|
cy.get(BUTTON_SELECTORS.confirm).click();
|
||||||
|
|
|
@ -6,7 +6,7 @@ export function createShipping({ channelId, name, address, price = 1 }) {
|
||||||
let shippingZone;
|
let shippingZone;
|
||||||
let warehouse;
|
let warehouse;
|
||||||
|
|
||||||
return createShippingZone(name, address.country)
|
return createShippingZone(name, address.country, channelId)
|
||||||
.then(shippingZoneResp => {
|
.then(shippingZoneResp => {
|
||||||
shippingZone = shippingZoneResp;
|
shippingZone = shippingZoneResp;
|
||||||
createWarehouse({ name, shippingZoneId: shippingZone.id, address });
|
createWarehouse({ name, shippingZoneId: shippingZone.id, address });
|
||||||
|
@ -26,9 +26,9 @@ export function createShipping({ channelId, name, address, price = 1 }) {
|
||||||
.then(() => ({ shippingMethod, shippingZone, warehouse }));
|
.then(() => ({ shippingMethod, shippingZone, warehouse }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createShippingZone(name, country) {
|
export function createShippingZone(name, country, channelId) {
|
||||||
return shippingMethodRequest
|
return shippingMethodRequest
|
||||||
.createShippingZone(name, country)
|
.createShippingZone(name, country, channelId)
|
||||||
.its("body.data.shippingZoneCreate.shippingZone");
|
.its("body.data.shippingZoneCreate.shippingZone");
|
||||||
}
|
}
|
||||||
export function createWarehouse({ name, shippingZoneId, address }) {
|
export function createWarehouse({ name, shippingZoneId, address }) {
|
||||||
|
|
BIN
cypress/videos/channels.js.mp4
Normal file
BIN
cypress/videos/channels.js.mp4
Normal file
Binary file not shown.
BIN
cypress/videos/collections.js.mp4
Normal file
BIN
cypress/videos/collections.js.mp4
Normal file
Binary file not shown.
|
@ -15,6 +15,22 @@
|
||||||
"context": "by preposition",
|
"context": "by preposition",
|
||||||
"string": "by"
|
"string": "by"
|
||||||
},
|
},
|
||||||
|
"channel hidden": {
|
||||||
|
"context": "channel publication status",
|
||||||
|
"string": "Hidden"
|
||||||
|
},
|
||||||
|
"channel publication date visible since": {
|
||||||
|
"context": "channel publication date",
|
||||||
|
"string": "Visible since {date}"
|
||||||
|
},
|
||||||
|
"channel publication date will become available": {
|
||||||
|
"context": "channel publication date",
|
||||||
|
"string": "Will become available on {date}"
|
||||||
|
},
|
||||||
|
"channel visible": {
|
||||||
|
"context": "channel publication status",
|
||||||
|
"string": "Visible"
|
||||||
|
},
|
||||||
"configurationMenuAttributes": {
|
"configurationMenuAttributes": {
|
||||||
"string": "Determine attributes used to create product types"
|
"string": "Determine attributes used to create product types"
|
||||||
},
|
},
|
||||||
|
@ -275,6 +291,10 @@
|
||||||
"context": "new discount label",
|
"context": "new discount label",
|
||||||
"string": "New discount value"
|
"string": "New discount value"
|
||||||
},
|
},
|
||||||
|
"not published channel": {
|
||||||
|
"context": "product label",
|
||||||
|
"string": "Not published"
|
||||||
|
},
|
||||||
"orderCustomerBillingAddressNotSet": {
|
"orderCustomerBillingAddressNotSet": {
|
||||||
"context": "no address is set in draft order",
|
"context": "no address is set in draft order",
|
||||||
"string": "Not set"
|
"string": "Not set"
|
||||||
|
@ -302,6 +322,10 @@
|
||||||
"pageTypeInputLabel": {
|
"pageTypeInputLabel": {
|
||||||
"string": "Select content type"
|
"string": "Select content type"
|
||||||
},
|
},
|
||||||
|
"product available for purchase": {
|
||||||
|
"context": "product availability",
|
||||||
|
"string": "Available for purchase"
|
||||||
|
},
|
||||||
"productExportFieldCategory": {
|
"productExportFieldCategory": {
|
||||||
"context": "product field",
|
"context": "product field",
|
||||||
"string": "Category"
|
"string": "Category"
|
||||||
|
@ -396,6 +420,10 @@
|
||||||
"context": "no warehouses info",
|
"context": "no warehouses info",
|
||||||
"string": "There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>"
|
"string": "There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>"
|
||||||
},
|
},
|
||||||
|
"published channel": {
|
||||||
|
"context": "product label",
|
||||||
|
"string": "Published"
|
||||||
|
},
|
||||||
"saleDetailsPageCategoriesQuantity": {
|
"saleDetailsPageCategoriesQuantity": {
|
||||||
"context": "number of categories",
|
"context": "number of categories",
|
||||||
"string": "Categories ({quantity})"
|
"string": "Categories ({quantity})"
|
||||||
|
@ -420,10 +448,18 @@
|
||||||
"context": "unassign product from sale, button",
|
"context": "unassign product from sale, button",
|
||||||
"string": "Unassign"
|
"string": "Unassign"
|
||||||
},
|
},
|
||||||
|
"set availability date": {
|
||||||
|
"context": "product availability date label",
|
||||||
|
"string": "Set availability date"
|
||||||
|
},
|
||||||
"shipment refund title": {
|
"shipment refund title": {
|
||||||
"context": "shipment refund title",
|
"context": "shipment refund title",
|
||||||
"string": "Shipment was refunded"
|
"string": "Shipment was refunded"
|
||||||
},
|
},
|
||||||
|
"shippingZoneChannels_dot_autocomplete_dot_label": {
|
||||||
|
"context": "ChannelsSection select field label",
|
||||||
|
"string": "Channel"
|
||||||
|
},
|
||||||
"shippingZoneDetailsDialogsDeleteShippingMethod": {
|
"shippingZoneDetailsDialogsDeleteShippingMethod": {
|
||||||
"context": "delete shipping method",
|
"context": "delete shipping method",
|
||||||
"string": "Are you sure you want to delete {name}?"
|
"string": "Are you sure you want to delete {name}?"
|
||||||
|
@ -433,7 +469,7 @@
|
||||||
"string": "Are you sure you want to delete {name}?"
|
"string": "Are you sure you want to delete {name}?"
|
||||||
},
|
},
|
||||||
"shippingZoneWarehouses_dot_autocomplete_dot_label": {
|
"shippingZoneWarehouses_dot_autocomplete_dot_label": {
|
||||||
"context": "autocomplete select label",
|
"context": "WarehousesSection select field label",
|
||||||
"string": "Warehouse"
|
"string": "Warehouse"
|
||||||
},
|
},
|
||||||
"src_dot_accept": {
|
"src_dot_accept": {
|
||||||
|
@ -1283,6 +1319,22 @@
|
||||||
"context": "channels section name",
|
"context": "channels section name",
|
||||||
"string": "Channels"
|
"string": "Channels"
|
||||||
},
|
},
|
||||||
|
"src_dot_channels_dot_ChannelsWithVariantsAvailabilityCard_dot_allVariantsLabel": {
|
||||||
|
"context": "all variants label",
|
||||||
|
"string": "All variants"
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_ChannelsWithVariantsAvailabilityCard_dot_variantCountLabel": {
|
||||||
|
"context": "variants count label",
|
||||||
|
"string": "{variantsCount} variants"
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_CannotDefineChannelsAvailabilityCard_dot_subtitle": {
|
||||||
|
"context": "CannotDefineChannelsAvailabilityCard subtitle",
|
||||||
|
"string": "You will be able to define availability of product after creating variants."
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_CannotDefineChannelsAvailabilityCard_dot_title": {
|
||||||
|
"context": "CannotDefineChannelsAvailabilityCard title",
|
||||||
|
"string": "Availability"
|
||||||
|
},
|
||||||
"src_dot_channels_dot_components_dot_ChannelDeleteDialog_dot_deleteChannel": {
|
"src_dot_channels_dot_components_dot_ChannelDeleteDialog_dot_deleteChannel": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Delete Channel"
|
"string": "Delete Channel"
|
||||||
|
@ -1363,6 +1415,26 @@
|
||||||
"context": "activate",
|
"context": "activate",
|
||||||
"string": "Activate"
|
"string": "Activate"
|
||||||
},
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_ChannelsWithVariantsAvailabilityDialog_dot_title": {
|
||||||
|
"context": "channels variants availability dialog title",
|
||||||
|
"string": "Manage Channels"
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_ChannelsWithVariantsAvailabilityDialog_dot_variantsSelectedLabel": {
|
||||||
|
"context": "variants selected label",
|
||||||
|
"string": "{variantsAmount} variants selected"
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_ShippingZonesCard_dot_addZoneTitle": {
|
||||||
|
"context": "add shipping zone title",
|
||||||
|
"string": "Add Shipping Zones"
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_ShippingZonesCard_dot_subtitle": {
|
||||||
|
"context": "card subtitle",
|
||||||
|
"string": "Select Shipping Zones that will be supplied via this channel. You can assign Shipping Zones to multiple channels."
|
||||||
|
},
|
||||||
|
"src_dot_channels_dot_components_dot_ShippingZonesCard_dot_title": {
|
||||||
|
"context": "title",
|
||||||
|
"string": "{zonesCount} Shipping Zones"
|
||||||
|
},
|
||||||
"src_dot_channels_dot_pages_dot_ChannelsListPage_dot_2754800034": {
|
"src_dot_channels_dot_pages_dot_ChannelsListPage_dot_2754800034": {
|
||||||
"context": "alert",
|
"context": "alert",
|
||||||
"string": "Channel limit reached"
|
"string": "Channel limit reached"
|
||||||
|
@ -1703,46 +1775,43 @@
|
||||||
"src_dot_components_dot_AutocompleteSelectMenu_dot_2332404293": {
|
"src_dot_components_dot_AutocompleteSelectMenu_dot_2332404293": {
|
||||||
"string": "No results"
|
"string": "No results"
|
||||||
},
|
},
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_1306298759": {
|
|
||||||
"context": "product availability",
|
|
||||||
"string": "Available for purchase"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_2162667201": {
|
|
||||||
"context": "channel publication date",
|
|
||||||
"string": "Visible since {date}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_2232321263": {
|
|
||||||
"context": "product publication date label",
|
|
||||||
"string": "will become published on {date}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_2938074852": {
|
|
||||||
"context": "product available for purchase date",
|
|
||||||
"string": "will become available on {date}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_3285520461": {
|
|
||||||
"context": "channel publication date",
|
|
||||||
"string": "Will become available on {date}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_570524410": {
|
|
||||||
"context": "product availability date label",
|
|
||||||
"string": "Set availability date"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_643174786": {
|
|
||||||
"context": "channel publication status",
|
|
||||||
"string": "Visible"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_77815154": {
|
|
||||||
"context": "channel publication status",
|
|
||||||
"string": "Hidden"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_AvailabilityCard_dot_825317195": {
|
|
||||||
"context": "product unavailability",
|
|
||||||
"string": "Unavailable for purchase"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_BulkAttributeUnassignDialog_dot_3177750460": {
|
"src_dot_components_dot_BulkAttributeUnassignDialog_dot_3177750460": {
|
||||||
"context": "unassign multiple attributes from item",
|
"context": "unassign multiple attributes from item",
|
||||||
"string": "{counter,plural,one{Are you sure you want to unassign this attribute from {itemTypeName}?} other{Are you sure you want to unassign {attributeQuantity} attributes from {itemTypeName}?}}"
|
"string": "{counter,plural,one{Are you sure you want to unassign this attribute from {itemTypeName}?} other{Are you sure you want to unassign {attributeQuantity} attributes from {itemTypeName}?}}"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_3326160357": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Availability"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_370220662": {
|
||||||
|
"context": "channels availability text",
|
||||||
|
"string": "Available at {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_4037103586": {
|
||||||
|
"context": "section header button",
|
||||||
|
"string": "Manage"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_1311467573": {
|
||||||
|
"string": "Show in product listings"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_1815688500": {
|
||||||
|
"context": "date",
|
||||||
|
"string": "since {date}"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_2060790769": {
|
||||||
|
"context": "publish on date",
|
||||||
|
"string": "Publish on"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_2264302389": {
|
||||||
|
"string": "Disabling this checkbox will remove product from search and category pages. It will be available on collection pages."
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_2699516026": {
|
||||||
|
"context": "available on date",
|
||||||
|
"string": "Set available on"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityCard_dot_Channel_dot_292404896": {
|
||||||
|
"string": "Set publication date"
|
||||||
|
},
|
||||||
"src_dot_components_dot_ChannelsAvailabilityContent_dot_1528830621": {
|
"src_dot_components_dot_ChannelsAvailabilityContent_dot_1528830621": {
|
||||||
"string": "Select channels you want for {contentType} to be available on"
|
"string": "Select channels you want for {contentType} to be available on"
|
||||||
},
|
},
|
||||||
|
@ -1758,6 +1827,25 @@
|
||||||
"src_dot_components_dot_ChannelsAvailabilityContent_dot_4243012684": {
|
"src_dot_components_dot_ChannelsAvailabilityContent_dot_4243012684": {
|
||||||
"string": "Search through channels"
|
"string": "Search through channels"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityDialogWrapper_dot_4243012684": {
|
||||||
|
"string": "Search through channels"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityDialogWrapper_dot_channelsAlphabeticallyTitle": {
|
||||||
|
"context": "channels alphabetically title",
|
||||||
|
"string": "Channels from A to Z"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityDialogWrapper_dot_notFoundTitle": {
|
||||||
|
"context": "no channels found title",
|
||||||
|
"string": "No Channels Found"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityDialogWrapper_dot_selectAllChannelsLabel": {
|
||||||
|
"context": "select all channels label",
|
||||||
|
"string": "Select All Channels"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_ChannelsAvailabilityDialogWrapper_dot_selectTitle": {
|
||||||
|
"context": "select title",
|
||||||
|
"string": "Select channels you want for {contentType} to be available on"
|
||||||
|
},
|
||||||
"src_dot_components_dot_ChannelsAvailabilityDropdown_dot_1043589445": {
|
"src_dot_components_dot_ChannelsAvailabilityDropdown_dot_1043589445": {
|
||||||
"context": "product channel publication status",
|
"context": "product channel publication status",
|
||||||
"string": "hidden"
|
"string": "hidden"
|
||||||
|
@ -1778,39 +1866,6 @@
|
||||||
"context": "product status",
|
"context": "product status",
|
||||||
"string": "Available in {count} out of {allCount, plural, one {# channel} other {# channels}}"
|
"string": "Available in {count} out of {allCount, plural, one {# channel} other {# channels}}"
|
||||||
},
|
},
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_1311467573": {
|
|
||||||
"string": "Show in product listings"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_1815688500": {
|
|
||||||
"context": "date",
|
|
||||||
"string": "since {date}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_2060790769": {
|
|
||||||
"context": "publish on date",
|
|
||||||
"string": "Publish on"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_2264302389": {
|
|
||||||
"string": "Disabling this checkbox will remove product from search and category pages. It will be available on collection pages."
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_2699516026": {
|
|
||||||
"context": "available on date",
|
|
||||||
"string": "Set available on"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_292404896": {
|
|
||||||
"string": "Set publication date"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_3326160357": {
|
|
||||||
"context": "section header",
|
|
||||||
"string": "Availability"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_370220662": {
|
|
||||||
"context": "channels availability text",
|
|
||||||
"string": "Available at {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ChannelsAvailability_dot_4037103586": {
|
|
||||||
"context": "section header button",
|
|
||||||
"string": "Manage"
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_ColumnPicker_dot_1483881697": {
|
"src_dot_components_dot_ColumnPicker_dot_1483881697": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Reset"
|
"string": "Reset"
|
||||||
|
@ -4955,9 +5010,6 @@
|
||||||
"context": "there are more elements of list that are hidden",
|
"context": "there are more elements of list that are hidden",
|
||||||
"string": "and {number} more"
|
"string": "and {number} more"
|
||||||
},
|
},
|
||||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1583816707": {
|
|
||||||
"string": "Add all channels"
|
|
||||||
},
|
|
||||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1890035856": {
|
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1890035856": {
|
||||||
"context": "informations about product organization, header",
|
"context": "informations about product organization, header",
|
||||||
"string": "Product Organization"
|
"string": "Product Organization"
|
||||||
|
@ -5564,28 +5616,28 @@
|
||||||
"src_dot_products_dot_views_dot_ProductList_dot_44832327": {
|
"src_dot_products_dot_views_dot_ProductList_dot_44832327": {
|
||||||
"string": "We are currently exporting your requested CSV. As soon as it is available it will be sent to your email address"
|
"string": "We are currently exporting your requested CSV. As soon as it is available it will be sent to your email address"
|
||||||
},
|
},
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_1177237881": {
|
|
||||||
"context": "dialog content",
|
|
||||||
"string": "{counter,plural,one{Are you sure you want to delete this variant?} other{Are you sure you want to delete {displayQuantity} variants?}}"
|
|
||||||
},
|
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_1454532689": {
|
|
||||||
"context": "dialog header",
|
|
||||||
"string": "Delete Product Variants"
|
|
||||||
},
|
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_2297471173": {
|
|
||||||
"context": "delete product",
|
|
||||||
"string": "Are you sure you want to delete {name}?"
|
|
||||||
},
|
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_3423943948": {
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_3423943948": {
|
||||||
"string": "Manage Products Channel Availability"
|
"string": "Manage Products Channel Availability"
|
||||||
},
|
},
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_4108890645": {
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_4108890645": {
|
||||||
"string": "Product removed"
|
"string": "Product removed"
|
||||||
},
|
},
|
||||||
"src_dot_products_dot_views_dot_ProductUpdate_dot_879305849": {
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_deleteProductDialogSubtitle": {
|
||||||
"context": "dialog header",
|
"context": "delete product dialog subtitle",
|
||||||
|
"string": "Are you sure you want to delete {name}?"
|
||||||
|
},
|
||||||
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_deleteProductDialogTitle": {
|
||||||
|
"context": "delete product dialog title",
|
||||||
"string": "Delete Product"
|
"string": "Delete Product"
|
||||||
},
|
},
|
||||||
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_deleteVariantDialogSubtitle": {
|
||||||
|
"context": "delete variant dialog subtitle",
|
||||||
|
"string": "{counter,plural,one{Are you sure you want to delete this variant?} other{Are you sure you want to delete {displayQuantity} variants?}}"
|
||||||
|
},
|
||||||
|
"src_dot_products_dot_views_dot_ProductUpdate_dot_deleteVariantDialogTitle": {
|
||||||
|
"context": "delete variant dialog title",
|
||||||
|
"string": "Delete Product Variants"
|
||||||
|
},
|
||||||
"src_dot_products_dot_views_dot_ProductVariantCreator_dot_2292700443": {
|
"src_dot_products_dot_views_dot_ProductVariantCreator_dot_2292700443": {
|
||||||
"context": "success message",
|
"context": "success message",
|
||||||
"string": "Successfully created variants"
|
"string": "Successfully created variants"
|
||||||
|
@ -5940,20 +5992,21 @@
|
||||||
"context": "shipping method name",
|
"context": "shipping method name",
|
||||||
"string": "Name"
|
"string": "Name"
|
||||||
},
|
},
|
||||||
"src_dot_shipping_dot_components_dot_ShippingZoneWarehouses_dot_1221560277": {
|
"src_dot_shipping_dot_components_dot_ShippingZoneSettingsCard_dot_selectFieldAddText": {
|
||||||
"context": "section header",
|
"context": "WarehousesSection select field add text",
|
||||||
"string": "Warehouse"
|
|
||||||
},
|
|
||||||
"src_dot_shipping_dot_components_dot_ShippingZoneWarehouses_dot_2304484478": {
|
|
||||||
"context": "button",
|
|
||||||
"string": "Add New Warehouse"
|
"string": "Add New Warehouse"
|
||||||
},
|
},
|
||||||
"src_dot_shipping_dot_components_dot_ShippingZoneWarehouses_dot_3852102652": {
|
"src_dot_shipping_dot_components_dot_ShippingZoneSettingsCard_dot_selectFieldPlaceholder": {
|
||||||
|
"context": "WarehousesSection select field placeholder",
|
||||||
|
"string": "Select Warehouse"
|
||||||
|
},
|
||||||
|
"src_dot_shipping_dot_components_dot_ShippingZoneSettingsCard_dot_subtitle": {
|
||||||
|
"context": "WarehousesSection subtitle",
|
||||||
"string": "Select warehouse from which you will ship products for this shipping zone. This warehouse address will also be used to calculate taxes."
|
"string": "Select warehouse from which you will ship products for this shipping zone. This warehouse address will also be used to calculate taxes."
|
||||||
},
|
},
|
||||||
"src_dot_shipping_dot_components_dot_ShippingZoneWarehouses_dot_46197273": {
|
"src_dot_shipping_dot_components_dot_ShippingZoneSettingsCard_dot_title": {
|
||||||
"context": "input placeholder",
|
"context": "ShippingZoneSettingsCard title",
|
||||||
"string": "Select Warehouse"
|
"string": "Settings"
|
||||||
},
|
},
|
||||||
"src_dot_shipping_dot_components_dot_ShippingZonesListPage_dot_1325966144": {
|
"src_dot_shipping_dot_components_dot_ShippingZonesListPage_dot_1325966144": {
|
||||||
"context": "header",
|
"context": "header",
|
||||||
|
@ -6985,6 +7038,10 @@
|
||||||
"context": "event",
|
"context": "event",
|
||||||
"string": "Customer updated"
|
"string": "Customer updated"
|
||||||
},
|
},
|
||||||
|
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1089569085": {
|
||||||
|
"context": "webhook",
|
||||||
|
"string": "Secret Key"
|
||||||
|
},
|
||||||
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1690209105": {
|
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1690209105": {
|
||||||
"context": "webhook",
|
"context": "webhook",
|
||||||
"string": "Target URL"
|
"string": "Target URL"
|
||||||
|
@ -7005,10 +7062,6 @@
|
||||||
"context": "webhook secret key help text",
|
"context": "webhook secret key help text",
|
||||||
"string": "secret key is used to create a hash signature with each payload. *optional field"
|
"string": "secret key is used to create a hash signature with each payload. *optional field"
|
||||||
},
|
},
|
||||||
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_3995417850": {
|
|
||||||
"context": "webhook",
|
|
||||||
"string": "Secret Key"
|
|
||||||
},
|
|
||||||
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_4194304040": {
|
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_4194304040": {
|
||||||
"context": "webhook specific information",
|
"context": "webhook specific information",
|
||||||
"string": "Webhook specific information"
|
"string": "Webhook specific information"
|
||||||
|
@ -7074,6 +7127,10 @@
|
||||||
"context": "transaction reference subtitle",
|
"context": "transaction reference subtitle",
|
||||||
"string": "Transaction reference"
|
"string": "Transaction reference"
|
||||||
},
|
},
|
||||||
|
"unavailable for purchase": {
|
||||||
|
"context": "product unavailability",
|
||||||
|
"string": "Unavailable for purchase"
|
||||||
|
},
|
||||||
"voucherDetailsUnassignCategory": {
|
"voucherDetailsUnassignCategory": {
|
||||||
"context": "unassign category from voucher, button",
|
"context": "unassign category from voucher, button",
|
||||||
"string": "Unassign"
|
"string": "Unassign"
|
||||||
|
@ -7085,5 +7142,13 @@
|
||||||
"voucherDetailsUnassignProduct": {
|
"voucherDetailsUnassignProduct": {
|
||||||
"context": "unassign product from voucher, button",
|
"context": "unassign product from voucher, button",
|
||||||
"string": "Unassign"
|
"string": "Unassign"
|
||||||
|
},
|
||||||
|
"will become available on": {
|
||||||
|
"context": "product available for purchase date",
|
||||||
|
"string": "will become available on {date}"
|
||||||
|
},
|
||||||
|
"will become published on": {
|
||||||
|
"context": "product publication date label",
|
||||||
|
"string": "will become published on {date}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -86,7 +86,6 @@ input AccountInput {
|
||||||
lastName: String
|
lastName: String
|
||||||
defaultBillingAddress: AddressInput
|
defaultBillingAddress: AddressInput
|
||||||
defaultShippingAddress: AddressInput
|
defaultShippingAddress: AddressInput
|
||||||
languageCode: LanguageCodeEnum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountRegister {
|
type AccountRegister {
|
||||||
|
@ -103,7 +102,6 @@ input AccountRegisterInput {
|
||||||
email: String!
|
email: String!
|
||||||
password: String!
|
password: String!
|
||||||
redirectUrl: String
|
redirectUrl: String
|
||||||
languageCode: LanguageCodeEnum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountRequestDeletion {
|
type AccountRequestDeletion {
|
||||||
|
@ -575,7 +573,6 @@ input AttributeFilterInput {
|
||||||
filterableInStorefront: Boolean
|
filterableInStorefront: Boolean
|
||||||
filterableInDashboard: Boolean
|
filterableInDashboard: Boolean
|
||||||
availableInGrid: Boolean
|
availableInGrid: Boolean
|
||||||
metadata: [MetadataInput]
|
|
||||||
search: String
|
search: String
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
type: AttributeTypeEnum
|
type: AttributeTypeEnum
|
||||||
|
@ -678,7 +675,6 @@ type AttributeValue implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
value: String
|
|
||||||
translation(languageCode: LanguageCodeEnum!): AttributeValueTranslation
|
translation(languageCode: LanguageCodeEnum!): AttributeValueTranslation
|
||||||
inputType: AttributeInputTypeEnum
|
inputType: AttributeInputTypeEnum
|
||||||
reference: ID
|
reference: ID
|
||||||
|
@ -879,7 +875,6 @@ type CategoryDelete {
|
||||||
|
|
||||||
input CategoryFilterInput {
|
input CategoryFilterInput {
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,6 +951,7 @@ type Channel implements Node {
|
||||||
slug: String!
|
slug: String!
|
||||||
currencyCode: String!
|
currencyCode: String!
|
||||||
hasOrders: Boolean!
|
hasOrders: Boolean!
|
||||||
|
shippingZones: [ShippingZone!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelActivate {
|
type ChannelActivate {
|
||||||
|
@ -981,6 +977,7 @@ input ChannelCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
slug: String!
|
slug: String!
|
||||||
currencyCode: String!
|
currencyCode: String!
|
||||||
|
addShippingZones: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelDeactivate {
|
type ChannelDeactivate {
|
||||||
|
@ -1009,6 +1006,7 @@ type ChannelError {
|
||||||
field: String
|
field: String
|
||||||
message: String
|
message: String
|
||||||
code: ChannelErrorCode!
|
code: ChannelErrorCode!
|
||||||
|
shippingZones: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelErrorCode {
|
enum ChannelErrorCode {
|
||||||
|
@ -1021,6 +1019,7 @@ enum ChannelErrorCode {
|
||||||
CHANNEL_TARGET_ID_MUST_BE_DIFFERENT
|
CHANNEL_TARGET_ID_MUST_BE_DIFFERENT
|
||||||
CHANNELS_CURRENCY_MUST_BE_THE_SAME
|
CHANNELS_CURRENCY_MUST_BE_THE_SAME
|
||||||
CHANNEL_WITH_ORDERS
|
CHANNEL_WITH_ORDERS
|
||||||
|
DUPLICATED_INPUT_ITEM
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelUpdate {
|
type ChannelUpdate {
|
||||||
|
@ -1036,6 +1035,8 @@ input ChannelUpdateInput {
|
||||||
isActive: Boolean
|
isActive: Boolean
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
|
addShippingZones: [ID!]
|
||||||
|
removeShippingZones: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Checkout implements Node & ObjectWithMetadata {
|
type Checkout implements Node & ObjectWithMetadata {
|
||||||
|
@ -1065,7 +1066,6 @@ type Checkout implements Node & ObjectWithMetadata {
|
||||||
subtotalPrice: TaxedMoney
|
subtotalPrice: TaxedMoney
|
||||||
token: UUID!
|
token: UUID!
|
||||||
totalPrice: TaxedMoney
|
totalPrice: TaxedMoney
|
||||||
languageCode: LanguageCodeEnum!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutAddPromoCode {
|
type CheckoutAddPromoCode {
|
||||||
|
@ -1124,7 +1124,6 @@ input CheckoutCreateInput {
|
||||||
email: String
|
email: String
|
||||||
shippingAddress: AddressInput
|
shippingAddress: AddressInput
|
||||||
billingAddress: AddressInput
|
billingAddress: AddressInput
|
||||||
languageCode: LanguageCodeEnum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutCustomerAttach {
|
type CheckoutCustomerAttach {
|
||||||
|
@ -1184,6 +1183,7 @@ enum CheckoutErrorCode {
|
||||||
ZERO_QUANTITY
|
ZERO_QUANTITY
|
||||||
MISSING_CHANNEL_SLUG
|
MISSING_CHANNEL_SLUG
|
||||||
CHANNEL_INACTIVE
|
CHANNEL_INACTIVE
|
||||||
|
UNAVAILABLE_VARIANT_IN_CHANNEL
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutLanguageCodeUpdate {
|
type CheckoutLanguageCodeUpdate {
|
||||||
|
@ -1423,7 +1423,6 @@ enum CollectionErrorCode {
|
||||||
input CollectionFilterInput {
|
input CollectionFilterInput {
|
||||||
published: CollectionPublished
|
published: CollectionPublished
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
channel: String
|
channel: String
|
||||||
}
|
}
|
||||||
|
@ -1908,7 +1907,6 @@ input CustomerInput {
|
||||||
email: String
|
email: String
|
||||||
isActive: Boolean
|
isActive: Boolean
|
||||||
note: String
|
note: String
|
||||||
languageCode: LanguageCodeEnum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerUpdate {
|
type CustomerUpdate {
|
||||||
|
@ -2628,6 +2626,13 @@ type InvoiceRequestDelete {
|
||||||
invoice: Invoice
|
invoice: Invoice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InvoiceSendEmail {
|
||||||
|
errors: [Error!]!
|
||||||
|
@deprecated(
|
||||||
|
reason: "Use typed errors with error codes. This field will be removed after 2020-07-31."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type InvoiceSendNotification {
|
type InvoiceSendNotification {
|
||||||
errors: [Error!]!
|
errors: [Error!]!
|
||||||
@deprecated(
|
@deprecated(
|
||||||
|
@ -2826,7 +2831,6 @@ enum MenuErrorCode {
|
||||||
input MenuFilterInput {
|
input MenuFilterInput {
|
||||||
search: String
|
search: String
|
||||||
slug: [String]
|
slug: [String]
|
||||||
metadata: [MetadataInput]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input MenuInput {
|
input MenuInput {
|
||||||
|
@ -2900,7 +2904,6 @@ type MenuItemDelete {
|
||||||
|
|
||||||
input MenuItemFilterInput {
|
input MenuItemFilterInput {
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input MenuItemInput {
|
input MenuItemInput {
|
||||||
|
@ -3341,7 +3344,7 @@ type Mutation {
|
||||||
invoiceCreate(input: InvoiceCreateInput!, orderId: ID!): InvoiceCreate
|
invoiceCreate(input: InvoiceCreateInput!, orderId: ID!): InvoiceCreate
|
||||||
invoiceDelete(id: ID!): InvoiceDelete
|
invoiceDelete(id: ID!): InvoiceDelete
|
||||||
invoiceUpdate(id: ID!, input: UpdateInvoiceInput!): InvoiceUpdate
|
invoiceUpdate(id: ID!, input: UpdateInvoiceInput!): InvoiceUpdate
|
||||||
invoiceSendNotification(id: ID!): InvoiceSendNotification
|
invoiceSendEmail(id: ID!): InvoiceSendEmail
|
||||||
giftCardActivate(id: ID!): GiftCardActivate
|
giftCardActivate(id: ID!): GiftCardActivate
|
||||||
giftCardCreate(input: GiftCardCreateInput!): GiftCardCreate
|
giftCardCreate(input: GiftCardCreateInput!): GiftCardCreate
|
||||||
giftCardDeactivate(id: ID!): GiftCardDeactivate
|
giftCardDeactivate(id: ID!): GiftCardDeactivate
|
||||||
|
@ -3366,6 +3369,7 @@ type Mutation {
|
||||||
voucherDelete(id: ID!): VoucherDelete
|
voucherDelete(id: ID!): VoucherDelete
|
||||||
voucherBulkDelete(ids: [ID]!): VoucherBulkDelete
|
voucherBulkDelete(ids: [ID]!): VoucherBulkDelete
|
||||||
voucherUpdate(id: ID!, input: VoucherInput!): VoucherUpdate
|
voucherUpdate(id: ID!, input: VoucherInput!): VoucherUpdate
|
||||||
|
invoiceSendNotification(id: ID!): InvoiceSendNotification
|
||||||
voucherCataloguesAdd(id: ID!, input: CatalogueInput!): VoucherAddCatalogues
|
voucherCataloguesAdd(id: ID!, input: CatalogueInput!): VoucherAddCatalogues
|
||||||
voucherCataloguesRemove(
|
voucherCataloguesRemove(
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -3763,7 +3767,6 @@ input OrderDraftFilterInput {
|
||||||
customer: String
|
customer: String
|
||||||
created: DateRangeInput
|
created: DateRangeInput
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
channels: [ID]
|
channels: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3921,7 +3924,6 @@ input OrderFilterInput {
|
||||||
customer: String
|
customer: String
|
||||||
created: DateRangeInput
|
created: DateRangeInput
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
channels: [ID]
|
channels: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4303,7 +4305,6 @@ enum PageErrorCode {
|
||||||
|
|
||||||
input PageFilterInput {
|
input PageFilterInput {
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageInfo {
|
type PageInfo {
|
||||||
|
@ -4579,10 +4580,6 @@ enum PaymentErrorCode {
|
||||||
NOT_SUPPORTED_GATEWAY
|
NOT_SUPPORTED_GATEWAY
|
||||||
}
|
}
|
||||||
|
|
||||||
input PaymentFilterInput {
|
|
||||||
checkouts: [ID]
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentGateway {
|
type PaymentGateway {
|
||||||
name: String!
|
name: String!
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -4853,6 +4850,10 @@ type Product implements Node & ObjectWithMetadata {
|
||||||
isAvailableForPurchase: Boolean
|
isAvailableForPurchase: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input PaymentFilterInput {
|
||||||
|
checkouts: [ID]
|
||||||
|
}
|
||||||
|
|
||||||
type ProductAttributeAssign {
|
type ProductAttributeAssign {
|
||||||
errors: [Error!]!
|
errors: [Error!]!
|
||||||
@deprecated(
|
@deprecated(
|
||||||
|
@ -4911,6 +4912,8 @@ input ProductChannelListingAddInput {
|
||||||
visibleInListings: Boolean
|
visibleInListings: Boolean
|
||||||
isAvailableForPurchase: Boolean
|
isAvailableForPurchase: Boolean
|
||||||
availableForPurchaseDate: Date
|
availableForPurchaseDate: Date
|
||||||
|
addVariants: [ID!]
|
||||||
|
removeVariants: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductChannelListingError {
|
type ProductChannelListingError {
|
||||||
|
@ -4920,6 +4923,7 @@ type ProductChannelListingError {
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
values: [ID!]
|
values: [ID!]
|
||||||
channels: [ID!]
|
channels: [ID!]
|
||||||
|
variants: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductChannelListingUpdate {
|
type ProductChannelListingUpdate {
|
||||||
|
@ -4932,7 +4936,7 @@ type ProductChannelListingUpdate {
|
||||||
}
|
}
|
||||||
|
|
||||||
input ProductChannelListingUpdateInput {
|
input ProductChannelListingUpdateInput {
|
||||||
addChannels: [ProductChannelListingAddInput!]
|
updateChannels: [ProductChannelListingAddInput!]
|
||||||
removeChannels: [ID!]
|
removeChannels: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5033,7 +5037,6 @@ input ProductFilterInput {
|
||||||
productType: ID
|
productType: ID
|
||||||
stocks: ProductStockFilterInput
|
stocks: ProductStockFilterInput
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
|
||||||
price: PriceRangeInput
|
price: PriceRangeInput
|
||||||
minimalPrice: PriceRangeInput
|
minimalPrice: PriceRangeInput
|
||||||
productTypes: [ID]
|
productTypes: [ID]
|
||||||
|
@ -5299,7 +5302,6 @@ input ProductTypeFilterInput {
|
||||||
search: String
|
search: String
|
||||||
configurable: ProductTypeConfigurable
|
configurable: ProductTypeConfigurable
|
||||||
productType: ProductTypeEnum
|
productType: ProductTypeEnum
|
||||||
metadata: [MetadataInput]
|
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5472,7 +5474,6 @@ type ProductVariantDelete {
|
||||||
input ProductVariantFilterInput {
|
input ProductVariantFilterInput {
|
||||||
search: String
|
search: String
|
||||||
sku: [String]
|
sku: [String]
|
||||||
metadata: [MetadataInput]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input ProductVariantInput {
|
input ProductVariantInput {
|
||||||
|
@ -5606,6 +5607,7 @@ type Query {
|
||||||
orderSettings: OrderSettings
|
orderSettings: OrderSettings
|
||||||
shippingZone(id: ID!, channel: String): ShippingZone
|
shippingZone(id: ID!, channel: String): ShippingZone
|
||||||
shippingZones(
|
shippingZones(
|
||||||
|
filter: ShippingZoneFilterInput
|
||||||
channel: String
|
channel: String
|
||||||
before: String
|
before: String
|
||||||
after: String
|
after: String
|
||||||
|
@ -6312,7 +6314,8 @@ type ShippingZone implements Node & ObjectWithMetadata {
|
||||||
priceRange: MoneyRange
|
priceRange: MoneyRange
|
||||||
countries: [CountryDisplay]
|
countries: [CountryDisplay]
|
||||||
shippingMethods: [ShippingMethod]
|
shippingMethods: [ShippingMethod]
|
||||||
warehouses: [Warehouse]
|
warehouses: [Warehouse!]!
|
||||||
|
channels: [Channel!]!
|
||||||
description: String
|
description: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6351,6 +6354,7 @@ input ShippingZoneCreateInput {
|
||||||
countries: [String]
|
countries: [String]
|
||||||
default: Boolean
|
default: Boolean
|
||||||
addWarehouses: [ID]
|
addWarehouses: [ID]
|
||||||
|
addChannels: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShippingZoneDelete {
|
type ShippingZoneDelete {
|
||||||
|
@ -6362,6 +6366,10 @@ type ShippingZoneDelete {
|
||||||
shippingZone: ShippingZone
|
shippingZone: ShippingZone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ShippingZoneFilterInput {
|
||||||
|
search: String
|
||||||
|
}
|
||||||
|
|
||||||
type ShippingZoneUpdate {
|
type ShippingZoneUpdate {
|
||||||
errors: [Error!]!
|
errors: [Error!]!
|
||||||
@deprecated(
|
@deprecated(
|
||||||
|
@ -6377,7 +6385,9 @@ input ShippingZoneUpdateInput {
|
||||||
countries: [String]
|
countries: [String]
|
||||||
default: Boolean
|
default: Boolean
|
||||||
addWarehouses: [ID]
|
addWarehouses: [ID]
|
||||||
|
addChannels: [ID!]
|
||||||
removeWarehouses: [ID]
|
removeWarehouses: [ID]
|
||||||
|
removeChannels: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shop {
|
type Shop {
|
||||||
|
@ -6728,11 +6738,23 @@ type Transaction implements Node {
|
||||||
token: String!
|
token: String!
|
||||||
kind: TransactionKind!
|
kind: TransactionKind!
|
||||||
isSuccess: Boolean!
|
isSuccess: Boolean!
|
||||||
error: String
|
error: TransactionError
|
||||||
gatewayResponse: JSONString!
|
|
||||||
amount: Money
|
amount: Money
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TransactionError {
|
||||||
|
TRANSACTIONERROR_INCORRECT_NUMBER
|
||||||
|
TRANSACTIONERROR_INVALID_NUMBER
|
||||||
|
TRANSACTIONERROR_INCORRECT_CVV
|
||||||
|
TRANSACTIONERROR_INVALID_CVV
|
||||||
|
TRANSACTIONERROR_INCORRECT_ZIP
|
||||||
|
TRANSACTIONERROR_INCORRECT_ADDRESS
|
||||||
|
TRANSACTIONERROR_INVALID_EXPIRY_DATE
|
||||||
|
TRANSACTIONERROR_EXPIRED
|
||||||
|
TRANSACTIONERROR_PROCESSING_ERROR
|
||||||
|
TRANSACTIONERROR_DECLINED
|
||||||
|
}
|
||||||
|
|
||||||
enum TransactionKind {
|
enum TransactionKind {
|
||||||
EXTERNAL
|
EXTERNAL
|
||||||
AUTH
|
AUTH
|
||||||
|
@ -6882,7 +6904,6 @@ type User implements Node & ObjectWithMetadata {
|
||||||
avatar(size: Int): Image
|
avatar(size: Int): Image
|
||||||
events: [CustomerEvent]
|
events: [CustomerEvent]
|
||||||
storedPaymentSources: [PaymentSource]
|
storedPaymentSources: [PaymentSource]
|
||||||
languageCode: LanguageCodeEnum!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserAvatarDelete {
|
type UserAvatarDelete {
|
||||||
|
@ -6931,7 +6952,6 @@ input UserCreateInput {
|
||||||
email: String
|
email: String
|
||||||
isActive: Boolean
|
isActive: Boolean
|
||||||
note: String
|
note: String
|
||||||
languageCode: LanguageCodeEnum
|
|
||||||
redirectUrl: String
|
redirectUrl: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7447,7 +7467,6 @@ enum WebhookSampleEventTypeEnum {
|
||||||
CHECKOUT_CREATED
|
CHECKOUT_CREATED
|
||||||
CHECKOUT_UPDATED
|
CHECKOUT_UPDATED
|
||||||
FULFILLMENT_CREATED
|
FULFILLMENT_CREATED
|
||||||
NOTIFY_USER
|
|
||||||
PAGE_CREATED
|
PAGE_CREATED
|
||||||
PAGE_UPDATED
|
PAGE_UPDATED
|
||||||
PAGE_DELETED
|
PAGE_DELETED
|
||||||
|
|
|
@ -7,9 +7,8 @@ import Checkbox from "@saleor/components/Checkbox";
|
||||||
import MoneyRange from "@saleor/components/MoneyRange";
|
import MoneyRange from "@saleor/components/MoneyRange";
|
||||||
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 TableCellAvatar, {
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
AVATAR_MARGIN
|
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
||||||
} from "@saleor/components/TableCellAvatar";
|
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import {
|
||||||
|
ExpansionPanel,
|
||||||
|
ExpansionPanelSummary,
|
||||||
|
makeStyles,
|
||||||
|
Typography
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import { Messages } from "@saleor/components/ChannelsAvailabilityCard/types";
|
||||||
|
import IconChevronDown from "@saleor/icons/ChevronDown";
|
||||||
|
import Label from "@saleor/orders/components/OrderHistory/Label";
|
||||||
|
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { ChannelsWithVariantsData } from "@saleor/products/views/ProductUpdate/types";
|
||||||
|
import { areAllChannelVariantsSelected } from "@saleor/products/views/ProductUpdate/utils";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
const useExpanderStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
boxShadow: "none",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
|
||||||
|
"&:before": {
|
||||||
|
content: "none"
|
||||||
|
},
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelWithVariantAvailabilityItemWrapperExpander" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const useSummaryStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
border: "none",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
minHeight: 0,
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
minHeight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
margin: 0,
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelWithVariantAvailabilityItemWrapperSummary" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
() => ({
|
||||||
|
container: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelWithVariantAvailabilityItemWrapper" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
variantCountLabel: {
|
||||||
|
defaultMessage: "{variantsCount} variants",
|
||||||
|
description: "variants count label"
|
||||||
|
},
|
||||||
|
allVariantsLabel: {
|
||||||
|
defaultMessage: "All variants",
|
||||||
|
description: "all variants label"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ChannelAvailabilityItemWrapperProps {
|
||||||
|
variants: ProductDetails_product_variants[];
|
||||||
|
channelId: string;
|
||||||
|
channels: ChannelData[];
|
||||||
|
channelsWithVariantsData: ChannelsWithVariantsData;
|
||||||
|
messages: Messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelWithVariantsAvailabilityItemWrapper: React.FC<ChannelAvailabilityItemWrapperProps> = ({
|
||||||
|
channels,
|
||||||
|
channelsWithVariantsData,
|
||||||
|
channelId,
|
||||||
|
variants,
|
||||||
|
messages: commonChannelMessages,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
const expanderClasses = useExpanderStyles({});
|
||||||
|
const summaryClasses = useSummaryStyles({});
|
||||||
|
const classes = useStyles({});
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const { name } = channels.find(getById(channelId));
|
||||||
|
const { selectedVariantsIds } = channelsWithVariantsData[channelId];
|
||||||
|
|
||||||
|
const variantsCount = selectedVariantsIds.length;
|
||||||
|
|
||||||
|
const variantsLabel = areAllChannelVariantsSelected(variants, {
|
||||||
|
selectedVariantsIds
|
||||||
|
})
|
||||||
|
? messages.allVariantsLabel
|
||||||
|
: messages.variantCountLabel;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExpansionPanel classes={expanderClasses}>
|
||||||
|
<ExpansionPanelSummary
|
||||||
|
expandIcon={<IconChevronDown />}
|
||||||
|
classes={summaryClasses}
|
||||||
|
>
|
||||||
|
<div className={classes.container}>
|
||||||
|
<Typography>{name}</Typography>
|
||||||
|
<Label text={intl.formatMessage(variantsLabel, { variantsCount })} />
|
||||||
|
<Label text={commonChannelMessages.availableDateText} />
|
||||||
|
</div>
|
||||||
|
</ExpansionPanelSummary>
|
||||||
|
{children}
|
||||||
|
</ExpansionPanel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelWithVariantsAvailabilityItemWrapper;
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import CentralPlacementDecorator from "@saleor/storybook/CentralPlacementDecorator";
|
||||||
|
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import ChannelsWithVariantsAvailabilityCard, {
|
||||||
|
ChannelsWithVariantsAvailabilityCardProps
|
||||||
|
} from "./ChannelsWithVariantsAvailabilityCard";
|
||||||
|
|
||||||
|
const props: ChannelsWithVariantsAvailabilityCardProps = {
|
||||||
|
channels: [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: "Channel 1",
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: true,
|
||||||
|
publicationDate: "2020-07-30",
|
||||||
|
availableForPurchase: null,
|
||||||
|
visibleInListings: false,
|
||||||
|
currency: "EUR",
|
||||||
|
variantsIds: ["variantA"],
|
||||||
|
costPrice: "5",
|
||||||
|
price: "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: "Channel 2",
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: true,
|
||||||
|
publicationDate: "2020-07-30",
|
||||||
|
availableForPurchase: null,
|
||||||
|
visibleInListings: false,
|
||||||
|
currency: "EUR",
|
||||||
|
variantsIds: ["variantA"],
|
||||||
|
costPrice: "5",
|
||||||
|
price: "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
name: "Channel 3",
|
||||||
|
isAvailableForPurchase: false,
|
||||||
|
isPublished: true,
|
||||||
|
publicationDate: "2020-07-30",
|
||||||
|
availableForPurchase: null,
|
||||||
|
visibleInListings: false,
|
||||||
|
currency: "EUR",
|
||||||
|
variantsIds: ["variantA"],
|
||||||
|
costPrice: "5",
|
||||||
|
price: "15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "variantA",
|
||||||
|
name: "Variant A",
|
||||||
|
media: [
|
||||||
|
{
|
||||||
|
url:
|
||||||
|
"https://test-envs-stack-testenvsmediabucket050c0d50-qdkqrzgoumxv.s3.amazonaws.com/feature-assing-variants-to-channel-listings/products/saleordemoproduct_fd_juice_06.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "variantB",
|
||||||
|
name: "Variant B",
|
||||||
|
media: [
|
||||||
|
{
|
||||||
|
url:
|
||||||
|
"https://test-envs-stack-testenvsmediabucket050c0d50-qdkqrzgoumxv.s3.amazonaws.com/feature-assing-variants-to-channel-listings/products/saleordemoproduct_fd_juice_05.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "variantC",
|
||||||
|
name: "Variant C",
|
||||||
|
media: []
|
||||||
|
}
|
||||||
|
] as ProductDetails_product_variants[],
|
||||||
|
channelsWithVariantsData: {
|
||||||
|
["1"]: {
|
||||||
|
selectedVariantsIds: ["variantA", "variantB"],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
},
|
||||||
|
["2"]: {
|
||||||
|
selectedVariantsIds: ["variantA", "variantC"],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
},
|
||||||
|
["3"]: {
|
||||||
|
selectedVariantsIds: [],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
hiddenLabel: "Not published",
|
||||||
|
visibleLabel: "Published"
|
||||||
|
},
|
||||||
|
onChange: () => undefined,
|
||||||
|
openModal: () => undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Channels / Channels with variants availability card", module)
|
||||||
|
.addDecorator(CommonDecorator)
|
||||||
|
.addDecorator(CentralPlacementDecorator)
|
||||||
|
.add("default", () => <ChannelsWithVariantsAvailabilityCard {...props} />);
|
|
@ -0,0 +1,101 @@
|
||||||
|
import CannotDefineChannelsAvailabilityCard from "@saleor/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard";
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import ChannelAvailabilityItemContent from "@saleor/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent";
|
||||||
|
import ChannelsAvailabilityCardWrapper, {
|
||||||
|
ChannelsAvailabilityWrapperProps
|
||||||
|
} from "@saleor/components/ChannelsAvailabilityCard/ChannelsAvailabilityCardWrapper";
|
||||||
|
import {
|
||||||
|
ChannelOpts,
|
||||||
|
ChannelsAvailabilityError,
|
||||||
|
Messages
|
||||||
|
} from "@saleor/components/ChannelsAvailabilityCard/types";
|
||||||
|
import { getChannelsAvailabilityMessages } from "@saleor/components/ChannelsAvailabilityCard/utils";
|
||||||
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
|
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { ChannelsWithVariantsData } from "@saleor/products/views/ProductUpdate/types";
|
||||||
|
import {
|
||||||
|
areAnyChannelVariantsSelected,
|
||||||
|
getTotalSelectedChannelsCount
|
||||||
|
} from "@saleor/products/views/ProductUpdate/utils";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import ChannelWithVariantsAvailabilityItemWrapper from "./ChannelWithVariantAvailabilityItemWrapper";
|
||||||
|
|
||||||
|
type CommonChannelsAvailabilityProps = Omit<
|
||||||
|
ChannelsAvailabilityWrapperProps,
|
||||||
|
"children" | "selectedChannelsCount" | "allChannelsCount"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ChannelsWithVariantsAvailabilityCardProps
|
||||||
|
extends CommonChannelsAvailabilityProps {
|
||||||
|
channelsWithVariantsData: ChannelsWithVariantsData;
|
||||||
|
channels: ChannelData[];
|
||||||
|
variants: ProductDetails_product_variants[];
|
||||||
|
errors?: ChannelsAvailabilityError[];
|
||||||
|
messages: Messages;
|
||||||
|
onChange: (id: string, data: ChannelOpts) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelsWithVariantsAvailabilityCard: React.FC<ChannelsWithVariantsAvailabilityCardProps> = ({
|
||||||
|
channels,
|
||||||
|
channelsWithVariantsData,
|
||||||
|
openModal,
|
||||||
|
variants,
|
||||||
|
errors = [],
|
||||||
|
messages,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const localizeDate = useDateLocalize();
|
||||||
|
|
||||||
|
const channelsMessages = getChannelsAvailabilityMessages({
|
||||||
|
messages,
|
||||||
|
channels,
|
||||||
|
intl,
|
||||||
|
localizeDate
|
||||||
|
});
|
||||||
|
|
||||||
|
const allChannelsCount = channels.length;
|
||||||
|
|
||||||
|
const selectedChannelsCount = getTotalSelectedChannelsCount(
|
||||||
|
channelsWithVariantsData
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!variants?.length) {
|
||||||
|
return <CannotDefineChannelsAvailabilityCard />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChannelsAvailabilityCardWrapper
|
||||||
|
selectedChannelsCount={selectedChannelsCount}
|
||||||
|
allChannelsCount={allChannelsCount}
|
||||||
|
openModal={openModal}
|
||||||
|
>
|
||||||
|
{channels
|
||||||
|
.filter(({ id }) =>
|
||||||
|
areAnyChannelVariantsSelected(channelsWithVariantsData[id])
|
||||||
|
)
|
||||||
|
.map(({ id }) => (
|
||||||
|
<ChannelWithVariantsAvailabilityItemWrapper
|
||||||
|
messages={channelsMessages[id]}
|
||||||
|
key={id}
|
||||||
|
channelsWithVariantsData={channelsWithVariantsData}
|
||||||
|
variants={variants}
|
||||||
|
channels={channels}
|
||||||
|
channelId={id}
|
||||||
|
>
|
||||||
|
<ChannelAvailabilityItemContent
|
||||||
|
onChange={onChange}
|
||||||
|
data={channels.find(getById(id))}
|
||||||
|
errors={errors}
|
||||||
|
messages={channelsMessages[id]}
|
||||||
|
/>
|
||||||
|
</ChannelWithVariantsAvailabilityItemWrapper>
|
||||||
|
))}
|
||||||
|
</ChannelsAvailabilityCardWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsWithVariantsAvailabilityCard;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import CentralPlacementDecorator from "@saleor/storybook/CentralPlacementDecorator";
|
||||||
|
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import CannotDefineChannelsAvailabilityCard from "./CannotDefineChannelsAvailabilityCard";
|
||||||
|
|
||||||
|
storiesOf("Channels / Cannot define channels availability card", module)
|
||||||
|
.addDecorator(CommonDecorator)
|
||||||
|
.addDecorator(CentralPlacementDecorator)
|
||||||
|
.add("default", () => <CannotDefineChannelsAvailabilityCard />);
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Card, CardContent } from "@material-ui/core";
|
||||||
|
import React from "react";
|
||||||
|
import { defineMessages, FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import CardTitle from "../../../components/CardTitle";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "Availability",
|
||||||
|
description: "CannotDefineChannelsAvailabilityCard title"
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
defaultMessage:
|
||||||
|
"You will be able to define availability of product after creating variants.",
|
||||||
|
description: "CannotDefineChannelsAvailabilityCard subtitle"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const CannotDefineChannelsAvailabilityCard: React.FC = () => (
|
||||||
|
<Card>
|
||||||
|
<CardTitle title={<FormattedMessage {...messages.title} />} />
|
||||||
|
<CardContent>
|
||||||
|
<FormattedMessage {...messages.subtitle} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CannotDefineChannelsAvailabilityCard;
|
|
@ -8,6 +8,8 @@ import ChannelForm, { ChannelFormProps } from "./ChannelForm";
|
||||||
const props: ChannelFormProps = {
|
const props: ChannelFormProps = {
|
||||||
data: {
|
data: {
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZonesIdsToAdd: [],
|
||||||
|
shippingZonesIdsToRemove: [],
|
||||||
name: "Test",
|
name: "Test",
|
||||||
slug: "test"
|
slug: "test"
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,8 @@ export interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
shippingZonesIdsToAdd: string[];
|
||||||
|
shippingZonesIdsToRemove: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelFormProps {
|
export interface ChannelFormProps {
|
||||||
|
|
11
src/channels/components/ChannelsAvailabilityDialog/utils.ts
Normal file
11
src/channels/components/ChannelsAvailabilityDialog/utils.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { filter } from "fuzzaldrin";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const useChannelsSearch = function<T extends { name: string }>(
|
||||||
|
channels: T[]
|
||||||
|
) {
|
||||||
|
const [query, onQueryChange] = React.useState("");
|
||||||
|
const filteredChannels = filter<T, "name">(channels, query, { key: "name" });
|
||||||
|
|
||||||
|
return { query, onQueryChange, filteredChannels };
|
||||||
|
};
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import ChannelsWithVariantsAvailabilityDialog, {
|
||||||
|
ChannelsAvailabilityDialogProps
|
||||||
|
} from "./ChannelsWithVariantsAvailabilityDialog";
|
||||||
|
|
||||||
|
const props: ChannelsAvailabilityDialogProps = {
|
||||||
|
channels: [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: "Channel 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: "Channel 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
name: "Channel 3"
|
||||||
|
}
|
||||||
|
] as ChannelData[],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "variantA",
|
||||||
|
name: "Variant A",
|
||||||
|
media: [
|
||||||
|
{
|
||||||
|
url:
|
||||||
|
"https://test-envs-stack-testenvsmediabucket050c0d50-qdkqrzgoumxv.s3.amazonaws.com/feature-assing-variants-to-channel-listings/products/saleordemoproduct_fd_juice_06.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "variantB",
|
||||||
|
name: "Variant B",
|
||||||
|
media: [
|
||||||
|
{
|
||||||
|
url:
|
||||||
|
"https://test-envs-stack-testenvsmediabucket050c0d50-qdkqrzgoumxv.s3.amazonaws.com/feature-assing-variants-to-channel-listings/products/saleordemoproduct_fd_juice_05.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "variantC",
|
||||||
|
name: "Variant C",
|
||||||
|
media: []
|
||||||
|
}
|
||||||
|
] as ProductDetails_product_variants[],
|
||||||
|
onChannelsWithVariantsConfirm: () => undefined,
|
||||||
|
addVariantToChannel: () => undefined,
|
||||||
|
removeVariantFromChannel: () => undefined,
|
||||||
|
channelsWithVariantsData: {
|
||||||
|
["1"]: {
|
||||||
|
selectedVariantsIds: ["variantA", "variantB"],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
},
|
||||||
|
["2"]: {
|
||||||
|
selectedVariantsIds: ["variantA", "variantC"],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
},
|
||||||
|
["3"]: {
|
||||||
|
selectedVariantsIds: [],
|
||||||
|
variantsIdsToRemove: [],
|
||||||
|
variantsIdsToAdd: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChannelsAvailiabilityModalClose: () => undefined,
|
||||||
|
isChannelsAvailabilityModalOpen: true,
|
||||||
|
toggleAllChannels: () => undefined,
|
||||||
|
toggleAllChannelVariants: () => () => undefined,
|
||||||
|
haveChannelsWithVariantsDataChanged: true
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Channels / Channels with Variants Availability Dialog", module)
|
||||||
|
.addDecorator(CommonDecorator)
|
||||||
|
.add("default", () => <ChannelsWithVariantsAvailabilityDialog {...props} />);
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { UseChannelsWithProductVariants } from "@saleor/products/views/ProductUpdate/types";
|
||||||
|
import {
|
||||||
|
areAllVariantsAtAllChannelsSelected,
|
||||||
|
areAnyChannelVariantsSelected
|
||||||
|
} from "@saleor/products/views/ProductUpdate/utils";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
import { useChannelsSearch } from "../../../components/ChannelsAvailabilityDialog/utils";
|
||||||
|
import ChannelsAvailabilityContentWrapper from "../../../components/ChannelsAvailabilityDialogWrapper/ChannelsAvailabilityDialogWrapper";
|
||||||
|
import ChannelsWithVariantsAvailabilityDialogContent from "./ChannelsWithVariantsAvailabilityDialogContent";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "Manage Channels",
|
||||||
|
description: "channels variants availability dialog title"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type UseChannelsWithVariantsCommonProps = Omit<
|
||||||
|
UseChannelsWithProductVariants,
|
||||||
|
| "onChannelsAvailiabilityModalOpen"
|
||||||
|
| "setHaveChannelsWithVariantsChanged"
|
||||||
|
| "channelsData"
|
||||||
|
| "setChannelsData"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ChannelsAvailabilityDialogProps
|
||||||
|
extends UseChannelsWithVariantsCommonProps {
|
||||||
|
channels: ChannelData[];
|
||||||
|
contentType?: string;
|
||||||
|
variants: ProductDetails_product_variants[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelsWithVariantsAvailabilityDialog: React.FC<ChannelsAvailabilityDialogProps> = ({
|
||||||
|
channels,
|
||||||
|
contentType,
|
||||||
|
variants,
|
||||||
|
isChannelsAvailabilityModalOpen,
|
||||||
|
toggleAllChannels,
|
||||||
|
channelsWithVariantsData,
|
||||||
|
onChannelsAvailiabilityModalClose,
|
||||||
|
haveChannelsWithVariantsDataChanged,
|
||||||
|
onChannelsWithVariantsConfirm,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [canConfirm, setCanConfirm] = useState(false);
|
||||||
|
const channelsWithVariantsDataRef = useRef(channelsWithVariantsData);
|
||||||
|
const { query, onQueryChange, filteredChannels } = useChannelsSearch(
|
||||||
|
channels
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetCanConfirm = () => {
|
||||||
|
const hasDataInsideDialogChanged = !isEqual(
|
||||||
|
channelsWithVariantsData,
|
||||||
|
channelsWithVariantsDataRef.current
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasDataInsideDialogChanged) {
|
||||||
|
channelsWithVariantsDataRef.current = channelsWithVariantsData;
|
||||||
|
setCanConfirm(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(handleSetCanConfirm, [channelsWithVariantsData]);
|
||||||
|
|
||||||
|
const hasAllChannelsSelected = areAllVariantsAtAllChannelsSelected(
|
||||||
|
variants,
|
||||||
|
channelsWithVariantsData
|
||||||
|
);
|
||||||
|
|
||||||
|
const isChannelSelected = (channelId: string) =>
|
||||||
|
areAnyChannelVariantsSelected(channelsWithVariantsData[channelId]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setCanConfirm(false);
|
||||||
|
onChannelsAvailiabilityModalClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
setCanConfirm(false);
|
||||||
|
onChannelsWithVariantsConfirm();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionDialog
|
||||||
|
confirmButtonState="default"
|
||||||
|
open={isChannelsAvailabilityModalOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
disabled={!canConfirm}
|
||||||
|
>
|
||||||
|
<ChannelsAvailabilityContentWrapper
|
||||||
|
hasAllSelected={hasAllChannelsSelected}
|
||||||
|
hasAnyChannelsToDisplay={!!filteredChannels.length}
|
||||||
|
query={query}
|
||||||
|
onQueryChange={onQueryChange}
|
||||||
|
toggleAll={toggleAllChannels}
|
||||||
|
contentType={contentType}
|
||||||
|
>
|
||||||
|
<ChannelsWithVariantsAvailabilityDialogContent
|
||||||
|
allVariants={variants}
|
||||||
|
channels={filteredChannels}
|
||||||
|
isChannelSelected={isChannelSelected}
|
||||||
|
channelsWithVariants={channelsWithVariantsData}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</ChannelsAvailabilityContentWrapper>
|
||||||
|
</ActionDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsWithVariantsAvailabilityDialog;
|
|
@ -0,0 +1,211 @@
|
||||||
|
import placeholderImage from "@assets/images/placeholder60x60.png";
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
ExpansionPanel,
|
||||||
|
ExpansionPanelSummary,
|
||||||
|
makeStyles,
|
||||||
|
Typography
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import IconCheckboxChecked from "@saleor/icons/CheckboxChecked";
|
||||||
|
import IconCheckboxSemiChecked from "@saleor/icons/CheckboxSemiChecked";
|
||||||
|
import IconChevronDown from "@saleor/icons/ChevronDown";
|
||||||
|
import Label from "@saleor/orders/components/OrderHistory/Label";
|
||||||
|
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||||
|
import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { ChannelsWithVariantsData } from "@saleor/products/views/ProductUpdate/types";
|
||||||
|
import { areAllChannelVariantsSelected } from "@saleor/products/views/ProductUpdate/utils";
|
||||||
|
import map from "lodash-es/map";
|
||||||
|
import React, { ChangeEvent } from "react";
|
||||||
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import ControlledCheckbox from "../../../components/ControlledCheckbox";
|
||||||
|
import Avatar from "../../../components/TableCellAvatar/Avatar";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
variantContainer: {
|
||||||
|
padding: theme.spacing(2, 0, 2, 4)
|
||||||
|
},
|
||||||
|
channelContainer: {
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
channelCheckboxContainer: {
|
||||||
|
width: "100%",
|
||||||
|
padding: theme.spacing(2, 0)
|
||||||
|
},
|
||||||
|
channelTitleContainer: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column"
|
||||||
|
},
|
||||||
|
variantTitleContainer: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelsWithVariantsAvailabilityDialogContent" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const useExpanderStyles = makeStyles(
|
||||||
|
() => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
boxShadow: "none",
|
||||||
|
|
||||||
|
"&:before": {
|
||||||
|
content: "none"
|
||||||
|
},
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelsWithVariantsAvailabilityExpander" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const useSummaryStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
border: "none",
|
||||||
|
height: theme.spacing(10),
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
minHeight: 0
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelsWithVariantsAvailabilityChannelSummary" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
variantsSelectedLabel: {
|
||||||
|
defaultMessage: "{variantsAmount} variants selected",
|
||||||
|
description: "variants selected label"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ChannelsWithVariantsAvailabilityDialogContentProps {
|
||||||
|
addVariantToChannel: (channelId: string, variantId: string) => void;
|
||||||
|
removeVariantFromChannel: (channelId: string, variantId: string) => void;
|
||||||
|
channelsWithVariants: ChannelsWithVariantsData;
|
||||||
|
toggleAllChannelVariants: (channelId: string) => () => void;
|
||||||
|
isChannelSelected: (channelId: string) => boolean;
|
||||||
|
channels: ChannelData[];
|
||||||
|
allVariants: ProductDetails_product_variants[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelsWithVariantsAvailabilityDialogContent: React.FC<ChannelsWithVariantsAvailabilityDialogContentProps> = ({
|
||||||
|
channelsWithVariants,
|
||||||
|
addVariantToChannel,
|
||||||
|
removeVariantFromChannel,
|
||||||
|
toggleAllChannelVariants,
|
||||||
|
isChannelSelected,
|
||||||
|
channels,
|
||||||
|
allVariants
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const classes = useStyles({});
|
||||||
|
const expanderClasses = useExpanderStyles({});
|
||||||
|
const summaryClasses = useSummaryStyles({});
|
||||||
|
|
||||||
|
const handleVariantChange = (channelId: string, variantId: string) => (
|
||||||
|
event: ChangeEvent<any>
|
||||||
|
) =>
|
||||||
|
event.target.value
|
||||||
|
? addVariantToChannel(channelId, variantId)
|
||||||
|
: removeVariantFromChannel(channelId, variantId);
|
||||||
|
|
||||||
|
const selectChannelIcon = (channelId: string) =>
|
||||||
|
areAllChannelVariantsSelected(
|
||||||
|
allVariants,
|
||||||
|
channelsWithVariants[channelId]
|
||||||
|
) ? (
|
||||||
|
<IconCheckboxChecked />
|
||||||
|
) : (
|
||||||
|
<IconCheckboxSemiChecked />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{map(channelsWithVariants, ({ selectedVariantsIds }, channelId) => {
|
||||||
|
const { name } = channels.find(getById(channelId));
|
||||||
|
|
||||||
|
const isVariantSelected = (variantId: string) =>
|
||||||
|
selectedVariantsIds.includes(variantId);
|
||||||
|
|
||||||
|
const getVariantThumbnailSrc = (variantId: string) =>
|
||||||
|
allVariants.find(getById(variantId)).media[0]?.url ||
|
||||||
|
placeholderImage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExpansionPanel classes={expanderClasses}>
|
||||||
|
<ExpansionPanelSummary
|
||||||
|
expandIcon={<IconChevronDown />}
|
||||||
|
classes={summaryClasses}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classes.channelContainer}
|
||||||
|
// stop expander when selecting & deselecting channel
|
||||||
|
onClick={event => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className={classes.channelCheckboxContainer}>
|
||||||
|
<ControlledCheckbox
|
||||||
|
checked={isChannelSelected(channelId)}
|
||||||
|
checkedIcon={selectChannelIcon(channelId)}
|
||||||
|
name={name}
|
||||||
|
label={
|
||||||
|
<div className={classes.channelTitleContainer}>
|
||||||
|
<Typography>{name}</Typography>
|
||||||
|
<Label
|
||||||
|
text={intl.formatMessage(
|
||||||
|
messages.variantsSelectedLabel,
|
||||||
|
{
|
||||||
|
variantsAmount: selectedVariantsIds.length
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onChange={toggleAllChannelVariants(channelId)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
|
</ExpansionPanelSummary>
|
||||||
|
{allVariants.map(({ id: variantId, name }) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={variantId}
|
||||||
|
className={classes.variantContainer}
|
||||||
|
data-test-id="channel-row"
|
||||||
|
>
|
||||||
|
<ControlledCheckbox
|
||||||
|
checked={isVariantSelected(variantId)}
|
||||||
|
name={name}
|
||||||
|
label={
|
||||||
|
<div className={classes.variantTitleContainer}>
|
||||||
|
<Avatar thumbnail={getVariantThumbnailSrc(variantId)} />
|
||||||
|
<Typography>{name}</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onChange={handleVariantChange(channelId, variantId)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</ExpansionPanel>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsWithVariantsAvailabilityDialogContent;
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ChannelsWithVariantsAvailabilityDialog";
|
||||||
|
export { default } from "./ChannelsWithVariantsAvailabilityDialog";
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Divider, Typography } from "@material-ui/core";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { Channel_channel_shippingZones } from "@saleor/channels/types/Channel";
|
||||||
|
import DeletableItem from "@saleor/components/DeletableItem";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
container: {
|
||||||
|
paddingLeft: theme.spacing(3),
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ShippingZoneItem" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ShippingZoneItemProps {
|
||||||
|
zone: Channel_channel_shippingZones;
|
||||||
|
onDelete: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShippingZoneItem: React.FC<ShippingZoneItemProps> = ({
|
||||||
|
zone,
|
||||||
|
onDelete
|
||||||
|
}) => {
|
||||||
|
const { id, name } = zone;
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classes.container}>
|
||||||
|
<Typography>{name}</Typography>
|
||||||
|
<DeletableItem id={id} onDelete={onDelete} />
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingZoneItem;
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Channel_channel_shippingZones } from "@saleor/channels/types/Channel";
|
||||||
|
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import ShippingZonesCard from "./ShippingZonesCard";
|
||||||
|
|
||||||
|
const shippingZones = [
|
||||||
|
{
|
||||||
|
__typename: "ShippingZone",
|
||||||
|
id: "2",
|
||||||
|
name: "Fancy shipping zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: "ShippingZone",
|
||||||
|
id: "3",
|
||||||
|
name: "Nice shipping zone"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseProps = {
|
||||||
|
addShippingZone: () => undefined,
|
||||||
|
removeShippingZone: () => undefined,
|
||||||
|
searchShippingZones: () => undefined,
|
||||||
|
fetchMoreShippingZones: {
|
||||||
|
loading: false,
|
||||||
|
hasMore: false,
|
||||||
|
onFetchMore: () => undefined
|
||||||
|
},
|
||||||
|
shippingZones: [],
|
||||||
|
shippingZonesChoices: shippingZones as Channel_channel_shippingZones[]
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Shipping zones card", module)
|
||||||
|
.addDecorator(CommonDecorator)
|
||||||
|
.add("with no options selected", () => <ShippingZonesCard {...baseProps} />)
|
||||||
|
.add("with options selected", () => (
|
||||||
|
<ShippingZonesCard
|
||||||
|
{...baseProps}
|
||||||
|
shippingZones={shippingZones as Channel_channel_shippingZones[]}
|
||||||
|
/>
|
||||||
|
));
|
|
@ -0,0 +1,84 @@
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Divider,
|
||||||
|
makeStyles,
|
||||||
|
Typography
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import ExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||||
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
|
import React from "react";
|
||||||
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import ShippingZoneItem from "./ShippingZoneItem";
|
||||||
|
import ShippingZonesCardListFooter from "./ShippingZonesCardListFooter";
|
||||||
|
import ShippingZonesListHeader from "./ShippingZonesListHeader";
|
||||||
|
import { ShippingZonesProps } from "./types";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "Shipping Zones",
|
||||||
|
description: "card title"
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
defaultMessage:
|
||||||
|
"Select Shipping Zones that will be supplied via this channel. You can assign Shipping Zones to multiple channels.",
|
||||||
|
description: "card subtitle"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const useExpanderStyles = makeStyles(
|
||||||
|
() => ({
|
||||||
|
// empty expanded needed for mui to use root styles
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
boxShadow: "none",
|
||||||
|
|
||||||
|
"&:before": {
|
||||||
|
content: "none"
|
||||||
|
},
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ShippingZonesCardExpander" }
|
||||||
|
);
|
||||||
|
|
||||||
|
type ShippingZonesCardProps = ShippingZonesProps;
|
||||||
|
|
||||||
|
const ShippingZonesCard: React.FC<ShippingZonesCardProps> = props => {
|
||||||
|
const {
|
||||||
|
shippingZones,
|
||||||
|
removeShippingZone,
|
||||||
|
fetchMoreShippingZones: { totalCount }
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const expanderClasses = useExpanderStyles({});
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const hasMoreZonesToBeSelected = totalCount !== shippingZones.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.title)} />
|
||||||
|
<CardContent>
|
||||||
|
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
<ExpansionPanel classes={expanderClasses}>
|
||||||
|
<ShippingZonesListHeader shippingZones={shippingZones} />
|
||||||
|
<Divider />
|
||||||
|
{shippingZones.map(zone => (
|
||||||
|
<ShippingZoneItem zone={zone} onDelete={removeShippingZone} />
|
||||||
|
))}
|
||||||
|
{hasMoreZonesToBeSelected ? (
|
||||||
|
<ShippingZonesCardListFooter {...props} />
|
||||||
|
) : null}
|
||||||
|
</ExpansionPanel>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingZonesCard;
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { ClickAwayListener } from "@material-ui/core";
|
||||||
|
import { Channel_channel_shippingZones } from "@saleor/channels/types/Channel";
|
||||||
|
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
|
import CardAddItemsFooter from "@saleor/products/components/ProductStocks/CardAddItemsFooter";
|
||||||
|
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
import { ShippingZonesProps } from "./types";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
addZoneTitle: {
|
||||||
|
defaultMessage: "Add Shipping Zones",
|
||||||
|
description: "add shipping zone title"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type ShippingZonesCardListFooterProps = ShippingZonesProps;
|
||||||
|
|
||||||
|
const ShippingZonesCardListFooter: React.FC<ShippingZonesCardListFooterProps> = ({
|
||||||
|
shippingZonesChoices,
|
||||||
|
searchShippingZones,
|
||||||
|
fetchMoreShippingZones,
|
||||||
|
addShippingZone,
|
||||||
|
shippingZones
|
||||||
|
}) => {
|
||||||
|
const [isChoicesSelectShown, setIsChoicesSelectShown] = useState(false);
|
||||||
|
const shippingZonesRef = useRef<Channel_channel_shippingZones[]>(
|
||||||
|
shippingZones
|
||||||
|
);
|
||||||
|
|
||||||
|
// select holds value and displays it so it needs remounting
|
||||||
|
// to display empty input after adding new zone
|
||||||
|
useEffect(() => {
|
||||||
|
if (shippingZones.length > shippingZonesRef.current.length) {
|
||||||
|
setIsChoicesSelectShown(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
shippingZonesRef.current = shippingZones;
|
||||||
|
}, [shippingZones]);
|
||||||
|
|
||||||
|
const handleChoice = ({ target }) => {
|
||||||
|
setIsChoicesSelectShown(false);
|
||||||
|
addShippingZone(target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFooterClickAway = () => {
|
||||||
|
setIsChoicesSelectShown(false);
|
||||||
|
searchShippingZones("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return isChoicesSelectShown ? (
|
||||||
|
<ClickAwayListener onClickAway={handleFooterClickAway}>
|
||||||
|
<div>
|
||||||
|
<SingleAutocompleteSelectField
|
||||||
|
value=""
|
||||||
|
displayValue=""
|
||||||
|
nakedInput
|
||||||
|
name="shippingZone"
|
||||||
|
choices={mapNodeToChoice(shippingZonesChoices)}
|
||||||
|
fetchChoices={searchShippingZones}
|
||||||
|
onChange={handleChoice}
|
||||||
|
{...fetchMoreShippingZones}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
|
) : (
|
||||||
|
<CardAddItemsFooter
|
||||||
|
onAdd={() => setIsChoicesSelectShown(true)}
|
||||||
|
title={messages.addZoneTitle}
|
||||||
|
testIds={{
|
||||||
|
link: "add-shipping-zone-link",
|
||||||
|
button: "add-shipping-zone-button"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingZonesCardListFooter;
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { ExpansionPanelSummary, Typography } from "@material-ui/core";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
|
||||||
|
import { Channel_channel_shippingZones } from "@saleor/channels/types/Channel";
|
||||||
|
import IconChevronDown from "@saleor/icons/ChevronDown";
|
||||||
|
import React from "react";
|
||||||
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
container: {
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center"
|
||||||
|
},
|
||||||
|
// empty expanded needed for mui to use root styles
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
border: "none",
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
minHeight: 0,
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
minHeight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingLeft: theme.spacing(1),
|
||||||
|
margin: 0,
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ShippingZonesListHeader" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "{zonesCount} Shipping Zones",
|
||||||
|
description: "title"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ShippingZonesListHeaderProps {
|
||||||
|
shippingZones: Channel_channel_shippingZones[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShippingZonesListHeader: React.FC<ShippingZonesListHeaderProps> = ({
|
||||||
|
shippingZones
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<ExpansionPanelSummary expandIcon={<IconChevronDown />} classes={classes}>
|
||||||
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
|
{intl.formatMessage(messages.title, {
|
||||||
|
zonesCount: shippingZones.length
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</ExpansionPanelSummary>
|
||||||
|
<HorizontalSpacer spacing={1.5} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingZonesListHeader;
|
2
src/channels/components/ShippingZonesCard/index.tsx
Normal file
2
src/channels/components/ShippingZonesCard/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ShippingZonesCard";
|
||||||
|
export { default } from "./ShippingZonesCard";
|
12
src/channels/components/ShippingZonesCard/types.ts
Normal file
12
src/channels/components/ShippingZonesCard/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Channel_channel_shippingZones } from "@saleor/channels/types/Channel";
|
||||||
|
import { SearchShippingZones_search_edges_node } from "@saleor/searches/types/SearchShippingZones";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
|
||||||
|
export interface ShippingZonesProps {
|
||||||
|
addShippingZone: (id: string) => void;
|
||||||
|
removeShippingZone: (id: string) => void;
|
||||||
|
searchShippingZones: (searchPhrase: string) => void;
|
||||||
|
fetchMoreShippingZones: FetchMoreProps;
|
||||||
|
shippingZones: Channel_channel_shippingZones[];
|
||||||
|
shippingZonesChoices: SearchShippingZones_search_edges_node[];
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm5lcDoy",
|
id: "Q2hhbm5lcDoy",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -27,6 +28,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm7lbDoy213",
|
id: "Q2hhbm7lbDoy213",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -37,6 +39,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
|
shippingZones: [],
|
||||||
id: "Q2hhbn5lbDoytr",
|
id: "Q2hhbn5lbDoytr",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
name: "Channel test",
|
name: "Channel test",
|
||||||
|
@ -45,6 +48,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm5lbDo5bot",
|
id: "Q2hhbm5lbDo5bot",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -54,6 +58,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm7lbDoyr0tr",
|
id: "Q2hhbm7lbDoyr0tr",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -63,6 +68,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbn5lbDoyya",
|
id: "Q2hhbn5lbDoyya",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -72,6 +78,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
{
|
{
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "euro",
|
currencyCode: "euro",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm5lbDo5w0z",
|
id: "Q2hhbm5lbDo5w0z",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
@ -83,6 +90,7 @@ export const channelsList: Channels_channels[] = [
|
||||||
export const channel: Channel_channel = {
|
export const channel: Channel_channel = {
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
currencyCode: "zl",
|
currencyCode: "zl",
|
||||||
|
shippingZones: [],
|
||||||
hasOrders: false,
|
hasOrders: false,
|
||||||
id: "Q2hhbm5lbDov78",
|
id: "Q2hhbm5lbDov78",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
|
|
@ -18,7 +18,14 @@ const props: ChannelDetailsPageProps = {
|
||||||
onBack: () => undefined,
|
onBack: () => undefined,
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => undefined,
|
||||||
saveButtonBarState: "default",
|
saveButtonBarState: "default",
|
||||||
updateChannelStatus: () => undefined
|
updateChannelStatus: () => undefined,
|
||||||
|
searchShippingZones: () => undefined,
|
||||||
|
searchShippingZonesData: undefined,
|
||||||
|
fetchMoreShippingZones: {
|
||||||
|
loading: false,
|
||||||
|
hasMore: false,
|
||||||
|
onFetchMore: () => undefined
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Views / Channels / Channel details", module)
|
storiesOf("Views / Channels / Channel details", module)
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
|
import ShippingZonesCard from "@saleor/channels/components/ShippingZonesCard/ShippingZonesCard";
|
||||||
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||||
|
import { SearchData } from "@saleor/hooks/makeTopLevelSearch";
|
||||||
|
import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||||
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
import {
|
||||||
|
getById,
|
||||||
|
getByUnmatchingId
|
||||||
|
} from "@saleor/orders/components/OrderReturnPage/utils";
|
||||||
|
import { SearchShippingZones_search_edges_node } from "@saleor/searches/types/SearchShippingZones";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { ChannelForm, FormData } from "../../components/ChannelForm";
|
import { ChannelForm, FormData } from "../../components/ChannelForm";
|
||||||
import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus";
|
import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus";
|
||||||
import { Channel_channel } from "../../types/Channel";
|
import {
|
||||||
|
Channel_channel,
|
||||||
|
Channel_channel_shippingZones
|
||||||
|
} from "../../types/Channel";
|
||||||
|
import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils";
|
||||||
|
|
||||||
export interface ChannelDetailsPageProps {
|
export interface ChannelDetailsPageProps {
|
||||||
channel?: Channel_channel;
|
channel?: Channel_channel;
|
||||||
|
@ -20,38 +35,97 @@ export interface ChannelDetailsPageProps {
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
onSubmit?: (data: FormData) => void;
|
onSubmit: (data: FormData) => void;
|
||||||
updateChannelStatus?: () => void;
|
updateChannelStatus?: () => void;
|
||||||
|
searchShippingZones: (query: string) => void;
|
||||||
|
searchShippingZonesData?: SearchData;
|
||||||
|
fetchMoreShippingZones: FetchMoreProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialData: FormData = {
|
|
||||||
currencyCode: "",
|
|
||||||
name: "",
|
|
||||||
slug: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
||||||
channel,
|
channel,
|
||||||
currencyCodes,
|
currencyCodes,
|
||||||
disabled,
|
disabled,
|
||||||
disabledStatus,
|
disabledStatus,
|
||||||
|
onSubmit,
|
||||||
errors,
|
errors,
|
||||||
onBack,
|
onBack,
|
||||||
onSubmit,
|
|
||||||
onDelete,
|
onDelete,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
updateChannelStatus
|
updateChannelStatus,
|
||||||
|
searchShippingZones,
|
||||||
|
searchShippingZonesData,
|
||||||
|
fetchMoreShippingZones
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedCurrencyCode, setSelectedCurrencyCode] = React.useState("");
|
const [selectedCurrencyCode, setSelectedCurrencyCode] = useState("");
|
||||||
|
|
||||||
|
const [shippingZonesToDisplay, setShippingZonesToDisplay] = useStateFromProps<
|
||||||
|
Channel_channel_shippingZones[]
|
||||||
|
>(channel?.shippingZones || []);
|
||||||
|
|
||||||
|
const initialData: FormData = {
|
||||||
|
currencyCode: "",
|
||||||
|
name: "",
|
||||||
|
slug: "",
|
||||||
|
shippingZonesIdsToAdd: [],
|
||||||
|
shippingZonesIdsToRemove: [],
|
||||||
|
...channel
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilteredShippingZonesChoices = (): SearchShippingZones_search_edges_node[] =>
|
||||||
|
getParsedSearchData({ data: searchShippingZonesData }).filter(
|
||||||
|
({ id: searchedZoneId }) =>
|
||||||
|
!shippingZonesToDisplay.some(({ id }) => id === searchedZoneId)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={onSubmit} initial={channel || initialData}>
|
<Form onSubmit={onSubmit} initial={initialData}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit, set }) => {
|
||||||
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
||||||
change,
|
change,
|
||||||
setSelectedCurrencyCode,
|
setSelectedCurrencyCode,
|
||||||
currencyCodes
|
currencyCodes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addShippingZone = (zoneId: string) => {
|
||||||
|
set({
|
||||||
|
...data,
|
||||||
|
shippingZonesIdsToRemove: getUpdatedIdsWithoutNewId(
|
||||||
|
data.shippingZonesIdsToRemove,
|
||||||
|
zoneId
|
||||||
|
),
|
||||||
|
shippingZonesIdsToAdd: getUpdatedIdsWithNewId(
|
||||||
|
data.shippingZonesIdsToAdd,
|
||||||
|
zoneId
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
setShippingZonesToDisplay([
|
||||||
|
...shippingZonesToDisplay,
|
||||||
|
getParsedSearchData({ data: searchShippingZonesData }).find(
|
||||||
|
getById(zoneId)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeShippingZone = (zoneId: string) => {
|
||||||
|
set({
|
||||||
|
...data,
|
||||||
|
shippingZonesIdsToAdd: getUpdatedIdsWithoutNewId(
|
||||||
|
data.shippingZonesIdsToAdd,
|
||||||
|
zoneId
|
||||||
|
),
|
||||||
|
shippingZonesIdsToRemove: getUpdatedIdsWithNewId(
|
||||||
|
data.shippingZonesIdsToRemove,
|
||||||
|
zoneId
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
setShippingZonesToDisplay(
|
||||||
|
shippingZonesToDisplay.filter(getByUnmatchingId(zoneId))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const formDisabled = !data.name || !data.slug || !data.currencyCode;
|
const formDisabled = !data.name || !data.slug || !data.currencyCode;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -68,15 +142,26 @@ export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!!updateChannelStatus && (
|
|
||||||
<div>
|
<div>
|
||||||
|
{!!updateChannelStatus && (
|
||||||
|
<>
|
||||||
<ChannelStatus
|
<ChannelStatus
|
||||||
isActive={channel?.isActive}
|
isActive={channel?.isActive}
|
||||||
disabled={disabledStatus}
|
disabled={disabledStatus}
|
||||||
updateChannelStatus={updateChannelStatus}
|
updateChannelStatus={updateChannelStatus}
|
||||||
/>
|
/>
|
||||||
</div>
|
<CardSpacer />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
<ShippingZonesCard
|
||||||
|
shippingZonesChoices={getFilteredShippingZonesChoices()}
|
||||||
|
shippingZones={shippingZonesToDisplay}
|
||||||
|
addShippingZone={addShippingZone}
|
||||||
|
removeShippingZone={removeShippingZone}
|
||||||
|
searchShippingZones={searchShippingZones}
|
||||||
|
fetchMoreShippingZones={fetchMoreShippingZones}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<SaveButtonBar
|
<SaveButtonBar
|
||||||
onCancel={onBack}
|
onCancel={onBack}
|
||||||
|
|
7
src/channels/pages/ChannelDetailsPage/utils.ts
Normal file
7
src/channels/pages/ChannelDetailsPage/utils.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import uniq from "lodash-es/uniq";
|
||||||
|
|
||||||
|
export const getUpdatedIdsWithNewId = (ids: string[], newId: string) =>
|
||||||
|
uniq([...ids, newId]);
|
||||||
|
|
||||||
|
export const getUpdatedIdsWithoutNewId = (ids: string[], newId: string) =>
|
||||||
|
ids.filter(id => id !== newId);
|
|
@ -7,6 +7,34 @@
|
||||||
// GraphQL query operation: Channel
|
// GraphQL query operation: Channel
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface Channel_channel_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channel_channel_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channel_channel_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channel_channel_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (Channel_channel_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (Channel_channel_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (Channel_channel_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Channel_channel {
|
export interface Channel_channel {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -15,6 +43,7 @@ export interface Channel_channel {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: Channel_channel_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Channel {
|
export interface Channel {
|
||||||
|
|
|
@ -9,6 +9,34 @@ import { ChannelErrorCode } from "./../../types/globalTypes";
|
||||||
// GraphQL mutation operation: ChannelActivate
|
// GraphQL mutation operation: ChannelActivate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ChannelActivate_channelActivate_channel_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelActivate_channelActivate_channel_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelActivate_channelActivate_channel_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelActivate_channelActivate_channel_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (ChannelActivate_channelActivate_channel_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (ChannelActivate_channelActivate_channel_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (ChannelActivate_channelActivate_channel_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChannelActivate_channelActivate_channel {
|
export interface ChannelActivate_channelActivate_channel {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +45,7 @@ export interface ChannelActivate_channelActivate_channel {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: ChannelActivate_channelActivate_channel_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelActivate_channelActivate_errors {
|
export interface ChannelActivate_channelActivate_errors {
|
||||||
|
|
|
@ -9,6 +9,34 @@ import { ChannelCreateInput, ChannelErrorCode } from "./../../types/globalTypes"
|
||||||
// GraphQL mutation operation: ChannelCreate
|
// GraphQL mutation operation: ChannelCreate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ChannelCreate_channelCreate_channel_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelCreate_channelCreate_channel_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelCreate_channelCreate_channel_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelCreate_channelCreate_channel_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (ChannelCreate_channelCreate_channel_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (ChannelCreate_channelCreate_channel_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (ChannelCreate_channelCreate_channel_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChannelCreate_channelCreate_channel {
|
export interface ChannelCreate_channelCreate_channel {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +45,7 @@ export interface ChannelCreate_channelCreate_channel {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: ChannelCreate_channelCreate_channel_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelCreate_channelCreate_errors {
|
export interface ChannelCreate_channelCreate_errors {
|
||||||
|
|
|
@ -9,6 +9,34 @@ import { ChannelErrorCode } from "./../../types/globalTypes";
|
||||||
// GraphQL mutation operation: ChannelDeactivate
|
// GraphQL mutation operation: ChannelDeactivate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ChannelDeactivate_channelDeactivate_channel_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDeactivate_channelDeactivate_channel_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDeactivate_channelDeactivate_channel_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDeactivate_channelDeactivate_channel_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (ChannelDeactivate_channelDeactivate_channel_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (ChannelDeactivate_channelDeactivate_channel_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (ChannelDeactivate_channelDeactivate_channel_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChannelDeactivate_channelDeactivate_channel {
|
export interface ChannelDeactivate_channelDeactivate_channel {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +45,7 @@ export interface ChannelDeactivate_channelDeactivate_channel {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: ChannelDeactivate_channelDeactivate_channel_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelDeactivate_channelDeactivate_errors {
|
export interface ChannelDeactivate_channelDeactivate_errors {
|
||||||
|
|
|
@ -9,6 +9,34 @@ import { ChannelUpdateInput, ChannelErrorCode } from "./../../types/globalTypes"
|
||||||
// GraphQL mutation operation: ChannelUpdate
|
// GraphQL mutation operation: ChannelUpdate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ChannelUpdate_channelUpdate_channel_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelUpdate_channelUpdate_channel_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelUpdate_channelUpdate_channel_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelUpdate_channelUpdate_channel_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (ChannelUpdate_channelUpdate_channel_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (ChannelUpdate_channelUpdate_channel_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (ChannelUpdate_channelUpdate_channel_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChannelUpdate_channelUpdate_channel {
|
export interface ChannelUpdate_channelUpdate_channel {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,6 +45,7 @@ export interface ChannelUpdate_channelUpdate_channel {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: ChannelUpdate_channelUpdate_channel_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelUpdate_channelUpdate_errors {
|
export interface ChannelUpdate_channelUpdate_errors {
|
||||||
|
|
|
@ -7,6 +7,34 @@
|
||||||
// GraphQL query operation: Channels
|
// GraphQL query operation: Channels
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface Channels_channels_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channels_channels_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channels_channels_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Channels_channels_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (Channels_channels_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (Channels_channels_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (Channels_channels_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Channels_channels {
|
export interface Channels_channels {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -15,6 +43,7 @@ export interface Channels_channels {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: Channels_channels_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Channels {
|
export interface Channels {
|
||||||
|
|
|
@ -3,9 +3,15 @@ import { CollectionDetails_collection } from "@saleor/collections/types/Collecti
|
||||||
import { SaleDetails_sale } from "@saleor/discounts/types/SaleDetails";
|
import { SaleDetails_sale } from "@saleor/discounts/types/SaleDetails";
|
||||||
import { VoucherDetails_voucher } from "@saleor/discounts/types/VoucherDetails";
|
import { VoucherDetails_voucher } from "@saleor/discounts/types/VoucherDetails";
|
||||||
import { RequireOnlyOne } from "@saleor/misc";
|
import { RequireOnlyOne } from "@saleor/misc";
|
||||||
import { ProductDetails_product } from "@saleor/products/types/ProductDetails";
|
import {
|
||||||
|
ProductDetails_product,
|
||||||
|
ProductDetails_product_variants
|
||||||
|
} from "@saleor/products/types/ProductDetails";
|
||||||
import { ProductVariantDetails_productVariant } from "@saleor/products/types/ProductVariantDetails";
|
import { ProductVariantDetails_productVariant } from "@saleor/products/types/ProductVariantDetails";
|
||||||
import { ShippingZone_shippingZone_shippingMethods_channelListings } from "@saleor/shipping/types/ShippingZone";
|
import {
|
||||||
|
ShippingZone_shippingZone_channels,
|
||||||
|
ShippingZone_shippingZone_shippingMethods_channelListings
|
||||||
|
} from "@saleor/shipping/types/ShippingZone";
|
||||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||||
import uniqBy from "lodash-es/uniqBy";
|
import uniqBy from "lodash-es/uniqBy";
|
||||||
|
|
||||||
|
@ -16,15 +22,16 @@ export interface Channel {
|
||||||
|
|
||||||
export interface ChannelData {
|
export interface ChannelData {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
publicationDate: string | null;
|
isPublished?: boolean;
|
||||||
currency: string;
|
publicationDate?: string | null;
|
||||||
price: string;
|
currency?: string;
|
||||||
costPrice: string;
|
variantsIds?: string[];
|
||||||
availableForPurchase: string;
|
price?: string;
|
||||||
isAvailableForPurchase: boolean;
|
costPrice?: string;
|
||||||
visibleInListings: boolean;
|
availableForPurchase?: string;
|
||||||
|
isAvailableForPurchase?: boolean;
|
||||||
|
visibleInListings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelPriceData {
|
export interface ChannelPriceData {
|
||||||
|
@ -95,21 +102,13 @@ export const createVariantChannels = (
|
||||||
data?: ProductVariantDetails_productVariant
|
data?: ProductVariantDetails_productVariant
|
||||||
): ChannelPriceData[] => {
|
): ChannelPriceData[] => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const productChannels = data?.product.channelListings.map(listing => ({
|
return data?.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() || "",
|
costPrice: listing.costPrice?.amount.toString() || "",
|
||||||
currency: listing.channel.currencyCode,
|
currency: listing.channel.currencyCode,
|
||||||
id: listing.channel.id,
|
id: listing.channel.id,
|
||||||
name: listing.channel.name,
|
name: listing.channel.name,
|
||||||
price: listing.price.amount.toString()
|
price: listing.price?.amount?.toString()
|
||||||
}));
|
}));
|
||||||
return uniqBy([...variantChannels, ...productChannels], obj => obj.id);
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
@ -147,6 +146,7 @@ export const createChannelsData = (data?: Channels_channels[]): ChannelData[] =>
|
||||||
currency: channel.currencyCode,
|
currency: channel.currencyCode,
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
isAvailableForPurchase: false,
|
isAvailableForPurchase: false,
|
||||||
|
variantsIds: [],
|
||||||
isPublished: false,
|
isPublished: false,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
price: "",
|
price: "",
|
||||||
|
@ -168,7 +168,7 @@ export const createChannelsDataWithPrice = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createShippingChannels = (
|
export const createShippingChannels = (
|
||||||
data?: Channels_channels[]
|
data?: ShippingZone_shippingZone_channels[]
|
||||||
): ChannelShippingData[] =>
|
): ChannelShippingData[] =>
|
||||||
data?.map(channel => ({
|
data?.map(channel => ({
|
||||||
currency: channel.currencyCode,
|
currency: channel.currencyCode,
|
||||||
|
@ -240,29 +240,54 @@ export const createChannelsDataFromSale = (saleData?: SaleDetails_sale) =>
|
||||||
export const createChannelsDataFromProduct = (
|
export const createChannelsDataFromProduct = (
|
||||||
productData?: ProductDetails_product
|
productData?: ProductDetails_product
|
||||||
) =>
|
) =>
|
||||||
productData?.channelListings?.map(option => {
|
productData?.channelListings?.map(
|
||||||
|
({
|
||||||
|
channel,
|
||||||
|
availableForPurchase,
|
||||||
|
isAvailableForPurchase,
|
||||||
|
visibleInListings,
|
||||||
|
publicationDate,
|
||||||
|
isPublished
|
||||||
|
}) => {
|
||||||
const variantChannel = productData.variants[0]?.channelListings.find(
|
const variantChannel = productData.variants[0]?.channelListings.find(
|
||||||
listing => listing.channel.id === option.channel.id
|
listing => listing.channel.id === channel.id
|
||||||
);
|
);
|
||||||
const price = variantChannel?.price;
|
const price = variantChannel?.price;
|
||||||
const costPrice = variantChannel?.costPrice;
|
const costPrice = variantChannel?.costPrice;
|
||||||
|
const variantsIds = extractVariantsIdsForChannel(
|
||||||
|
productData.variants,
|
||||||
|
channel.id
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availableForPurchase: option?.availableForPurchase,
|
availableForPurchase,
|
||||||
costPrice: costPrice ? costPrice.amount.toString() : "",
|
isPublished,
|
||||||
|
publicationDate,
|
||||||
|
variantsIds,
|
||||||
|
costPrice: costPrice?.amount.toString() ?? "",
|
||||||
currency: price ? price.currency : "",
|
currency: price ? price.currency : "",
|
||||||
id: option.channel.id,
|
id: channel.id,
|
||||||
isAvailableForPurchase: !!option?.isAvailableForPurchase,
|
isAvailableForPurchase: !!isAvailableForPurchase,
|
||||||
isPublished: option.isPublished,
|
name: channel.name,
|
||||||
name: option.channel.name,
|
|
||||||
price: price ? price.amount.toString() : "",
|
price: price ? price.amount.toString() : "",
|
||||||
publicationDate: option.publicationDate,
|
visibleInListings: !!visibleInListings
|
||||||
visibleInListings: !!option.visibleInListings
|
|
||||||
};
|
};
|
||||||
}) || [];
|
}
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
export const extractVariantsIdsForChannel = (
|
||||||
|
productVariants: ProductDetails_product_variants[],
|
||||||
|
channelId: string
|
||||||
|
) =>
|
||||||
|
productVariants
|
||||||
|
?.filter(({ channelListings }) =>
|
||||||
|
channelListings.some(({ channel }) => channel.id === channelId)
|
||||||
|
)
|
||||||
|
.map(({ id }) => id) || [];
|
||||||
|
|
||||||
export const createSortedChannelsDataFromProduct = (
|
export const createSortedChannelsDataFromProduct = (
|
||||||
productData?: ProductDetails_product
|
productData?: ProductDetails_product
|
||||||
) =>
|
): ChannelData[] =>
|
||||||
createChannelsDataFromProduct(productData).sort((channel, nextChannel) =>
|
createChannelsDataFromProduct(productData).sort((channel, nextChannel) =>
|
||||||
channel.name.localeCompare(nextChannel.name)
|
channel.name.localeCompare(nextChannel.name)
|
||||||
);
|
);
|
||||||
|
@ -272,7 +297,9 @@ export const createSortedChannelsData = (data?: Channels_channels[]) =>
|
||||||
channel.name.localeCompare(nextChannel.name)
|
channel.name.localeCompare(nextChannel.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const createSortedShippingChannels = (data?: Channels_channels[]) =>
|
export const createSortedShippingChannels = (
|
||||||
|
data?: ShippingZone_shippingZone_channels[]
|
||||||
|
) =>
|
||||||
createShippingChannels(data)?.sort((channel, nextChannel) =>
|
createShippingChannels(data)?.sort((channel, nextChannel) =>
|
||||||
channel.name.localeCompare(nextChannel.name)
|
channel.name.localeCompare(nextChannel.name)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
|
import { FormData } from "@saleor/channels/components/ChannelForm/ChannelForm";
|
||||||
|
import { ChannelCreate } from "@saleor/channels/types/ChannelCreate";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||||
|
import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||||
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 { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/utils";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||||
import currencyCodes from "currency-codes";
|
import currencyCodes from "currency-codes";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { ChannelCreateInput } from "../../../types/globalTypes";
|
|
||||||
import { useChannelCreateMutation } from "../../mutations";
|
import { useChannelCreateMutation } from "../../mutations";
|
||||||
import ChannelDetailsPage from "../../pages/ChannelDetailsPage";
|
import ChannelDetailsPage from "../../pages/ChannelDetailsPage";
|
||||||
import { ChannelCreate } from "../../types/ChannelCreate";
|
|
||||||
import { channelPath, channelsListUrl } from "../../urls";
|
import { channelPath, channelsListUrl } from "../../urls";
|
||||||
|
|
||||||
export const ChannelCreateView = ({}) => {
|
export const ChannelCreateView = ({}) => {
|
||||||
|
@ -23,25 +26,38 @@ export const ChannelCreateView = ({}) => {
|
||||||
|
|
||||||
const handleBack = () => navigate(channelsListUrl());
|
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({
|
const [createChannel, createChannelOpts] = useChannelCreateMutation({
|
||||||
onCompleted: onSubmit
|
onCompleted: ({ channelCreate: { errors, channel } }: ChannelCreate) => {
|
||||||
|
notify(getDefaultNotifierSuccessErrorData(errors, intl));
|
||||||
|
|
||||||
|
if (!errors.length) {
|
||||||
|
navigate(channelPath(channel.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (data: ChannelCreateInput) =>
|
const handleSubmit = ({
|
||||||
|
shippingZonesIdsToAdd,
|
||||||
|
shippingZonesIdsToRemove,
|
||||||
|
currencyCode,
|
||||||
|
...rest
|
||||||
|
}: FormData) =>
|
||||||
createChannel({
|
createChannel({
|
||||||
variables: {
|
variables: {
|
||||||
input: { ...data, currencyCode: data.currencyCode.toUpperCase() }
|
input: {
|
||||||
|
...rest,
|
||||||
|
currencyCode: currencyCode.toUpperCase(),
|
||||||
|
addShippingZones: shippingZonesIdsToAdd
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
loadMore: fetchMoreShippingZones,
|
||||||
|
search: searchShippingZones,
|
||||||
|
result: searchShippingZonesResult
|
||||||
|
} = useShippingZonesSearch({
|
||||||
|
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||||
});
|
});
|
||||||
|
|
||||||
const currencyCodeChoices = currencyCodes.data.map(currencyData => ({
|
const currencyCodeChoices = currencyCodes.data.map(currencyData => ({
|
||||||
|
@ -77,6 +93,12 @@ export const ChannelCreateView = ({}) => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<ChannelDetailsPage
|
<ChannelDetailsPage
|
||||||
|
searchShippingZones={searchShippingZones}
|
||||||
|
searchShippingZonesData={searchShippingZonesResult.data}
|
||||||
|
fetchMoreShippingZones={getSearchFetchMoreProps(
|
||||||
|
searchShippingZonesResult,
|
||||||
|
fetchMoreShippingZones
|
||||||
|
)}
|
||||||
disabled={createChannelOpts.loading}
|
disabled={createChannelOpts.loading}
|
||||||
errors={createChannelOpts?.data?.channelCreate?.errors || []}
|
errors={createChannelOpts?.data?.channelCreate?.errors || []}
|
||||||
currencyCodes={currencyCodeChoices}
|
currencyCodes={currencyCodeChoices}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
import ChannelDeleteDialog from "@saleor/channels/components/ChannelDeleteDialog";
|
import ChannelDeleteDialog from "@saleor/channels/components/ChannelDeleteDialog";
|
||||||
|
import { FormData } from "@saleor/channels/components/ChannelForm/ChannelForm";
|
||||||
import { ChannelDelete } from "@saleor/channels/types/ChannelDelete";
|
import { ChannelDelete } from "@saleor/channels/types/ChannelDelete";
|
||||||
import { getChannelsCurrencyChoices } from "@saleor/channels/utils";
|
import { getChannelsCurrencyChoices } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||||
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||||
|
import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||||
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 { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/utils";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { ChannelUpdateInput } from "../../../types/globalTypes";
|
|
||||||
import {
|
import {
|
||||||
useChannelActivateMutation,
|
useChannelActivateMutation,
|
||||||
useChannelDeactivateMutation,
|
useChannelDeactivateMutation,
|
||||||
|
@ -54,14 +57,15 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
ChannelUrlQueryParams
|
ChannelUrlQueryParams
|
||||||
>(navigate, params => channelUrl(id, params), params);
|
>(navigate, params => channelUrl(id, params), params);
|
||||||
|
|
||||||
const onSubmit = (data: ChannelUpdate) => {
|
const [updateChannel, updateChannelOpts] = useChannelUpdateMutation({
|
||||||
if (!data.channelUpdate.errors.length) {
|
onCompleted: ({ channelUpdate: { errors } }: ChannelUpdate) =>
|
||||||
notify({
|
notify(getDefaultNotifierSuccessErrorData(errors, intl))
|
||||||
status: "success",
|
});
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
|
const { data, loading } = useChannelDetails({
|
||||||
|
displayLoader: true,
|
||||||
|
variables: { id }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = (error: ChannelErrorFragment) => {
|
const handleError = (error: ChannelErrorFragment) => {
|
||||||
notify({
|
notify({
|
||||||
|
@ -70,15 +74,6 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, loading } = useChannelDetails({
|
|
||||||
displayLoader: true,
|
|
||||||
variables: { id }
|
|
||||||
});
|
|
||||||
|
|
||||||
const [updateChannel, updateChannelOpts] = useChannelUpdateMutation({
|
|
||||||
onCompleted: onSubmit
|
|
||||||
});
|
|
||||||
|
|
||||||
const [activateChannel, activateChannelOpts] = useChannelActivateMutation({
|
const [activateChannel, activateChannelOpts] = useChannelActivateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
const errors = data.channelActivate.errors;
|
const errors = data.channelActivate.errors;
|
||||||
|
@ -100,15 +95,25 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (data: ChannelUpdateInput) =>
|
const handleSubmit = ({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
shippingZonesIdsToRemove,
|
||||||
|
shippingZonesIdsToAdd
|
||||||
|
}: FormData) =>
|
||||||
updateChannel({
|
updateChannel({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id: data?.channel.id,
|
||||||
input: { name: data.name, slug: data.slug }
|
input: {
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
addShippingZones: shippingZonesIdsToAdd,
|
||||||
|
removeShippingZones: shippingZonesIdsToRemove
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onCompleted = (data: ChannelDelete) => {
|
const onDeleteCompleted = (data: ChannelDelete) => {
|
||||||
const errors = data.channelDelete.errors;
|
const errors = data.channelDelete.errors;
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
|
@ -130,7 +135,7 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const [deleteChannel, deleteChannelOpts] = useChannelDeleteMutation({
|
const [deleteChannel, deleteChannelOpts] = useChannelDeleteMutation({
|
||||||
onCompleted
|
onCompleted: onDeleteCompleted
|
||||||
});
|
});
|
||||||
|
|
||||||
const channelsChoices = getChannelsCurrencyChoices(
|
const channelsChoices = getChannelsCurrencyChoices(
|
||||||
|
@ -146,6 +151,14 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
deleteChannel({ variables: data });
|
deleteChannel({ variables: data });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
loadMore: fetchMoreShippingZones,
|
||||||
|
search: searchShippingZones,
|
||||||
|
result: searchShippingZonesResult
|
||||||
|
} = useShippingZonesSearch({
|
||||||
|
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle
|
<WindowTitle
|
||||||
|
@ -160,15 +173,21 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
<PageHeader title={data?.channel?.name} />
|
<PageHeader title={data?.channel?.name} />
|
||||||
<ChannelDetailsPage
|
<ChannelDetailsPage
|
||||||
|
searchShippingZones={searchShippingZones}
|
||||||
|
searchShippingZonesData={searchShippingZonesResult.data}
|
||||||
|
fetchMoreShippingZones={getSearchFetchMoreProps(
|
||||||
|
searchShippingZonesResult,
|
||||||
|
fetchMoreShippingZones
|
||||||
|
)}
|
||||||
channel={data?.channel}
|
channel={data?.channel}
|
||||||
disabled={updateChannelOpts.loading || loading}
|
disabled={updateChannelOpts.loading || loading}
|
||||||
disabledStatus={
|
disabledStatus={
|
||||||
activateChannelOpts.loading || deactivateChannelOpts.loading
|
activateChannelOpts.loading || deactivateChannelOpts.loading
|
||||||
}
|
}
|
||||||
errors={updateChannelOpts?.data?.channelUpdate?.errors || []}
|
errors={updateChannelOpts?.data?.channelUpdate?.errors || []}
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onDelete={() => openModal("remove")}
|
onDelete={() => openModal("remove")}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
updateChannelStatus={() =>
|
updateChannelStatus={() =>
|
||||||
data?.channel?.isActive
|
data?.channel?.isActive
|
||||||
? deactivateChannel({ variables: { id } })
|
? deactivateChannel({ variables: { id } })
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
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 ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
@ -129,7 +129,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<AvailabilityCard
|
<ChannelsAvailabilityCard
|
||||||
messages={{
|
messages={{
|
||||||
hiddenLabel: intl.formatMessage({
|
hiddenLabel: intl.formatMessage({
|
||||||
defaultMessage: "Hidden",
|
defaultMessage: "Hidden",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
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 ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
@ -124,7 +124,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<AvailabilityCard
|
<ChannelsAvailabilityCard
|
||||||
messages={{
|
messages={{
|
||||||
hiddenLabel: intl.formatMessage({
|
hiddenLabel: intl.formatMessage({
|
||||||
defaultMessage: "Hidden",
|
defaultMessage: "Hidden",
|
||||||
|
|
|
@ -11,9 +11,8 @@ import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailab
|
||||||
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 TableCellAvatar, {
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
AVATAR_MARGIN
|
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
||||||
} from "@saleor/components/TableCellAvatar";
|
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
import { makeStyles } from "@saleor/theme";
|
import { makeStyles } from "@saleor/theme";
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { createChannelsDataFromProduct } from "@saleor/channels/utils";
|
|
||||||
import { product } from "@saleor/products/fixtures";
|
|
||||||
import Decorator from "@saleor/storybook/Decorator";
|
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
const productChannels = createChannelsDataFromProduct(product(""));
|
|
||||||
|
|
||||||
import AvailabilityCard from "./AvailabilityCard";
|
|
||||||
const props = {
|
|
||||||
allChannelsCount: 4,
|
|
||||||
channels: productChannels,
|
|
||||||
errors: [],
|
|
||||||
messages: {
|
|
||||||
hiddenLabel: "Not published",
|
|
||||||
hiddenSecondLabel: "hidden label",
|
|
||||||
visibleLabel: "Published"
|
|
||||||
},
|
|
||||||
onChange: () => undefined,
|
|
||||||
openModal: () => undefined,
|
|
||||||
selectedChannelsCount: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
storiesOf("Generics / AvailabilityCard", module)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("default", () => <AvailabilityCard {...props} />);
|
|
|
@ -1,94 +0,0 @@
|
||||||
import ChannelsAvailability, {
|
|
||||||
ChannelsAvailabilityProps,
|
|
||||||
Message
|
|
||||||
} from "@saleor/components/ChannelsAvailability";
|
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
|
||||||
import React from "react";
|
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
|
|
||||||
interface AvailabilityCardProps {
|
|
||||||
messages: Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AvailabilityCard: React.FC<AvailabilityCardProps &
|
|
||||||
Omit<ChannelsAvailabilityProps, "channelsMessages">> = props => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const localizeDate = useDateLocalize();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChannelsAvailability
|
|
||||||
{...props}
|
|
||||||
channelsMessages={props.channels.reduce(
|
|
||||||
(prevVal, currVal) => ({
|
|
||||||
...prevVal,
|
|
||||||
[currVal.id]: {
|
|
||||||
...props.messages,
|
|
||||||
availableDateText:
|
|
||||||
currVal.publicationDate && !currVal.isPublished
|
|
||||||
? intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Will become available on {date}",
|
|
||||||
description: "channel publication date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(currVal.publicationDate, "L")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: currVal.publicationDate
|
|
||||||
? intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Visible since {date}",
|
|
||||||
description: "channel publication date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(currVal.publicationDate, "L")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: currVal.isPublished
|
|
||||||
? intl.formatMessage({
|
|
||||||
defaultMessage: "Visible",
|
|
||||||
description: "channel publication status"
|
|
||||||
})
|
|
||||||
: intl.formatMessage({
|
|
||||||
defaultMessage: "Hidden",
|
|
||||||
description: "channel publication status"
|
|
||||||
}),
|
|
||||||
availableLabel: intl.formatMessage({
|
|
||||||
defaultMessage: "Available for purchase",
|
|
||||||
description: "product availability"
|
|
||||||
}),
|
|
||||||
availableSecondLabel: intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "will become available on {date}",
|
|
||||||
description: "product available for purchase date"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(currVal.availableForPurchase, "L")
|
|
||||||
}
|
|
||||||
),
|
|
||||||
hiddenSecondLabel: intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "will become published on {date}",
|
|
||||||
description: "product publication date label"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(currVal.publicationDate, "L")
|
|
||||||
}
|
|
||||||
),
|
|
||||||
setAvailabilityDateLabel: intl.formatMessage({
|
|
||||||
defaultMessage: "Set availability date",
|
|
||||||
description: "product availability date label"
|
|
||||||
}),
|
|
||||||
unavailableLabel: intl.formatMessage({
|
|
||||||
defaultMessage: "Unavailable for purchase",
|
|
||||||
description: "product unavailability"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AvailabilityCard;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./AvailabilityCard";
|
|
||||||
export { default } from "./AvailabilityCard";
|
|
|
@ -1,475 +0,0 @@
|
||||||
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 RequirePermissions from "@saleor/components/RequirePermissions";
|
|
||||||
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
|
||||||
import { ProductChannelListingErrorFragment } from "@saleor/fragments/types/ProductChannelListingErrorFragment";
|
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
|
||||||
import useUser from "@saleor/hooks/useUser";
|
|
||||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
|
||||||
import { RequireOnlyOne } from "@saleor/misc";
|
|
||||||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
|
||||||
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).toISOString(), "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 || ""}
|
|
||||||
onChange={e =>
|
|
||||||
onChange(id, {
|
|
||||||
...formData,
|
|
||||||
publicationDate: e.target.value || null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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 { user } = useUser();
|
|
||||||
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={
|
|
||||||
<RequirePermissions
|
|
||||||
userPermissions={user?.userPermissions || []}
|
|
||||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={openModal}
|
|
||||||
data-test-id="channels-availiability-manage-button"
|
|
||||||
>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Manage",
|
|
||||||
description: "section header button"
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
</RequirePermissions>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<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;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./ChannelsAvailability";
|
|
||||||
export { default } from "./ChannelsAvailability";
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||||
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import RadioSwitchField from "@saleor/components/RadioSwitchField";
|
||||||
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
|
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";
|
||||||
|
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "../types";
|
||||||
|
export interface ChannelContentProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
data: ChannelData;
|
||||||
|
errors: ChannelsAvailabilityError[];
|
||||||
|
messages: Messages;
|
||||||
|
onChange: (id: string, data: ChannelOpts) => void;
|
||||||
|
}
|
||||||
|
const ChannelContent: React.FC<ChannelContentProps> = ({
|
||||||
|
data,
|
||||||
|
disabled,
|
||||||
|
errors,
|
||||||
|
messages,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
availableForPurchase,
|
||||||
|
isAvailableForPurchase: isAvailable,
|
||||||
|
isPublished,
|
||||||
|
publicationDate,
|
||||||
|
visibleInListings,
|
||||||
|
id
|
||||||
|
} = 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
|
||||||
|
);
|
||||||
|
const [isAvailableDate, setAvailableDate] = useState(false);
|
||||||
|
const intl = useIntl();
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
const todayDate = localizeDate(new Date(dateNow).toISOString(), "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.container}>
|
||||||
|
<RadioSwitchField
|
||||||
|
className={classes.radioField}
|
||||||
|
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 || ""}
|
||||||
|
onChange={e =>
|
||||||
|
onChange(id, {
|
||||||
|
...formData,
|
||||||
|
publicationDate: e.target.value || null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={classes.date}
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{hasAvailableProps && (
|
||||||
|
<>
|
||||||
|
<Hr />
|
||||||
|
<RadioSwitchField
|
||||||
|
className={classes.radioField}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ChannelContent;
|
|
@ -0,0 +1,95 @@
|
||||||
|
import {
|
||||||
|
ExpansionPanel,
|
||||||
|
ExpansionPanelSummary,
|
||||||
|
makeStyles
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import IconChevronDown from "@saleor/icons/ChevronDown";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Messages } from "../types";
|
||||||
|
|
||||||
|
const useExpanderStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
boxShadow: "none",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
|
||||||
|
"&:before": {
|
||||||
|
content: "none"
|
||||||
|
},
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelContentWrapperExpander" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const useSummaryStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
expanded: {},
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
border: "none",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
minHeight: 0,
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
minHeight: 0,
|
||||||
|
padding: theme.spacing(2, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
margin: 0,
|
||||||
|
|
||||||
|
"&$expanded": {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelContentWrapperExpanderSummary" }
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ChannelContentWrapperProps {
|
||||||
|
data: ChannelData;
|
||||||
|
children: React.ReactNode;
|
||||||
|
messages: Messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelContentWrapper: React.FC<ChannelContentWrapperProps> = ({
|
||||||
|
data,
|
||||||
|
messages,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
const expanderClasses = useExpanderStyles({});
|
||||||
|
const summaryClasses = useSummaryStyles({});
|
||||||
|
|
||||||
|
const { name } = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExpansionPanel
|
||||||
|
classes={expanderClasses}
|
||||||
|
data-test="channel-availability-item"
|
||||||
|
>
|
||||||
|
<ExpansionPanelSummary
|
||||||
|
expandIcon={<IconChevronDown />}
|
||||||
|
classes={summaryClasses}
|
||||||
|
>
|
||||||
|
<Typography>{name}</Typography>
|
||||||
|
<Typography variant="caption">{messages.availableDateText}</Typography>
|
||||||
|
</ExpansionPanelSummary>
|
||||||
|
{children}
|
||||||
|
</ExpansionPanel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelContentWrapper;
|
|
@ -7,9 +7,10 @@ import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import ChannelsAvailability, {
|
import ChannelsAvailabilityCard, {
|
||||||
ChannelsAvailabilityProps
|
ChannelsAvailabilityCardProps
|
||||||
} from "./ChannelsAvailability";
|
} from "./ChannelsAvailabilityCard";
|
||||||
|
import { Messages } from "./types";
|
||||||
|
|
||||||
const user: User = {
|
const user: User = {
|
||||||
__typename: "User",
|
__typename: "User",
|
||||||
|
@ -30,7 +31,7 @@ const user: User = {
|
||||||
|
|
||||||
const productChannels = createChannelsDataFromProduct(product(""));
|
const productChannels = createChannelsDataFromProduct(product(""));
|
||||||
|
|
||||||
const props: ChannelsAvailabilityProps = {
|
const props: ChannelsAvailabilityCardProps = {
|
||||||
allChannelsCount: 4,
|
allChannelsCount: 4,
|
||||||
channelsList: productChannels.map(channel => ({
|
channelsList: productChannels.map(channel => ({
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
|
@ -42,16 +43,16 @@ const props: ChannelsAvailabilityProps = {
|
||||||
selectedChannelsCount: 3
|
selectedChannelsCount: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Generics / ChannelsAvailability", module)
|
storiesOf("Generics / Channels availability card", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.addDecorator(UserDecorator(user))
|
.addDecorator(UserDecorator(user))
|
||||||
.add("default", () => <ChannelsAvailability {...props} />)
|
.add("default", () => <ChannelsAvailabilityCard {...props} />)
|
||||||
.add("with onChange", () => (
|
.add("with onChange", () => (
|
||||||
<ChannelsAvailability
|
<ChannelsAvailabilityCard
|
||||||
{...props}
|
{...props}
|
||||||
channelsList={undefined}
|
channelsList={undefined}
|
||||||
channels={productChannels}
|
channels={productChannels}
|
||||||
channelsMessages={productChannels.reduce(
|
messages={productChannels.reduce(
|
||||||
(prevVal, currVal) => ({
|
(prevVal, currVal) => ({
|
||||||
...prevVal,
|
...prevVal,
|
||||||
[currVal.id]: {
|
[currVal.id]: {
|
||||||
|
@ -60,7 +61,7 @@ storiesOf("Generics / ChannelsAvailability", module)
|
||||||
hiddenSecondLabel: "Will become published"
|
hiddenSecondLabel: "Will become published"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{}
|
{} as Messages
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
|
@ -0,0 +1,93 @@
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { Channel as ChannelList, ChannelData } from "@saleor/channels/utils";
|
||||||
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
|
import { RequireOnlyOne } from "@saleor/misc";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import ChannelAvailabilityItemContent from "./Channel/ChannelAvailabilityItemContent";
|
||||||
|
import ChannelAvailabilityItemWrapper from "./Channel/ChannelAvailabilityItemWrapper";
|
||||||
|
import ChannelsAvailabilityCardWrapper, {
|
||||||
|
ChannelsAvailabilityWrapperProps
|
||||||
|
} from "./ChannelsAvailabilityCardWrapper";
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "./types";
|
||||||
|
import { getChannelsAvailabilityMessages } from "./utils";
|
||||||
|
|
||||||
|
export interface ChannelsAvailability
|
||||||
|
extends Omit<ChannelsAvailabilityWrapperProps, "children"> {
|
||||||
|
channels: ChannelData[];
|
||||||
|
channelsList: ChannelList[];
|
||||||
|
errors?: ChannelsAvailabilityError[];
|
||||||
|
disabled?: boolean;
|
||||||
|
messages?: Messages;
|
||||||
|
onChange?: (id: string, data: ChannelOpts) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelsAvailabilityCardProps = RequireOnlyOne<
|
||||||
|
ChannelsAvailability,
|
||||||
|
"channels" | "channelsList"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = props => {
|
||||||
|
const {
|
||||||
|
channelsList,
|
||||||
|
errors = [],
|
||||||
|
selectedChannelsCount = 0,
|
||||||
|
allChannelsCount = 0,
|
||||||
|
channels,
|
||||||
|
messages,
|
||||||
|
onChange,
|
||||||
|
openModal
|
||||||
|
} = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
const localizeDate = useDateLocalize();
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
const channelsMessages = getChannelsAvailabilityMessages({
|
||||||
|
messages,
|
||||||
|
channels,
|
||||||
|
intl,
|
||||||
|
localizeDate
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChannelsAvailabilityCardWrapper
|
||||||
|
selectedChannelsCount={selectedChannelsCount}
|
||||||
|
allChannelsCount={allChannelsCount}
|
||||||
|
openModal={openModal}
|
||||||
|
>
|
||||||
|
{channels
|
||||||
|
? channels.map(data => {
|
||||||
|
const channelErrors =
|
||||||
|
errors?.filter(error => error.channels.includes(data.id)) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChannelAvailabilityItemWrapper messages={messages} data={data}>
|
||||||
|
<ChannelAvailabilityItemContent
|
||||||
|
data={data}
|
||||||
|
onChange={onChange}
|
||||||
|
messages={channelsMessages[data.id]}
|
||||||
|
errors={channelErrors}
|
||||||
|
/>
|
||||||
|
</ChannelAvailabilityItemWrapper>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: 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}
|
||||||
|
</ChannelsAvailabilityCardWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsAvailability;
|
|
@ -0,0 +1,87 @@
|
||||||
|
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 Hr from "@saleor/components/Hr";
|
||||||
|
import RequirePermissions from "@saleor/components/RequirePermissions";
|
||||||
|
import useUser from "@saleor/hooks/useUser";
|
||||||
|
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
|
||||||
|
export interface ChannelsAvailabilityWrapperProps {
|
||||||
|
selectedChannelsCount: number;
|
||||||
|
allChannelsCount: number;
|
||||||
|
openModal: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperProps> = props => {
|
||||||
|
const {
|
||||||
|
selectedChannelsCount,
|
||||||
|
allChannelsCount,
|
||||||
|
openModal,
|
||||||
|
children
|
||||||
|
} = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
const classes = useStyles({});
|
||||||
|
const { user } = useUser();
|
||||||
|
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={
|
||||||
|
<RequirePermissions
|
||||||
|
userPermissions={user?.userPermissions || []}
|
||||||
|
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={openModal}
|
||||||
|
data-test-id="channels-availiability-manage-button"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Manage",
|
||||||
|
description: "section header button"
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</RequirePermissions>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CardContent className={classes.card}>
|
||||||
|
{!!channelsAvailabilityText && (
|
||||||
|
<>
|
||||||
|
<Typography className={classes.channelInfo}>
|
||||||
|
{channelsAvailabilityText}
|
||||||
|
</Typography>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsAvailabilityWrapper;
|
2
src/components/ChannelsAvailabilityCard/index.ts
Normal file
2
src/components/ChannelsAvailabilityCard/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ChannelsAvailabilityCard";
|
||||||
|
export { default } from "./ChannelsAvailabilityCard";
|
85
src/components/ChannelsAvailabilityCard/styles.ts
Normal file
85
src/components/ChannelsAvailabilityCard/styles.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
container: {
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
radioField: {
|
||||||
|
paddingLeft: theme.spacing(1)
|
||||||
|
},
|
||||||
|
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" }
|
||||||
|
);
|
26
src/components/ChannelsAvailabilityCard/types.tsx
Normal file
26
src/components/ChannelsAvailabilityCard/types.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { CollectionChannelListingErrorFragment } from "@saleor/fragments/types/CollectionChannelListingErrorFragment";
|
||||||
|
import { ProductChannelListingErrorFragment } from "@saleor/fragments/types/ProductChannelListingErrorFragment";
|
||||||
|
|
||||||
|
export interface ChannelOpts {
|
||||||
|
availableForPurchase?: string;
|
||||||
|
isAvailableForPurchase?: boolean;
|
||||||
|
isPublished: boolean;
|
||||||
|
publicationDate: string | null;
|
||||||
|
visibleInListings?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Messages {
|
||||||
|
visibleLabel: string;
|
||||||
|
hiddenLabel: string;
|
||||||
|
visibleSecondLabel?: string;
|
||||||
|
hiddenSecondLabel?: string;
|
||||||
|
availableDateText?: string;
|
||||||
|
availableLabel?: string;
|
||||||
|
unavailableLabel?: string;
|
||||||
|
availableSecondLabel?: string;
|
||||||
|
setAvailabilityDateLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelsAvailabilityError =
|
||||||
|
| ProductChannelListingErrorFragment
|
||||||
|
| CollectionChannelListingErrorFragment;
|
95
src/components/ChannelsAvailabilityCard/utils.ts
Normal file
95
src/components/ChannelsAvailabilityCard/utils.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { ChannelData } from "@saleor/channels/utils";
|
||||||
|
import { LocalizeDate } from "@saleor/hooks/useDateLocalize";
|
||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
|
import { Messages } from "./types";
|
||||||
|
|
||||||
|
export const getChannelsAvailabilityMessages = ({
|
||||||
|
messages,
|
||||||
|
channels = [],
|
||||||
|
intl,
|
||||||
|
localizeDate
|
||||||
|
}: {
|
||||||
|
messages?: Messages;
|
||||||
|
channels?: ChannelData[];
|
||||||
|
intl: IntlShape;
|
||||||
|
localizeDate: LocalizeDate;
|
||||||
|
}): Messages =>
|
||||||
|
channels.reduce(
|
||||||
|
(prevVal, currVal) => ({
|
||||||
|
...prevVal,
|
||||||
|
[currVal.id]: {
|
||||||
|
...messages,
|
||||||
|
availableDateText:
|
||||||
|
currVal.publicationDate && !currVal.isPublished
|
||||||
|
? intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Will become available on {date}",
|
||||||
|
description: "channel publication date",
|
||||||
|
id: "channel publication date will become available"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(currVal.publicationDate, "L")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: currVal.publicationDate
|
||||||
|
? intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Visible since {date}",
|
||||||
|
description: "channel publication date",
|
||||||
|
id: "channel publication date visible since"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(currVal.publicationDate, "L")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: currVal.isPublished
|
||||||
|
? intl.formatMessage({
|
||||||
|
defaultMessage: "Visible",
|
||||||
|
description: "channel publication status",
|
||||||
|
id: "channel visible"
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
defaultMessage: "Hidden",
|
||||||
|
description: "channel publication status",
|
||||||
|
id: "channel hidden"
|
||||||
|
}),
|
||||||
|
availableLabel: intl.formatMessage({
|
||||||
|
defaultMessage: "Available for purchase",
|
||||||
|
description: "product availability",
|
||||||
|
id: "product available for purchase"
|
||||||
|
}),
|
||||||
|
availableSecondLabel: intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "will become available on {date}",
|
||||||
|
description: "product available for purchase date",
|
||||||
|
id: "will become available on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(currVal.availableForPurchase, "L")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
hiddenSecondLabel: intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "will become published on {date}",
|
||||||
|
description: "product publication date label",
|
||||||
|
id: "will become published on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(currVal.publicationDate, "L")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
setAvailabilityDateLabel: intl.formatMessage({
|
||||||
|
defaultMessage: "Set availability date",
|
||||||
|
description: "product availability date label",
|
||||||
|
id: "set availability date"
|
||||||
|
}),
|
||||||
|
unavailableLabel: intl.formatMessage({
|
||||||
|
defaultMessage: "Unavailable for purchase",
|
||||||
|
description: "product unavailability",
|
||||||
|
id: "unavailable for purchase"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{} as Messages
|
||||||
|
);
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./ChannelsAvailabilityContent";
|
|
||||||
export { default } from "./ChannelsAvailabilityContent";
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { Channel } from "@saleor/channels/utils";
|
import { Channel } from "@saleor/channels/utils";
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import { ChannelsAvailabilityContent } from "@saleor/components/ChannelsAvailabilityContent";
|
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import ChannelsAvailabilityDialogChannelsList from "../ChannelsAvailabilityDialogChannelsList";
|
||||||
|
import ChannelsAvailabilityDialogWrapper from "../ChannelsAvailabilityDialogWrapper";
|
||||||
|
import { useChannelsSearch } from "./utils";
|
||||||
|
|
||||||
export interface ChannelsAvailabilityDialogProps {
|
export interface ChannelsAvailabilityDialogProps {
|
||||||
isSelected: (option: Channel) => boolean;
|
isSelected: (option: Channel) => boolean;
|
||||||
channels: Channel[];
|
channels: Channel[];
|
||||||
confirmButtonState: ConfirmButtonTransitionState;
|
confirmButtonState: ConfirmButtonTransitionState;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onChange: (option: Channel) => void;
|
onChange: (option: Channel) => void;
|
||||||
|
@ -23,16 +26,25 @@ export const ChannelsAvailabilityDialog: React.FC<ChannelsAvailabilityDialogProp
|
||||||
isSelected,
|
isSelected,
|
||||||
channels,
|
channels,
|
||||||
confirmButtonState,
|
confirmButtonState,
|
||||||
contentType = "",
|
contentType,
|
||||||
disabled,
|
disabled,
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
onChange,
|
onChange,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
selected = 0,
|
selected,
|
||||||
title,
|
title,
|
||||||
toggleAll
|
toggleAll
|
||||||
}) => (
|
}) => {
|
||||||
|
const { query, onQueryChange, filteredChannels } = useChannelsSearch(
|
||||||
|
channels
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleAll = () => toggleAll(channels, selected);
|
||||||
|
|
||||||
|
const hasAllSelected = selected === channels.length;
|
||||||
|
|
||||||
|
return (
|
||||||
<ActionDialog
|
<ActionDialog
|
||||||
confirmButtonState={confirmButtonState}
|
confirmButtonState={confirmButtonState}
|
||||||
open={open}
|
open={open}
|
||||||
|
@ -41,16 +53,22 @@ export const ChannelsAvailabilityDialog: React.FC<ChannelsAvailabilityDialogProp
|
||||||
title={title}
|
title={title}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<ChannelsAvailabilityContent
|
<ChannelsAvailabilityDialogWrapper
|
||||||
channels={channels}
|
hasAnyChannelsToDisplay={!!filteredChannels.length}
|
||||||
disabled={disabled}
|
hasAllSelected={hasAllSelected}
|
||||||
|
query={query}
|
||||||
|
onQueryChange={onQueryChange}
|
||||||
|
toggleAll={handleToggleAll}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
isSelected={isSelected}
|
>
|
||||||
selected={selected}
|
<ChannelsAvailabilityDialogChannelsList
|
||||||
toggleAll={toggleAll}
|
channels={filteredChannels}
|
||||||
|
isChannelSelected={isSelected}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
|
</ChannelsAvailabilityDialogWrapper>
|
||||||
</ActionDialog>
|
</ActionDialog>
|
||||||
);
|
);
|
||||||
ChannelsAvailabilityDialog.displayName = "ChannelsAvailabilityDialog";
|
};
|
||||||
|
|
||||||
export default ChannelsAvailabilityDialog;
|
export default ChannelsAvailabilityDialog;
|
||||||
|
|
11
src/components/ChannelsAvailabilityDialog/utils.ts
Normal file
11
src/components/ChannelsAvailabilityDialog/utils.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { filter } from "fuzzaldrin";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const useChannelsSearch = function<T extends { name: string }>(
|
||||||
|
channels: T[]
|
||||||
|
) {
|
||||||
|
const [query, onQueryChange] = React.useState("");
|
||||||
|
const filteredChannels = filter<T, "name">(channels, query, { key: "name" });
|
||||||
|
|
||||||
|
return { query, onQueryChange, filteredChannels };
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { makeStyles } from "@material-ui/core";
|
||||||
|
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 React from "react";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
label: {
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
"&:last-child": {
|
||||||
|
"& hr": {
|
||||||
|
display: "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
margin: theme.spacing(1, 0)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelsAvailabilityContent" }
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ChannelsAvailabilityContentProps {
|
||||||
|
isChannelSelected: (channel: Channel) => boolean;
|
||||||
|
channels: Channel[];
|
||||||
|
onChange: (option: Channel) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelsAvailabilityContent: React.FC<ChannelsAvailabilityContentProps> = ({
|
||||||
|
isChannelSelected,
|
||||||
|
channels,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{channels.map(option => (
|
||||||
|
<div
|
||||||
|
key={option.id}
|
||||||
|
className={classes.option}
|
||||||
|
data-test-id="channel-row"
|
||||||
|
>
|
||||||
|
<ControlledCheckbox
|
||||||
|
checked={isChannelSelected(option)}
|
||||||
|
name={option.name}
|
||||||
|
label={
|
||||||
|
<Typography className={classes.label}>{option.name}</Typography>
|
||||||
|
}
|
||||||
|
onChange={() => onChange(option)}
|
||||||
|
/>
|
||||||
|
<Hr />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsAvailabilityContent;
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ChannelsAvailabilityDialogChannelsList";
|
||||||
|
export { default } from "./ChannelsAvailabilityDialogChannelsList";
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { makeStyles } from "@material-ui/core";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
||||||
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import Label from "@saleor/orders/components/OrderHistory/Label";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { defineMessages, FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notFound: {
|
||||||
|
paddingBottom: theme.spacing(2)
|
||||||
|
},
|
||||||
|
scrollArea: {
|
||||||
|
maxHeight: 400,
|
||||||
|
overflowY: "scroll"
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
marginBottom: 5
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ChannelsAvailabilityContent" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
selectTitle: {
|
||||||
|
defaultMessage:
|
||||||
|
"Select channels you want for {contentType} to be available on",
|
||||||
|
description: "select title"
|
||||||
|
},
|
||||||
|
selectAllChannelsLabel: {
|
||||||
|
defaultMessage: "Select All Channels",
|
||||||
|
description: "select all channels label"
|
||||||
|
},
|
||||||
|
channelsAlphabeticallyTitle: {
|
||||||
|
defaultMessage: "Channels from A to Z",
|
||||||
|
description: "channels alphabetically title"
|
||||||
|
},
|
||||||
|
notFoundTitle: {
|
||||||
|
defaultMessage: "No Channels Found",
|
||||||
|
description: "no channels found title"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ChannelsAvailabilityContentProps {
|
||||||
|
contentType?: string;
|
||||||
|
toggleAll?: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
toggleAllLabel?: React.ReactNode;
|
||||||
|
query: string;
|
||||||
|
onQueryChange: (query: string) => void;
|
||||||
|
hasAnyChannelsToDisplay: boolean;
|
||||||
|
hasAllSelected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelsAvailabilityContentWrapper: React.FC<ChannelsAvailabilityContentProps> = ({
|
||||||
|
contentType = "",
|
||||||
|
toggleAll,
|
||||||
|
toggleAllLabel,
|
||||||
|
children,
|
||||||
|
hasAnyChannelsToDisplay,
|
||||||
|
query,
|
||||||
|
onQueryChange,
|
||||||
|
hasAllSelected
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
const intl = useIntl();
|
||||||
|
const searchText = intl.formatMessage({
|
||||||
|
defaultMessage: "Search through channels"
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.content}>
|
||||||
|
{!!contentType && (
|
||||||
|
<Typography className={classes.text} variant="caption">
|
||||||
|
<FormattedMessage {...messages.selectTitle} />
|
||||||
|
</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={hasAllSelected}
|
||||||
|
name="allChannels"
|
||||||
|
label={
|
||||||
|
toggleAllLabel || (
|
||||||
|
<Label
|
||||||
|
text={intl.formatMessage(messages.selectAllChannelsLabel)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={toggleAll}
|
||||||
|
/>
|
||||||
|
<Hr />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Typography className={classes.contentTitle}>
|
||||||
|
<FormattedMessage {...messages.channelsAlphabeticallyTitle} />
|
||||||
|
</Typography>
|
||||||
|
<div
|
||||||
|
className={classes.scrollArea}
|
||||||
|
data-test-id="manage-products-channels-availiability-list"
|
||||||
|
>
|
||||||
|
{hasAnyChannelsToDisplay ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<div className={classes.notFound}>
|
||||||
|
<FormattedMessage {...messages.notFoundTitle} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelsAvailabilityContentWrapper;
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ChannelsAvailabilityDialogWrapper";
|
||||||
|
export { default } from "./ChannelsAvailabilityDialogWrapper";
|
|
@ -8,6 +8,7 @@ interface ControlledCheckboxProps {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
checkedIcon?: React.ReactNode;
|
||||||
onChange(event: any);
|
onChange(event: any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +18,14 @@ export const ControlledCheckbox: React.FC<ControlledCheckboxProps> = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
|
checkedIcon,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
checkedIcon={checkedIcon}
|
||||||
checked={!!checked}
|
checked={!!checked}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
9
src/components/DeletableItem/DeletableItem.stories.tsx
Normal file
9
src/components/DeletableItem/DeletableItem.stories.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import DeletableItem from "./DeletableItem";
|
||||||
|
|
||||||
|
storiesOf("Generics / Deletable Item", module)
|
||||||
|
.addDecorator(CommonDecorator)
|
||||||
|
.add("default", () => <DeletableItem id="1" onDelete={() => undefined} />);
|
35
src/components/DeletableItem/DeletableItem.tsx
Normal file
35
src/components/DeletableItem/DeletableItem.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import TrashIcon from "@saleor/icons/Trash";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
container: {
|
||||||
|
cursor: "pointer",
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "DeletableItem" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface DeletableItemProps {
|
||||||
|
onDelete: (id: string) => void;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeletableItem: React.FC<DeletableItemProps> = ({ onDelete, id }) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
const handleDelete = () => onDelete(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container} onClick={handleDelete}>
|
||||||
|
<TrashIcon />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeletableItem;
|
2
src/components/DeletableItem/index.tsx
Normal file
2
src/components/DeletableItem/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./DeletableItem";
|
||||||
|
export { default } from "./DeletableItem";
|
|
@ -31,7 +31,7 @@ const Story: React.FC<Partial<
|
||||||
SingleAutocompleteSelectFieldProps & {
|
SingleAutocompleteSelectFieldProps & {
|
||||||
enableLoadMore: boolean;
|
enableLoadMore: boolean;
|
||||||
}
|
}
|
||||||
>> = ({ allowCustomValues, emptyOption, enableLoadMore }) => {
|
>> = ({ allowCustomValues, emptyOption, enableLoadMore, nakedInput }) => {
|
||||||
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
const [displayValue, setDisplayValue] = React.useState(suggestions[0].label);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -59,6 +59,7 @@ const Story: React.FC<Partial<
|
||||||
onFetchMore={enableLoadMore ? onFetchMore : undefined}
|
onFetchMore={enableLoadMore ? onFetchMore : undefined}
|
||||||
allowCustomValues={allowCustomValues}
|
allowCustomValues={allowCustomValues}
|
||||||
emptyOption={emptyOption}
|
emptyOption={emptyOption}
|
||||||
|
nakedInput={nakedInput}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -104,6 +105,7 @@ storiesOf("Generics / Select with autocomplete", module)
|
||||||
.add("no data", () => (
|
.add("no data", () => (
|
||||||
<SingleAutocompleteSelectFieldContent {...contentProps} choices={[]} />
|
<SingleAutocompleteSelectFieldContent {...contentProps} choices={[]} />
|
||||||
))
|
))
|
||||||
|
.add("naked", () => <Story nakedInput />)
|
||||||
.add("interactive", () => <Story />)
|
.add("interactive", () => <Story />)
|
||||||
.add("interactive with custom option", () => (
|
.add("interactive with custom option", () => (
|
||||||
<Story allowCustomValues={true} />
|
<Story allowCustomValues={true} />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { InputProps } from "@material-ui/core/Input";
|
import { InputProps } from "@material-ui/core/Input";
|
||||||
|
import InputBase from "@material-ui/core/InputBase";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import { ExtendedFormHelperTextProps } from "@saleor/channels/components/ChannelForm/types";
|
import { ExtendedFormHelperTextProps } from "@saleor/channels/components/ChannelForm/types";
|
||||||
import { makeStyles } from "@saleor/theme";
|
import { makeStyles } from "@saleor/theme";
|
||||||
|
@ -16,12 +17,15 @@ import SingleAutocompleteSelectFieldContent, {
|
||||||
} from "./SingleAutocompleteSelectFieldContent";
|
} from "./SingleAutocompleteSelectFieldContent";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
{
|
theme => ({
|
||||||
container: {
|
container: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
position: "relative"
|
position: "relative"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
nakedInput: {
|
||||||
|
padding: theme.spacing(2, 3)
|
||||||
|
}
|
||||||
|
}),
|
||||||
{ name: "SingleAutocompleteSelectField" }
|
{ name: "SingleAutocompleteSelectField" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -44,6 +48,7 @@ export interface SingleAutocompleteSelectFieldProps
|
||||||
fetchChoices?: (value: string) => void;
|
fetchChoices?: (value: string) => void;
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
FormHelperTextProps?: ExtendedFormHelperTextProps;
|
FormHelperTextProps?: ExtendedFormHelperTextProps;
|
||||||
|
nakedInput?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
||||||
|
@ -72,6 +77,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
onChange,
|
onChange,
|
||||||
onFetchMore,
|
onFetchMore,
|
||||||
FormHelperTextProps,
|
FormHelperTextProps,
|
||||||
|
nakedInput = false,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
@ -157,13 +163,9 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
ensureProperValues();
|
ensureProperValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const TextFieldComponent = nakedInput ? InputBase : TextField;
|
||||||
<div
|
|
||||||
className={classNames(classes.container, className)}
|
const commonInputProps = {
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<TextField
|
|
||||||
InputProps={{
|
|
||||||
...InputProps,
|
...InputProps,
|
||||||
...getInputProps({
|
...getInputProps({
|
||||||
placeholder
|
placeholder
|
||||||
|
@ -177,7 +179,25 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
id: undefined,
|
id: undefined,
|
||||||
onBlur: handleBlur,
|
onBlur: handleBlur,
|
||||||
onClick: toggleMenu
|
onClick: toggleMenu
|
||||||
}}
|
};
|
||||||
|
|
||||||
|
const nakedInputProps = nakedInput
|
||||||
|
? {
|
||||||
|
"aria-label": "naked",
|
||||||
|
...commonInputProps,
|
||||||
|
autoFocus: true,
|
||||||
|
className: classes.nakedInput
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(classes.container, className)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<TextFieldComponent
|
||||||
|
{...nakedInputProps}
|
||||||
|
InputProps={commonInputProps}
|
||||||
error={error}
|
error={error}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
helperText={helperText}
|
helperText={helperText}
|
||||||
|
|
84
src/components/TableCellAvatar/Avatar.tsx
Normal file
84
src/components/TableCellAvatar/Avatar.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { makeStyles } from "@material-ui/core";
|
||||||
|
import MuiAvatar from "@material-ui/core/Avatar";
|
||||||
|
import Cached from "@material-ui/icons/Cached";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Image from "../../icons/Image";
|
||||||
|
|
||||||
|
export const AVATAR_MARGIN = 32;
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
alignRight: {
|
||||||
|
justifyContent: "flex-end"
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
background: "none",
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
borderRadius: 2,
|
||||||
|
color: "#bdbdbd",
|
||||||
|
display: "inline-flex",
|
||||||
|
padding: theme.spacing(0.5)
|
||||||
|
},
|
||||||
|
children: {
|
||||||
|
alignSelf: "center",
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
"&:not(first-child)": {
|
||||||
|
paddingLeft: 0
|
||||||
|
},
|
||||||
|
paddingRight: theme.spacing(3),
|
||||||
|
width: "1%"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "Avatar" }
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface AvatarProps {
|
||||||
|
thumbnail?: string;
|
||||||
|
alignRight?: boolean;
|
||||||
|
avatarProps?: string;
|
||||||
|
children?: React.ReactNode | React.ReactNodeArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Avatar: React.FC<AvatarProps> = ({
|
||||||
|
children,
|
||||||
|
alignRight,
|
||||||
|
thumbnail,
|
||||||
|
avatarProps
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(classes.content, {
|
||||||
|
[classes.alignRight]: alignRight
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{thumbnail === undefined ? (
|
||||||
|
<MuiAvatar className={classNames(classes.avatar, avatarProps)}>
|
||||||
|
<Cached color="primary" />
|
||||||
|
</MuiAvatar>
|
||||||
|
) : thumbnail === null ? (
|
||||||
|
<MuiAvatar className={classNames(classes.avatar, avatarProps)}>
|
||||||
|
<Image color="primary" />
|
||||||
|
</MuiAvatar>
|
||||||
|
) : (
|
||||||
|
<MuiAvatar
|
||||||
|
className={classNames(classes.avatar, avatarProps)}
|
||||||
|
src={thumbnail}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!alignRight && <div className={classes.children}>{children}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar;
|
|
@ -1,36 +1,12 @@
|
||||||
import Avatar from "@material-ui/core/Avatar";
|
|
||||||
import TableCell, { TableCellProps } from "@material-ui/core/TableCell";
|
import TableCell, { TableCellProps } from "@material-ui/core/TableCell";
|
||||||
import Cached from "@material-ui/icons/Cached";
|
|
||||||
import { makeStyles } from "@saleor/theme";
|
import { makeStyles } from "@saleor/theme";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Image from "../../icons/Image";
|
import Avatar, { AvatarProps } from "./Avatar";
|
||||||
|
|
||||||
export const AVATAR_MARGIN = 32;
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
alignRight: {
|
|
||||||
justifyContent: "flex-end"
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
background: "none",
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
borderRadius: 2,
|
|
||||||
color: "#bdbdbd",
|
|
||||||
display: "inline-flex",
|
|
||||||
padding: theme.spacing(0.5)
|
|
||||||
},
|
|
||||||
children: {
|
|
||||||
alignSelf: "center",
|
|
||||||
marginLeft: theme.spacing(2),
|
|
||||||
width: "100%"
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex"
|
|
||||||
},
|
|
||||||
root: {
|
root: {
|
||||||
"&:not(first-child)": {
|
"&:not(first-child)": {
|
||||||
paddingLeft: 0
|
paddingLeft: 0
|
||||||
|
@ -42,51 +18,20 @@ const useStyles = makeStyles(
|
||||||
{ name: "TableCellAvatar" }
|
{ name: "TableCellAvatar" }
|
||||||
);
|
);
|
||||||
|
|
||||||
interface TableCellAvatarProps extends TableCellProps {
|
interface TableCellAvatarProps extends AvatarProps, TableCellProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
thumbnail?: string;
|
|
||||||
alignRight?: boolean;
|
|
||||||
avatarProps?: string;
|
|
||||||
children?: React.ReactNode | React.ReactNodeArray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableCellAvatar: React.FC<TableCellAvatarProps> = props => {
|
const TableCellAvatar: React.FC<TableCellAvatarProps> = props => {
|
||||||
const {
|
const { className, ...rest } = props;
|
||||||
children,
|
|
||||||
className,
|
|
||||||
alignRight,
|
|
||||||
thumbnail,
|
|
||||||
avatarProps,
|
|
||||||
...rest
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCell className={classNames(classes.root, className)} {...rest}>
|
<TableCell className={classNames(classes.root, className)} {...rest}>
|
||||||
<div
|
<Avatar {...rest} />
|
||||||
className={classNames(classes.content, {
|
|
||||||
[classes.alignRight]: alignRight
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{thumbnail === undefined ? (
|
|
||||||
<Avatar className={classNames(classes.avatar, avatarProps)}>
|
|
||||||
<Cached color="primary" />
|
|
||||||
</Avatar>
|
|
||||||
) : thumbnail === null ? (
|
|
||||||
<Avatar className={classNames(classes.avatar, avatarProps)}>
|
|
||||||
<Image color="primary" />
|
|
||||||
</Avatar>
|
|
||||||
) : (
|
|
||||||
<Avatar
|
|
||||||
className={classNames(classes.avatar, avatarProps)}
|
|
||||||
src={thumbnail}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!alignRight && <div className={classes.children}>{children}</div>}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
TableCellAvatar.displayName = "TableCellAvatar";
|
|
||||||
export default TableCellAvatar;
|
export default TableCellAvatar;
|
||||||
|
|
|
@ -11,9 +11,8 @@ import { ChannelsAvailabilityDropdown } from "@saleor/components/ChannelsAvailab
|
||||||
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 TableCellAvatar, {
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
AVATAR_MARGIN
|
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
||||||
} from "@saleor/components/TableCellAvatar";
|
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
import { makeStyles } from "@saleor/theme";
|
import { makeStyles } from "@saleor/theme";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChannelSaleData } from "@saleor/channels/utils";
|
import { ChannelSaleData } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import ChannelsAvailability from "@saleor/components/ChannelsAvailability";
|
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
@ -117,7 +117,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ChannelsAvailability
|
<ChannelsAvailabilityCard
|
||||||
selectedChannelsCount={data.channelListings.length}
|
selectedChannelsCount={data.channelListings.length}
|
||||||
allChannelsCount={allChannelsCount}
|
allChannelsCount={allChannelsCount}
|
||||||
channelsList={data.channelListings.map(channel => ({
|
channelsList={data.channelListings.map(channel => ({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChannelSaleData } from "@saleor/channels/utils";
|
import { ChannelSaleData } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import ChannelsAvailability from "@saleor/components/ChannelsAvailability";
|
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
@ -293,7 +293,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
||||||
sale={sale}
|
sale={sale}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<ChannelsAvailability
|
<ChannelsAvailabilityCard
|
||||||
selectedChannelsCount={data.channelListings.length}
|
selectedChannelsCount={data.channelListings.length}
|
||||||
allChannelsCount={allChannelsCount}
|
allChannelsCount={allChannelsCount}
|
||||||
channelsList={data.channelListings.map(channel => ({
|
channelsList={data.channelListings.map(channel => ({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChannelVoucherData } from "@saleor/channels/utils";
|
import { ChannelVoucherData } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import ChannelsAvailability from "@saleor/components/ChannelsAvailability";
|
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
@ -170,7 +170,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ChannelsAvailability
|
<ChannelsAvailabilityCard
|
||||||
selectedChannelsCount={data.channelListings.length}
|
selectedChannelsCount={data.channelListings.length}
|
||||||
allChannelsCount={allChannelsCount}
|
allChannelsCount={allChannelsCount}
|
||||||
channelsList={data.channelListings.map(channel => ({
|
channelsList={data.channelListings.map(channel => ({
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Typography from "@material-ui/core/Typography";
|
||||||
import { ChannelVoucherData } from "@saleor/channels/utils";
|
import { ChannelVoucherData } from "@saleor/channels/utils";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import ChannelsAvailability from "@saleor/components/ChannelsAvailability";
|
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
|
||||||
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 CountryList from "@saleor/components/CountryList";
|
import CountryList from "@saleor/components/CountryList";
|
||||||
|
@ -394,7 +394,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
||||||
selectedChannelId={selectedChannelId}
|
selectedChannelId={selectedChannelId}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<ChannelsAvailability
|
<ChannelsAvailabilityCard
|
||||||
selectedChannelsCount={data.channelListings.length}
|
selectedChannelsCount={data.channelListings.length}
|
||||||
allChannelsCount={allChannelsCount}
|
allChannelsCount={allChannelsCount}
|
||||||
channelsList={data.channelListings.map(channel => ({
|
channelsList={data.channelListings.map(channel => ({
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { shippingZoneFragment } from "./shipping";
|
||||||
|
|
||||||
export const channelErrorFragment = gql`
|
export const channelErrorFragment = gql`
|
||||||
fragment ChannelErrorFragment on ChannelError {
|
fragment ChannelErrorFragment on ChannelError {
|
||||||
code
|
code
|
||||||
|
@ -20,9 +22,12 @@ export const channelFragment = gql`
|
||||||
|
|
||||||
export const channelDetailsFragment = gql`
|
export const channelDetailsFragment = gql`
|
||||||
${channelFragment}
|
${channelFragment}
|
||||||
|
${shippingZoneFragment}
|
||||||
fragment ChannelDetailsFragment on Channel {
|
fragment ChannelDetailsFragment on Channel {
|
||||||
...ChannelFragment
|
...ChannelFragment
|
||||||
hasOrders
|
hasOrders
|
||||||
|
shippingZones {
|
||||||
|
...ShippingZoneFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -202,6 +202,9 @@ export const productFragmentDetails = gql`
|
||||||
sku
|
sku
|
||||||
name
|
name
|
||||||
margin
|
margin
|
||||||
|
media {
|
||||||
|
url(size: 200)
|
||||||
|
}
|
||||||
stocks {
|
stocks {
|
||||||
...StockFragment
|
...StockFragment
|
||||||
}
|
}
|
||||||
|
@ -256,6 +259,7 @@ export const selectedVariantAttributeFragment = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const fragmentVariant = gql`
|
export const fragmentVariant = gql`
|
||||||
|
${fragmentProductMedia}
|
||||||
${selectedVariantAttributeFragment}
|
${selectedVariantAttributeFragment}
|
||||||
${priceRangeFragment}
|
${priceRangeFragment}
|
||||||
${fragmentProductMedia}
|
${fragmentProductMedia}
|
||||||
|
|
|
@ -7,6 +7,34 @@
|
||||||
// GraphQL fragment: ChannelDetailsFragment
|
// GraphQL fragment: ChannelDetailsFragment
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ChannelDetailsFragment_shippingZones_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDetailsFragment_shippingZones_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDetailsFragment_shippingZones_countries {
|
||||||
|
__typename: "CountryDisplay";
|
||||||
|
code: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelDetailsFragment_shippingZones {
|
||||||
|
__typename: "ShippingZone";
|
||||||
|
metadata: (ChannelDetailsFragment_shippingZones_metadata | null)[];
|
||||||
|
privateMetadata: (ChannelDetailsFragment_shippingZones_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
countries: (ChannelDetailsFragment_shippingZones_countries | null)[] | null;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChannelDetailsFragment {
|
export interface ChannelDetailsFragment {
|
||||||
__typename: "Channel";
|
__typename: "Channel";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -15,4 +43,5 @@ export interface ChannelDetailsFragment {
|
||||||
slug: string;
|
slug: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
hasOrders: boolean;
|
hasOrders: boolean;
|
||||||
|
shippingZones: ChannelDetailsFragment_shippingZones[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,11 @@ export interface Product_media {
|
||||||
oembedData: any;
|
oembedData: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Product_variants_media {
|
||||||
|
__typename: "ProductMedia";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Product_variants_stocks_warehouse {
|
export interface Product_variants_stocks_warehouse {
|
||||||
__typename: "Warehouse";
|
__typename: "Warehouse";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -232,6 +237,7 @@ export interface Product_variants {
|
||||||
sku: string;
|
sku: string;
|
||||||
name: string;
|
name: string;
|
||||||
margin: number | null;
|
margin: number | null;
|
||||||
|
media: Product_variants_media[] | null;
|
||||||
stocks: (Product_variants_stocks | null)[] | null;
|
stocks: (Product_variants_stocks | null)[] | null;
|
||||||
trackInventory: boolean;
|
trackInventory: boolean;
|
||||||
channelListings: Product_variants_channelListings[] | null;
|
channelListings: Product_variants_channelListings[] | null;
|
||||||
|
|
|
@ -124,5 +124,5 @@ export interface ShippingZoneDetailsFragment {
|
||||||
description: string | null;
|
description: string | null;
|
||||||
default: boolean;
|
default: boolean;
|
||||||
shippingMethods: (ShippingZoneDetailsFragment_shippingMethods | null)[] | null;
|
shippingMethods: (ShippingZoneDetailsFragment_shippingMethods | null)[] | null;
|
||||||
warehouses: (ShippingZoneDetailsFragment_warehouses | null)[] | null;
|
warehouses: ShippingZoneDetailsFragment_warehouses[];
|
||||||
}
|
}
|
||||||
|
|
2
src/hooks/makeTopLevelSearch/index.ts
Normal file
2
src/hooks/makeTopLevelSearch/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./makeTopLevelSearch";
|
||||||
|
export * from "./makeTopLevelSearch";
|
|
@ -1,7 +1,7 @@
|
||||||
import { PageInfoFragment } from "@saleor/fragments/types/PageInfoFragment";
|
import { PageInfoFragment } from "@saleor/fragments/types/PageInfoFragment";
|
||||||
import { DocumentNode } from "graphql";
|
import { DocumentNode } from "graphql";
|
||||||
|
|
||||||
import makeSearch, { SearchVariables, UseSearchHook } from "./makeSearch";
|
import makeSearch, { SearchVariables, UseSearchHook } from "../makeSearch";
|
||||||
|
|
||||||
export interface SearchData {
|
export interface SearchData {
|
||||||
search: {
|
search: {
|
||||||
|
@ -12,6 +12,10 @@ export interface SearchData {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResultSearchData {
|
||||||
|
data: SearchData;
|
||||||
|
}
|
||||||
|
|
||||||
function makeTopLevelSearch<
|
function makeTopLevelSearch<
|
||||||
TData extends SearchData,
|
TData extends SearchData,
|
||||||
TVariables extends SearchVariables
|
TVariables extends SearchVariables
|
12
src/hooks/makeTopLevelSearch/types.ts
Normal file
12
src/hooks/makeTopLevelSearch/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export interface CommonSearchOpts {
|
||||||
|
onLoadMore?: () => void;
|
||||||
|
loading?: boolean;
|
||||||
|
data?: {
|
||||||
|
search?: {
|
||||||
|
totalCount?: number;
|
||||||
|
pageInfo?: {
|
||||||
|
hasNextPage: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
17
src/hooks/makeTopLevelSearch/utils.ts
Normal file
17
src/hooks/makeTopLevelSearch/utils.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
|
||||||
|
import { ResultSearchData } from "./makeTopLevelSearch";
|
||||||
|
import { CommonSearchOpts } from "./types";
|
||||||
|
|
||||||
|
export const getSearchFetchMoreProps = (
|
||||||
|
{ data, loading }: CommonSearchOpts,
|
||||||
|
onFetchMore: any
|
||||||
|
): FetchMoreProps => ({
|
||||||
|
hasMore: !!data?.search?.pageInfo?.hasNextPage,
|
||||||
|
totalCount: data?.search?.totalCount,
|
||||||
|
loading: !!loading,
|
||||||
|
onFetchMore
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getParsedSearchData = ({ data }: ResultSearchData) =>
|
||||||
|
data?.search?.edges?.map(({ node }) => node) || [];
|
|
@ -2,7 +2,9 @@ import { LocaleContext } from "@saleor/components/Locale";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
function useDateLocalize(): (date: string, format?: string) => string {
|
export type LocalizeDate = (date: string, format?: string) => string;
|
||||||
|
|
||||||
|
function useDateLocalize(): LocalizeDate {
|
||||||
const { locale } = useContext(LocaleContext);
|
const { locale } = useContext(LocaleContext);
|
||||||
|
|
||||||
return (date: string, format?: string) =>
|
return (date: string, format?: string) =>
|
||||||
|
|
|
@ -100,11 +100,12 @@ function useForm<T extends FormData>(
|
||||||
setData(initial);
|
setData(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(newData: Partial<T>) {
|
function set(newData: Partial<T>, setHasChanged = true) {
|
||||||
setData(data => ({
|
setData(data => ({
|
||||||
...data,
|
...data,
|
||||||
...newData
|
...newData
|
||||||
}));
|
}));
|
||||||
|
setChanged(setHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
|
|
2
src/hooks/useNotifier/index.ts
Normal file
2
src/hooks/useNotifier/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./useNotifier";
|
||||||
|
export * from "./useNotifier";
|
18
src/hooks/useNotifier/utils.ts
Normal file
18
src/hooks/useNotifier/utils.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { IMessage } from "@saleor/components/messages";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import commonErrorMessages from "@saleor/utils/errors/common";
|
||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
|
export const getDefaultNotifierSuccessErrorData = (
|
||||||
|
errors: any[],
|
||||||
|
intl: IntlShape
|
||||||
|
): IMessage =>
|
||||||
|
!errors.length
|
||||||
|
? {
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonErrorMessages.unknownError)
|
||||||
|
};
|
12
src/icons/CheckboxSemiChecked.tsx
Normal file
12
src/icons/CheckboxSemiChecked.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const CheckboxSemiChecked = createSvgIcon(
|
||||||
|
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="5" y="5" width="14" height="14" stroke="#06847B" />
|
||||||
|
<rect x="8" y="11" width="8" height="2" fill="#06847B" />
|
||||||
|
</svg>,
|
||||||
|
"CheckboxSemiChecked"
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CheckboxSemiChecked;
|
25
src/icons/ChevronDown.tsx
Normal file
25
src/icons/ChevronDown.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import useTheme from "@saleor/hooks/useTheme";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const ChevronDown: React.FC = () => {
|
||||||
|
const { isDark } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="7"
|
||||||
|
viewBox="0 0 10 7"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1 1L5 5L9 1"
|
||||||
|
stroke={isDark ? "#FAFAFA" : "#28234A"}
|
||||||
|
stroke-opacity="0.4"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChevronDown;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue