Migrate product create page to new MacawUI (#3372)
Co-authored-by: Michał Droń <droniu@droniu.dev> Co-authored-by: timur <timuric@gmail.com> Co-authored-by: wojteknowacki <wojciech.nowacki@saleor.io>
This commit is contained in:
parent
4aa648353e
commit
180e3c56e6
102 changed files with 1589 additions and 2215 deletions
|
@ -263,13 +263,15 @@ describe("Tests for customer", () => {
|
||||||
createCustomer(email, randomName, address, true).then(({ user }) => {
|
createCustomer(email, randomName, address, true).then(({ user }) => {
|
||||||
cy.visit(customerDetailsUrl(user.id))
|
cy.visit(customerDetailsUrl(user.id))
|
||||||
.get(CUSTOMER_DETAILS_SELECTORS.nameInput)
|
.get(CUSTOMER_DETAILS_SELECTORS.nameInput)
|
||||||
.clearAndType(updatedName)
|
.clear()
|
||||||
|
.type(updatedName)
|
||||||
.get(CUSTOMER_DETAILS_SELECTORS.lastNameInput)
|
.get(CUSTOMER_DETAILS_SELECTORS.lastNameInput)
|
||||||
.clearAndType(updatedName)
|
.clearAndType(updatedName)
|
||||||
.get(CUSTOMER_DETAILS_SELECTORS.noteInput)
|
.get(CUSTOMER_DETAILS_SELECTORS.noteInput)
|
||||||
.clearAndType(updatedName)
|
.clearAndType(updatedName)
|
||||||
.get(CUSTOMER_DETAILS_SELECTORS.emailInput)
|
.get(CUSTOMER_DETAILS_SELECTORS.emailInput)
|
||||||
.clearAndType(`${updatedName}@example.com`)
|
.clear()
|
||||||
|
.type(`${updatedName}@example.com`)
|
||||||
.addAliasToGraphRequest("UpdateCustomer")
|
.addAliasToGraphRequest("UpdateCustomer")
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
.click()
|
.click()
|
||||||
|
|
|
@ -193,7 +193,9 @@ describe("As an admin I want to manage warehouses", () => {
|
||||||
warehouse = warehouseResp;
|
warehouse = warehouseResp;
|
||||||
cy.visit(warehouseDetailsUrl(warehouse.id))
|
cy.visit(warehouseDetailsUrl(warehouse.id))
|
||||||
.get(WAREHOUSES_DETAILS.nameInput)
|
.get(WAREHOUSES_DETAILS.nameInput)
|
||||||
.clearAndType(updatedName)
|
.clear()
|
||||||
|
.type(updatedName)
|
||||||
|
// .clearAndType(updatedName)
|
||||||
.fillUpBasicAddress(secondUsAddress)
|
.fillUpBasicAddress(secondUsAddress)
|
||||||
.addAliasToGraphRequest("WarehouseUpdate")
|
.addAliasToGraphRequest("WarehouseUpdate")
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
|
|
|
@ -162,7 +162,9 @@ describe("Tests for pages", () => {
|
||||||
.then(({ page }) => {
|
.then(({ page }) => {
|
||||||
cy.visit(pageDetailsUrl(page.id))
|
cy.visit(pageDetailsUrl(page.id))
|
||||||
.get(PAGE_DETAILS_SELECTORS.nameInput)
|
.get(PAGE_DETAILS_SELECTORS.nameInput)
|
||||||
.clearAndType(updatedName)
|
.clear()
|
||||||
|
.type(updatedName)
|
||||||
|
// .clearAndType(updatedName)
|
||||||
.get(PAGE_DETAILS_SELECTORS.isNotPublishedCheckbox)
|
.get(PAGE_DETAILS_SELECTORS.isNotPublishedCheckbox)
|
||||||
.click()
|
.click()
|
||||||
.addAliasToGraphRequest("PageUpdate")
|
.addAliasToGraphRequest("PageUpdate")
|
||||||
|
|
|
@ -192,7 +192,6 @@ describe("Updating products without sku", () => {
|
||||||
.get(SHARED_ELEMENTS.skeleton)
|
.get(SHARED_ELEMENTS.skeleton)
|
||||||
.should("not.exist")
|
.should("not.exist")
|
||||||
.get(VARIANTS_SELECTORS.skuTextField)
|
.get(VARIANTS_SELECTORS.skuTextField)
|
||||||
.find("input")
|
|
||||||
.clear()
|
.clear()
|
||||||
.addAliasToGraphRequest("VariantUpdate")
|
.addAliasToGraphRequest("VariantUpdate")
|
||||||
.get(VARIANTS_SELECTORS.variantNameInput)
|
.get(VARIANTS_SELECTORS.variantNameInput)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const AVAILABLE_CHANNELS_FORM = {
|
export const AVAILABLE_CHANNELS_FORM = {
|
||||||
manageChannelsButton: "[data-test-id='channels-availability-manage-button']",
|
manageChannelsButton: "[data-test-id='channels-availability-manage-button']",
|
||||||
assignedChannels: "[data-test-id='expand-icon']",
|
assignedChannels: "[data-test-id='expand-icon']",
|
||||||
publishedRadioButtons: "[name*='isPublished']",
|
publishedRadioButtons: "[name*='isPublished'] > ",
|
||||||
availableForPurchaseRadioButtons: "[name*='isAvailableForPurchase']",
|
availableForPurchaseRadioButtons: "[name*='isAvailableForPurchase']",
|
||||||
radioButtonsValueTrue: "[value='true']",
|
radioButtonsValueTrue: "[value='true']",
|
||||||
radioButtonsValueFalse: "[value='false']",
|
radioButtonsValueFalse: "[value='false']",
|
||||||
|
|
|
@ -6,6 +6,6 @@ export const PAGE_DETAILS_SELECTORS = {
|
||||||
isNotPublishedCheckbox: '[name="isPublished"][value=false]',
|
isNotPublishedCheckbox: '[name="isPublished"][value=false]',
|
||||||
uploadFileButton: '[data-test-id="button-upload-file"]',
|
uploadFileButton: '[data-test-id="button-upload-file"]',
|
||||||
richTextEditorAttributeValue: '[class*="ce-paragraph"]',
|
richTextEditorAttributeValue: '[class*="ce-paragraph"]',
|
||||||
booleanAttributeValueCheckbox: '[name*="attribute:"][type="checkbox"]',
|
booleanAttributeValueCheckbox: '[role="checkbox"]',
|
||||||
numericAttributeValueInput: '[name*="attribute:"]',
|
numericAttributeValueInput: '[name*="attribute:"]',
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ Cypress.Commands.add("clearAndType", { prevSubject: true }, (subject, text) => {
|
||||||
if (subject.find("[contenteditable]").length > 0) {
|
if (subject.find("[contenteditable]").length > 0) {
|
||||||
cy.wrap(subject).find("[contenteditable]").clear().type(text);
|
cy.wrap(subject).find("[contenteditable]").clear().type(text);
|
||||||
} else {
|
} else {
|
||||||
cy.wrap(subject).clear().type(text);
|
cy.wrap(subject).clear({ force: true }).type(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ export function createAttributeWithInputType({
|
||||||
entityType,
|
entityType,
|
||||||
numericSystemType,
|
numericSystemType,
|
||||||
swatchImage,
|
swatchImage,
|
||||||
valueRequired = true
|
valueRequired = true,
|
||||||
}) {
|
}) {
|
||||||
fillUpAttributeCreateFields({ name, attributeType, valueRequired });
|
fillUpAttributeCreateFields({ name, attributeType, valueRequired });
|
||||||
if (
|
if (
|
||||||
|
@ -34,7 +34,7 @@ export function createAttributeWithInputType({
|
||||||
export function fillUpAttributeCreateFields({
|
export function fillUpAttributeCreateFields({
|
||||||
name,
|
name,
|
||||||
attributeType,
|
attributeType,
|
||||||
valueRequired
|
valueRequired,
|
||||||
}) {
|
}) {
|
||||||
fillUpAttributeNameAndCode(name);
|
fillUpAttributeNameAndCode(name);
|
||||||
cy.get(ATTRIBUTES_DETAILS.inputTypeSelect)
|
cy.get(ATTRIBUTES_DETAILS.inputTypeSelect)
|
||||||
|
@ -49,9 +49,11 @@ export function fillUpAttributeCreateFields({
|
||||||
export function fillUpAttributeNameAndCode(name, code = name) {
|
export function fillUpAttributeNameAndCode(name, code = name) {
|
||||||
return cy
|
return cy
|
||||||
.get(ATTRIBUTES_DETAILS.nameInput)
|
.get(ATTRIBUTES_DETAILS.nameInput)
|
||||||
.clearAndType(name)
|
.clear()
|
||||||
|
.type(name)
|
||||||
.get(ATTRIBUTES_DETAILS.codeInput)
|
.get(ATTRIBUTES_DETAILS.codeInput)
|
||||||
.clearAndType(code);
|
.clear()
|
||||||
|
.type(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveAttribute() {
|
export function saveAttribute() {
|
||||||
|
@ -63,9 +65,7 @@ export function saveAttribute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitAttribute() {
|
export function submitAttribute() {
|
||||||
cy.get(BUTTON_SELECTORS.confirm)
|
cy.get(BUTTON_SELECTORS.confirm).click().confirmationMessageShouldDisappear();
|
||||||
.click()
|
|
||||||
.confirmationMessageShouldDisappear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSingleValue(valueName) {
|
export function addSingleValue(valueName) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ export function createCollection(collectionName, isPublished, channel) {
|
||||||
.get(AVAILABLE_CHANNELS_FORM.availableChannel)
|
.get(AVAILABLE_CHANNELS_FORM.availableChannel)
|
||||||
.click()
|
.click()
|
||||||
.get(`${AVAILABLE_CHANNELS_FORM.publishedRadioButtons}${publishedSelector}`)
|
.get(`${AVAILABLE_CHANNELS_FORM.publishedRadioButtons}${publishedSelector}`)
|
||||||
|
.first()
|
||||||
.click();
|
.click();
|
||||||
return saveCollection().its("response.body.data.collectionCreate.collection");
|
return saveCollection().its("response.body.data.collectionCreate.collection");
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ export function createVariant({
|
||||||
cy.get(VARIANTS_SELECTORS.saveButton)
|
cy.get(VARIANTS_SELECTORS.saveButton)
|
||||||
.click()
|
.click()
|
||||||
.get(VARIANTS_SELECTORS.skuTextField)
|
.get(VARIANTS_SELECTORS.skuTextField)
|
||||||
.find("input")
|
|
||||||
.should("be.enabled")
|
.should("be.enabled")
|
||||||
.get(BUTTON_SELECTORS.back)
|
.get(BUTTON_SELECTORS.back)
|
||||||
.click()
|
.click()
|
||||||
|
@ -86,7 +85,7 @@ export function fillUpVariantDetails({
|
||||||
cy.get(VARIANTS_SELECTORS.variantNameInput).type(variantName);
|
cy.get(VARIANTS_SELECTORS.variantNameInput).type(variantName);
|
||||||
}
|
}
|
||||||
if (sku) {
|
if (sku) {
|
||||||
cy.get(VARIANTS_SELECTORS.skuTextField).type(sku);
|
cy.get(VARIANTS_SELECTORS.skuTextField).click({ force: true }).type(sku);
|
||||||
}
|
}
|
||||||
if (warehouseName) {
|
if (warehouseName) {
|
||||||
cy.get(VARIANTS_SELECTORS.addWarehouseButton).click();
|
cy.get(VARIANTS_SELECTORS.addWarehouseButton).click();
|
||||||
|
@ -114,7 +113,7 @@ export function fillUpVariantAttributeAndSku({ attributeName, sku }) {
|
||||||
.contains(attributeName)
|
.contains(attributeName)
|
||||||
.click();
|
.click();
|
||||||
if (sku) {
|
if (sku) {
|
||||||
cy.get(VARIANTS_SELECTORS.skuTextField).type(sku);
|
cy.get(VARIANTS_SELECTORS.skuTextField).click({ force: true }).type(sku);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ function updateProductManageInChannel(productUrl, manageSelector) {
|
||||||
.get(AVAILABLE_CHANNELS_FORM.assignedChannels)
|
.get(AVAILABLE_CHANNELS_FORM.assignedChannels)
|
||||||
.click()
|
.click()
|
||||||
.get(manageSelector)
|
.get(manageSelector)
|
||||||
|
.first()
|
||||||
.click()
|
.click()
|
||||||
.waitForProgressBarToNotBeVisible()
|
.waitForProgressBarToNotBeVisible()
|
||||||
.addAliasToGraphRequest("ProductChannelListingUpdate")
|
.addAliasToGraphRequest("ProductChannelListingUpdate")
|
||||||
|
|
|
@ -80,7 +80,9 @@ export function selectProductsOutOfStock() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectFilterBy(filter) {
|
export function selectFilterBy(filter) {
|
||||||
return showFilters().get(PRODUCTS_LIST.filters.filterBy[filter]).click();
|
return showFilters()
|
||||||
|
.get(PRODUCTS_LIST.filters.filterBy[filter])
|
||||||
|
.click({ timeout: 1000, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectFilterByAttribute(attributeSlug) {
|
export function selectFilterByAttribute(attributeSlug) {
|
||||||
|
@ -106,6 +108,8 @@ export function selectChannel(channelSlug) {
|
||||||
export function submitFilters() {
|
export function submitFilters() {
|
||||||
cy.addAliasToGraphRequest("ProductList")
|
cy.addAliasToGraphRequest("ProductList")
|
||||||
.get(BUTTON_SELECTORS.submit)
|
.get(BUTTON_SELECTORS.submit)
|
||||||
|
.scrollIntoView()
|
||||||
|
.should("be.visible")
|
||||||
.click()
|
.click()
|
||||||
.waitForRequestAndCheckIfNoErrors("@ProductList")
|
.waitForRequestAndCheckIfNoErrors("@ProductList")
|
||||||
.get(PRODUCTS_LIST.emptyProductRow)
|
.get(PRODUCTS_LIST.emptyProductRow)
|
||||||
|
|
|
@ -26,9 +26,11 @@ export function fillUpShippingZoneData({
|
||||||
channelName,
|
channelName,
|
||||||
}) {
|
}) {
|
||||||
cy.get(SHIPPING_ZONE_DETAILS.nameInput)
|
cy.get(SHIPPING_ZONE_DETAILS.nameInput)
|
||||||
.clearAndType(shippingName)
|
.clear()
|
||||||
|
.type(shippingName)
|
||||||
.get(SHIPPING_ZONE_DETAILS.descriptionInput)
|
.get(SHIPPING_ZONE_DETAILS.descriptionInput)
|
||||||
.clearAndType(shippingName)
|
.clear()
|
||||||
|
.type(shippingName)
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
.click()
|
.click()
|
||||||
.confirmationMessageShouldAppear()
|
.confirmationMessageShouldAppear()
|
||||||
|
|
|
@ -21,7 +21,9 @@ export function updateTranslationToCategory({
|
||||||
.get(SHARED_ELEMENTS.skeleton)
|
.get(SHARED_ELEMENTS.skeleton)
|
||||||
.should("not.exist")
|
.should("not.exist")
|
||||||
.get(ELEMENT_TRANSLATION.translationInputField)
|
.get(ELEMENT_TRANSLATION.translationInputField)
|
||||||
.clearAndType(translatedName)
|
.clear()
|
||||||
|
.type(translatedName)
|
||||||
|
// .clearAndType(translatedName)
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
.click()
|
.click()
|
||||||
.confirmationMessageShouldDisappear()
|
.confirmationMessageShouldDisappear()
|
||||||
|
@ -37,14 +39,17 @@ export function updateTranslationToCategory({
|
||||||
.get(ELEMENT_TRANSLATION.editSeoTitleButton)
|
.get(ELEMENT_TRANSLATION.editSeoTitleButton)
|
||||||
.click()
|
.click()
|
||||||
.get(ELEMENT_TRANSLATION.translationInputField)
|
.get(ELEMENT_TRANSLATION.translationInputField)
|
||||||
.clearAndType(translatedSeoTitle)
|
.clear()
|
||||||
|
.type(translatedSeoTitle)
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
.click()
|
.click()
|
||||||
.confirmationMessageShouldDisappear()
|
.confirmationMessageShouldDisappear()
|
||||||
.get(ELEMENT_TRANSLATION.editSeoDescriptionButton)
|
.get(ELEMENT_TRANSLATION.editSeoDescriptionButton)
|
||||||
.click()
|
.click()
|
||||||
.get(ELEMENT_TRANSLATION.translationInputField)
|
.get(ELEMENT_TRANSLATION.translationInputField)
|
||||||
.clearAndType(translatedSeoDescription)
|
.clear()
|
||||||
|
.type(translatedSeoDescription)
|
||||||
|
// .clearAndType(translatedSeoDescription)
|
||||||
.get(BUTTON_SELECTORS.confirm)
|
.get(BUTTON_SELECTORS.confirm)
|
||||||
.click()
|
.click()
|
||||||
.confirmationMessageShouldDisappear();
|
.confirmationMessageShouldDisappear();
|
||||||
|
|
|
@ -1614,6 +1614,10 @@
|
||||||
"context": "area units type",
|
"context": "area units type",
|
||||||
"string": "Area"
|
"string": "Area"
|
||||||
},
|
},
|
||||||
|
"AD1PlC": {
|
||||||
|
"context": "channels availability text",
|
||||||
|
"string": "In {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}"
|
||||||
|
},
|
||||||
"ADTNND": {
|
"ADTNND": {
|
||||||
"context": "product type",
|
"context": "product type",
|
||||||
"string": "Physical"
|
"string": "Physical"
|
||||||
|
@ -8100,10 +8104,6 @@
|
||||||
"context": "voucher discount type",
|
"context": "voucher discount type",
|
||||||
"string": "Fixed Amount"
|
"string": "Fixed Amount"
|
||||||
},
|
},
|
||||||
"vY2lpx": {
|
|
||||||
"context": "channels availability text",
|
|
||||||
"string": "Available at {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}"
|
|
||||||
},
|
|
||||||
"vZMs8f": {
|
"vZMs8f": {
|
||||||
"context": "default product variant indicator",
|
"context": "default product variant indicator",
|
||||||
"string": "Default"
|
"string": "Default"
|
||||||
|
@ -8497,6 +8497,9 @@
|
||||||
"context": "voucher requirements, header",
|
"context": "voucher requirements, header",
|
||||||
"string": "Minimum Requirements"
|
"string": "Minimum Requirements"
|
||||||
},
|
},
|
||||||
|
"yi1HSj": {
|
||||||
|
"string": "({numberOfCharacters} of {maxCharacters} characters)"
|
||||||
|
},
|
||||||
"ymo+cm": {
|
"ymo+cm": {
|
||||||
"context": "voucher discount",
|
"context": "voucher discount",
|
||||||
"string": "All products"
|
"string": "All products"
|
||||||
|
|
61
package-lock.json
generated
61
package-lock.json
generated
|
@ -27,7 +27,7 @@
|
||||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.68",
|
"@saleor/macaw-ui": "0.8.0-pre.73",
|
||||||
"@saleor/sdk": "^0.5.0",
|
"@saleor/sdk": "^0.5.0",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
@ -7976,9 +7976,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@saleor/macaw-ui": {
|
"node_modules/@saleor/macaw-ui": {
|
||||||
"version": "0.8.0-pre.68",
|
"version": "0.8.0-pre.73",
|
||||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.68.tgz",
|
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.73.tgz",
|
||||||
"integrity": "sha512-4P1Ec4dpNk0MyejtosBZLd0EHl3pOBY1aFt09/R0Qpdiyu2+O7Jh/O1pwG+cpaG7J6hcrtMGsfYfLl24PwIAAA==",
|
"integrity": "sha512-7+bFDATTV8ZjX3dpU20z5QMUMSZHoNgidLWEzUBkHWk6EgiDf+V5Mn2t8Eexd34uLOIKCQ0j4f/mVG+slRsj/w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dessert-box/react": "^0.4.0",
|
"@dessert-box/react": "^0.4.0",
|
||||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||||
|
@ -7992,14 +7992,17 @@
|
||||||
"@radix-ui/react-select": "^1.2.0",
|
"@radix-ui/react-select": "^1.2.0",
|
||||||
"@radix-ui/react-toggle": "^1.0.2",
|
"@radix-ui/react-toggle": "^1.0.2",
|
||||||
"@radix-ui/react-tooltip": "^1.0.5",
|
"@radix-ui/react-tooltip": "^1.0.5",
|
||||||
|
"@vanilla-extract/css-utils": "^0.1.3",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"downshift": "^6.1.7",
|
"downshift": "^6.1.7",
|
||||||
|
"downshift7": "npm:downshift@7.6.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"react-inlinesvg": "^3.0.1"
|
"react-inlinesvg": "^3.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 <19"
|
"node": ">=16 <19",
|
||||||
|
"pnpm": ">=8"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
@ -16986,6 +16989,27 @@
|
||||||
"react": ">=16.12.0"
|
"react": ">=16.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/downshift7": {
|
||||||
|
"name": "downshift",
|
||||||
|
"version": "7.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
|
||||||
|
"integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.14.8",
|
||||||
|
"compute-scroll-into-view": "^2.0.4",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^17.0.2",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/downshift7/node_modules/compute-scroll-into-view": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||||
|
},
|
||||||
"node_modules/duplexer": {
|
"node_modules/duplexer": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
|
@ -43347,9 +43371,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@saleor/macaw-ui": {
|
"@saleor/macaw-ui": {
|
||||||
"version": "0.8.0-pre.68",
|
"version": "0.8.0-pre.73",
|
||||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.68.tgz",
|
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.73.tgz",
|
||||||
"integrity": "sha512-4P1Ec4dpNk0MyejtosBZLd0EHl3pOBY1aFt09/R0Qpdiyu2+O7Jh/O1pwG+cpaG7J6hcrtMGsfYfLl24PwIAAA==",
|
"integrity": "sha512-7+bFDATTV8ZjX3dpU20z5QMUMSZHoNgidLWEzUBkHWk6EgiDf+V5Mn2t8Eexd34uLOIKCQ0j4f/mVG+slRsj/w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@dessert-box/react": "^0.4.0",
|
"@dessert-box/react": "^0.4.0",
|
||||||
"@floating-ui/react-dom-interactions": "^0.5.0",
|
"@floating-ui/react-dom-interactions": "^0.5.0",
|
||||||
|
@ -43363,8 +43387,10 @@
|
||||||
"@radix-ui/react-select": "^1.2.0",
|
"@radix-ui/react-select": "^1.2.0",
|
||||||
"@radix-ui/react-toggle": "^1.0.2",
|
"@radix-ui/react-toggle": "^1.0.2",
|
||||||
"@radix-ui/react-tooltip": "^1.0.5",
|
"@radix-ui/react-tooltip": "^1.0.5",
|
||||||
|
"@vanilla-extract/css-utils": "^0.1.3",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"downshift": "^6.1.7",
|
"downshift": "^6.1.7",
|
||||||
|
"downshift7": "npm:downshift@7.6.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"react-inlinesvg": "^3.0.1"
|
"react-inlinesvg": "^3.0.1"
|
||||||
|
@ -49697,6 +49723,25 @@
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downshift7": {
|
||||||
|
"version": "npm:downshift@7.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
|
||||||
|
"integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.14.8",
|
||||||
|
"compute-scroll-into-view": "^2.0.4",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^17.0.2",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"compute-scroll-into-view": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"duplexer": {
|
"duplexer": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@reach/auto-id": "^0.16.0",
|
"@reach/auto-id": "^0.16.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.68",
|
"@saleor/macaw-ui": "0.8.0-pre.73",
|
||||||
"@saleor/sdk": "^0.5.0",
|
"@saleor/sdk": "^0.5.0",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { ListSettingsUpdate } from "@dashboard/components/TablePagination";
|
import { ListSettingsUpdate } from "@dashboard/components/TablePagination";
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import { ProductErrorFragment } from "@dashboard/graphql";
|
import { ProductErrorFragment } from "@dashboard/graphql";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
|
|
|
@ -8,9 +8,9 @@ import { Button } from "@dashboard/components/Button";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import CardTitle from "@dashboard/components/CardTitle";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
||||||
import { CategoryDetailsQuery, ProductErrorFragment } from "@dashboard/graphql";
|
import { CategoryDetailsQuery, ProductErrorFragment } from "@dashboard/graphql";
|
||||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import {
|
import {
|
||||||
CollectionChannelListingErrorFragment,
|
CollectionChannelListingErrorFragment,
|
||||||
CollectionErrorFragment,
|
CollectionErrorFragment,
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import {
|
import {
|
||||||
CollectionChannelListingErrorFragment,
|
CollectionChannelListingErrorFragment,
|
||||||
CollectionDetailsQuery,
|
CollectionDetailsQuery,
|
||||||
|
|
|
@ -19,10 +19,7 @@ interface TopNavMenuProps {
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopNavMenu: React.FC<TopNavMenuProps> = ({
|
export const Menu: React.FC<TopNavMenuProps> = ({ items, dataTestId }) => (
|
||||||
items,
|
|
||||||
dataTestId,
|
|
||||||
}) => (
|
|
||||||
<Dropdown data-test-id={dataTestId}>
|
<Dropdown data-test-id={dataTestId}>
|
||||||
<Dropdown.Trigger>
|
<Dropdown.Trigger>
|
||||||
<Button
|
<Button
|
|
@ -13,7 +13,7 @@ interface TopNavProps {
|
||||||
isAlignToRight?: boolean;
|
isAlignToRight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopNav: React.FC<PropsWithChildren<TopNavProps>> = ({
|
export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
||||||
title,
|
title,
|
||||||
href,
|
href,
|
||||||
withoutBorder = false,
|
withoutBorder = false,
|
|
@ -1,3 +1,8 @@
|
||||||
export * from "./TopNav";
|
import { Menu } from "./Menu";
|
||||||
|
import { Root } from "./Root";
|
||||||
export * from "./TopNavLink";
|
export * from "./TopNavLink";
|
||||||
export * from "./TopNavWrapper";
|
export * from "./TopNavWrapper";
|
||||||
|
|
||||||
|
export const TopNav = Object.assign(Root, {
|
||||||
|
Menu,
|
||||||
|
});
|
||||||
|
|
33
src/components/Attributes/AttributeListItem.tsx
Normal file
33
src/components/Attributes/AttributeListItem.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
PageErrorWithAttributesFragment,
|
||||||
|
ProductErrorWithAttributesFragment,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AttributeRow from "./AttributeRow";
|
||||||
|
import { AttributeRowProps } from "./types";
|
||||||
|
|
||||||
|
type AttributeListItemProps = Omit<AttributeRowProps, "error"> & {
|
||||||
|
errors: Array<
|
||||||
|
ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment
|
||||||
|
>;
|
||||||
|
onAttributeSelectBlur: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AttributeListItem: React.FC<AttributeListItemProps> = ({
|
||||||
|
errors,
|
||||||
|
attribute,
|
||||||
|
onAttributeSelectBlur,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const error = errors.find(err => err.attributes?.includes(attribute.id));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AttributeRow
|
||||||
|
attribute={attribute}
|
||||||
|
error={error}
|
||||||
|
onAttributeSelectBlur={onAttributeSelectBlur}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,5 @@
|
||||||
import { inputTypeMessages } from "@dashboard/attributes/components/AttributeDetails/messages";
|
import { inputTypeMessages } from "@dashboard/attributes/components/AttributeDetails/messages";
|
||||||
import { getMeasurementUnitMessage } from "@dashboard/attributes/components/AttributeDetails/utils";
|
import { BasicAttributeRow } from "@dashboard/components/Attributes/BasicAttributeRow";
|
||||||
import BasicAttributeRow from "@dashboard/components/Attributes/BasicAttributeRow";
|
|
||||||
import ExtendedAttributeRow from "@dashboard/components/Attributes/ExtendedAttributeRow";
|
import ExtendedAttributeRow from "@dashboard/components/Attributes/ExtendedAttributeRow";
|
||||||
import { attributeRowMessages } from "@dashboard/components/Attributes/messages";
|
import { attributeRowMessages } from "@dashboard/components/Attributes/messages";
|
||||||
import { SwatchRow } from "@dashboard/components/Attributes/SwatchRow";
|
import { SwatchRow } from "@dashboard/components/Attributes/SwatchRow";
|
||||||
|
@ -13,8 +12,6 @@ import {
|
||||||
getSingleChoices,
|
getSingleChoices,
|
||||||
getSingleDisplayValue,
|
getSingleDisplayValue,
|
||||||
} from "@dashboard/components/Attributes/utils";
|
} from "@dashboard/components/Attributes/utils";
|
||||||
import Checkbox from "@dashboard/components/Checkbox";
|
|
||||||
import { DateTimeField } from "@dashboard/components/DateTimeField";
|
|
||||||
import FileUploadField from "@dashboard/components/FileUploadField";
|
import FileUploadField from "@dashboard/components/FileUploadField";
|
||||||
import MultiAutocompleteSelectField from "@dashboard/components/MultiAutocompleteSelectField";
|
import MultiAutocompleteSelectField from "@dashboard/components/MultiAutocompleteSelectField";
|
||||||
import RichTextEditor from "@dashboard/components/RichTextEditor";
|
import RichTextEditor from "@dashboard/components/RichTextEditor";
|
||||||
|
@ -22,11 +19,12 @@ import SingleAutocompleteSelectField from "@dashboard/components/SingleAutocompl
|
||||||
import SortableChipsField from "@dashboard/components/SortableChipsField";
|
import SortableChipsField from "@dashboard/components/SortableChipsField";
|
||||||
import { AttributeInputTypeEnum } from "@dashboard/graphql";
|
import { AttributeInputTypeEnum } from "@dashboard/graphql";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { InputAdornment, TextField } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
|
import { Box, Checkbox, Input, Text } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useStyles } from "./styles";
|
import { DateTimeField } from "../DateTimeField";
|
||||||
import { AttributeRowProps } from "./types";
|
import { AttributeRowProps } from "./types";
|
||||||
|
|
||||||
const AttributeRow: React.FC<AttributeRowProps> = ({
|
const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
|
@ -47,7 +45,6 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
richTextGetters,
|
richTextGetters,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
switch (attribute.data.inputType) {
|
switch (attribute.data.inputType) {
|
||||||
case AttributeInputTypeEnum.REFERENCE:
|
case AttributeInputTypeEnum.REFERENCE:
|
||||||
|
@ -77,7 +74,6 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label}>
|
<BasicAttributeRow label={attribute.label}>
|
||||||
<FileUploadField
|
<FileUploadField
|
||||||
className={classes.fileField}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
file={getFileChoice(attribute)}
|
file={getFileChoice(attribute)}
|
||||||
|
@ -93,7 +89,10 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
);
|
);
|
||||||
case AttributeInputTypeEnum.DROPDOWN:
|
case AttributeInputTypeEnum.DROPDOWN:
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label}>
|
<BasicAttributeRow
|
||||||
|
label={attribute.label}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
|
>
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
choices={getSingleChoices(attributeValues)}
|
choices={getSingleChoices(attributeValues)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -102,6 +101,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
helperText={getErrorMessage(error, intl)}
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
||||||
value={attribute.value[0]}
|
value={attribute.value[0]}
|
||||||
onChange={event => onChange(attribute.id, event.target.value)}
|
onChange={event => onChange(attribute.id, event.target.value)}
|
||||||
|
@ -130,18 +130,19 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
<BasicAttributeRow
|
<BasicAttributeRow
|
||||||
label={attribute.label}
|
label={attribute.label}
|
||||||
description={intl.formatMessage(inputTypeMessages.plainText)}
|
description={intl.formatMessage(inputTypeMessages.plainText)}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
>
|
>
|
||||||
<TextField
|
<Input
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
|
||||||
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
onChange={event => onChange(attribute.id, event.target.value)}
|
onChange={event => onChange(attribute.id, event.target.value)}
|
||||||
type="text"
|
type="text"
|
||||||
value={attribute.value[0]}
|
value={attribute.value[0]}
|
||||||
|
size="small"
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
|
helperText={getErrorMessage(error, intl)}
|
||||||
/>
|
/>
|
||||||
</BasicAttributeRow>
|
</BasicAttributeRow>
|
||||||
);
|
);
|
||||||
|
@ -157,70 +158,85 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
<BasicAttributeRow
|
<BasicAttributeRow
|
||||||
label={attribute.label}
|
label={attribute.label}
|
||||||
description={intl.formatMessage(inputTypeMessages.richText)}
|
description={intl.formatMessage(inputTypeMessages.richText)}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
>
|
>
|
||||||
{getShouldMount(attribute.id) && (
|
{getShouldMount(attribute.id) && (
|
||||||
<RichTextEditor
|
<Box __minWidth={210}>
|
||||||
defaultValue={defaultValue}
|
<RichTextEditor
|
||||||
editorRef={getMountEditor(attribute.id)}
|
defaultValue={defaultValue}
|
||||||
onChange={getHandleChange(attribute.id)}
|
editorRef={getMountEditor(attribute.id)}
|
||||||
name={`attribute:${attribute.label}`}
|
onChange={getHandleChange(attribute.id)}
|
||||||
disabled={disabled}
|
name={`attribute:${attribute.label}`}
|
||||||
error={!!error}
|
disabled={disabled}
|
||||||
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
||||||
/>
|
helperText={getErrorMessage(error, intl)}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</BasicAttributeRow>
|
</BasicAttributeRow>
|
||||||
);
|
);
|
||||||
case AttributeInputTypeEnum.NUMERIC:
|
case AttributeInputTypeEnum.NUMERIC:
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label}>
|
<BasicAttributeRow
|
||||||
<TextField
|
label={attribute.label}
|
||||||
fullWidth
|
id={`attribute:${attribute.label}`}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
|
||||||
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
label={intl.formatMessage(attributeRowMessages.valueLabel)}
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
onChange={event => onChange(attribute.id, event.target.value)}
|
onChange={event => onChange(attribute.id, event.target.value)}
|
||||||
type="number"
|
type="number"
|
||||||
value={attribute.value[0]}
|
value={attribute.value[0]}
|
||||||
InputProps={
|
size="small"
|
||||||
attribute.data.unit && {
|
helperText={getErrorMessage(error, intl)}
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
{getMeasurementUnitMessage(
|
|
||||||
attribute.data.unit,
|
|
||||||
intl.formatMessage,
|
|
||||||
)}
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</BasicAttributeRow>
|
</BasicAttributeRow>
|
||||||
);
|
);
|
||||||
case AttributeInputTypeEnum.BOOLEAN:
|
case AttributeInputTypeEnum.BOOLEAN:
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label}>
|
<Box as="li" display="flex" gap={5} alignItems="center" padding={3}>
|
||||||
<div className={classes.pullRight}>
|
<Box data-test-id="attribute-value">
|
||||||
<Checkbox
|
<Box
|
||||||
disabled={disabled}
|
display="flex"
|
||||||
name={`attribute:${attribute.label}`}
|
gap={2}
|
||||||
onChange={event =>
|
flexDirection="column"
|
||||||
onChange(attribute.id, JSON.stringify(event.target.checked))
|
alignItems="flex-end"
|
||||||
}
|
>
|
||||||
checked={JSON.parse(attribute.value[0] ?? "false")}
|
<Checkbox
|
||||||
className={classes.pullRight}
|
name={`attribute:${attribute.label}`}
|
||||||
helperText={getErrorMessage(error, intl)}
|
onCheckedChange={checked => onChange(attribute.id, checked)}
|
||||||
error={!!error}
|
checked={JSON.parse(attribute.value[0] ?? "false")}
|
||||||
/>
|
error={!!error}
|
||||||
</div>
|
id={`attribute:${attribute.label}`}
|
||||||
</BasicAttributeRow>
|
/>
|
||||||
|
<Text variant="caption" color="textCriticalDefault">
|
||||||
|
{getErrorMessage(error, intl)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
data-test-id="attribute-label"
|
||||||
|
as="label"
|
||||||
|
htmlFor={`attribute:${attribute.label}`}
|
||||||
|
display="flex"
|
||||||
|
gap={3}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
<Text>{attribute.label}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
case AttributeInputTypeEnum.DATE:
|
case AttributeInputTypeEnum.DATE:
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label} flexValueContainer>
|
<BasicAttributeRow
|
||||||
|
label={attribute.label}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -228,6 +244,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
helperText={getErrorMessage(error, intl)}
|
helperText={getErrorMessage(error, intl)}
|
||||||
label={intl.formatMessage(commonMessages.date)}
|
label={intl.formatMessage(commonMessages.date)}
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
onChange={event => onChange(attribute.id, event.target.value)}
|
onChange={event => onChange(attribute.id, event.target.value)}
|
||||||
type="date"
|
type="date"
|
||||||
value={attribute.value[0]}
|
value={attribute.value[0]}
|
||||||
|
@ -237,7 +254,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
|
||||||
);
|
);
|
||||||
case AttributeInputTypeEnum.DATE_TIME:
|
case AttributeInputTypeEnum.DATE_TIME:
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label} flexValueContainer>
|
<BasicAttributeRow label={attribute.label}>
|
||||||
<DateTimeField
|
<DateTimeField
|
||||||
fullWidth
|
fullWidth
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { AttributeReference } from "@dashboard/attributes/utils/data";
|
import { AttributeReference } from "@dashboard/attributes/utils/data";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import {
|
import {
|
||||||
AttributeEntityTypeEnum,
|
AttributeEntityTypeEnum,
|
||||||
AttributeInputTypeEnum,
|
AttributeInputTypeEnum,
|
||||||
|
@ -13,13 +11,12 @@ import {
|
||||||
import { FormsetAtomicData } from "@dashboard/hooks/useFormset";
|
import { FormsetAtomicData } from "@dashboard/hooks/useFormset";
|
||||||
import { FetchMoreProps } from "@dashboard/types";
|
import { FetchMoreProps } from "@dashboard/types";
|
||||||
import { RichTextGetters } from "@dashboard/utils/richText/useMultipleRichText";
|
import { RichTextGetters } from "@dashboard/utils/richText/useMultipleRichText";
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Accordion, Box, Divider, Text } from "@saleor/macaw-ui/next";
|
||||||
import { ChevronIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import AttributeRow from "./AttributeRow";
|
import { DashboardCard } from "../Card";
|
||||||
|
import { AttributeListItem } from "./AttributeListItem";
|
||||||
import { AttributeRowHandlers, VariantAttributeScope } from "./types";
|
import { AttributeRowHandlers, VariantAttributeScope } from "./types";
|
||||||
|
|
||||||
export interface AttributeInputData {
|
export interface AttributeInputData {
|
||||||
|
@ -49,61 +46,6 @@ export interface AttributesProps extends AttributeRowHandlers {
|
||||||
richTextGetters: RichTextGetters<string>;
|
richTextGetters: RichTextGetters<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
attributeSection: {
|
|
||||||
"&:last-of-type": {
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
padding: theme.spacing(2, 0),
|
|
||||||
},
|
|
||||||
attributeSectionLabel: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
overflow: "visible",
|
|
||||||
},
|
|
||||||
cardContent: {
|
|
||||||
"&:last-child": {
|
|
||||||
paddingBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
paddingTop: theme.spacing(1),
|
|
||||||
},
|
|
||||||
expansionBar: {
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
expansionBarButton: {
|
|
||||||
padding: 4,
|
|
||||||
marginBottom: theme.spacing(1),
|
|
||||||
},
|
|
||||||
expansionBarButtonIcon: {
|
|
||||||
transition: theme.transitions.duration.short + "ms",
|
|
||||||
},
|
|
||||||
expansionBarLabel: {
|
|
||||||
color: theme.palette.text.disabled,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
expansionBarLabelContainer: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
rotate: {
|
|
||||||
transform: "rotate(180deg)",
|
|
||||||
},
|
|
||||||
uploadFileButton: {
|
|
||||||
float: "right",
|
|
||||||
},
|
|
||||||
uploadFileContent: {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
float: "right",
|
|
||||||
fontSize: theme.typography.body1.fontSize,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "Attributes" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
attributesNumber: {
|
attributesNumber: {
|
||||||
id: "z0gGP+",
|
id: "z0gGP+",
|
||||||
|
@ -117,7 +59,7 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Attributes: React.FC<AttributesProps> = ({
|
export const Attributes: React.FC<AttributesProps> = ({
|
||||||
attributes,
|
attributes,
|
||||||
attributeValues,
|
attributeValues,
|
||||||
errors,
|
errors,
|
||||||
|
@ -127,66 +69,50 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
|
||||||
const [expanded, setExpansionStatus] = React.useState(true);
|
|
||||||
const toggleExpansion = () => setExpansionStatus(!expanded);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.card}>
|
<DashboardCard>
|
||||||
<CardTitle title={title || intl.formatMessage(messages.header)} />
|
<DashboardCard.Title>
|
||||||
<CardContent className={classes.cardContent}>
|
{title || intl.formatMessage(messages.header)}
|
||||||
<div className={classes.expansionBar}>
|
</DashboardCard.Title>
|
||||||
<div className={classes.expansionBarLabelContainer}>
|
<DashboardCard.Content>
|
||||||
<Typography className={classes.expansionBarLabel} variant="caption">
|
<Box display="flex" flexDirection="column" gap={5}>
|
||||||
<FormattedMessage
|
<Accordion defaultValue="attributes-accordion">
|
||||||
{...messages.attributesNumber}
|
<Accordion.Item value="attributes-accordion">
|
||||||
values={{
|
<Accordion.Item.Trigger buttonDataTestId="attributes-expand">
|
||||||
number: attributes.length,
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
}}
|
<FormattedMessage
|
||||||
/>
|
{...messages.attributesNumber}
|
||||||
</Typography>
|
values={{
|
||||||
</div>
|
number: attributes.length,
|
||||||
<IconButton
|
}}
|
||||||
variant="secondary"
|
|
||||||
hoverOutline={false}
|
|
||||||
className={classes.expansionBarButton}
|
|
||||||
onClick={toggleExpansion}
|
|
||||||
data-test-id="attributes-expand"
|
|
||||||
>
|
|
||||||
<ChevronIcon
|
|
||||||
className={clsx(classes.expansionBarButtonIcon, {
|
|
||||||
[classes.rotate]: expanded,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
{expanded && attributes.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Hr />
|
|
||||||
{attributes.map((attribute, attributeIndex) => {
|
|
||||||
const error = errors.find(err =>
|
|
||||||
err.attributes?.includes(attribute.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={attribute.id}>
|
|
||||||
{attributeIndex > 0 && <Hr />}
|
|
||||||
<AttributeRow
|
|
||||||
attribute={attribute}
|
|
||||||
attributeValues={attributeValues}
|
|
||||||
error={error}
|
|
||||||
onAttributeSelectBlur={onAttributeSelectBlur}
|
|
||||||
richTextGetters={richTextGetters}
|
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</Text>
|
||||||
);
|
</Accordion.Item.Trigger>
|
||||||
})}
|
<Accordion.Item.Content>
|
||||||
</>
|
{attributes.length > 0 && (
|
||||||
)}
|
<ul>
|
||||||
</CardContent>
|
<Divider />
|
||||||
</Card>
|
{attributes.map((attribute, attributeIndex) => (
|
||||||
|
<React.Fragment key={attribute.id}>
|
||||||
|
{attributeIndex > 0 && <Divider />}
|
||||||
|
<AttributeListItem
|
||||||
|
attribute={attribute}
|
||||||
|
errors={errors}
|
||||||
|
attributeValues={attributeValues}
|
||||||
|
onAttributeSelectBlur={onAttributeSelectBlur}
|
||||||
|
richTextGetters={richTextGetters}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</Accordion.Item.Content>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</Box>
|
||||||
|
</DashboardCard.Content>
|
||||||
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Attributes.displayName = "Attributes";
|
|
||||||
export default Attributes;
|
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
import Grid from "@dashboard/components/Grid";
|
import { Box, InfoIcon, sprinkles, Text, Tooltip } from "@saleor/macaw-ui/next";
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import HelpOutline from "@material-ui/icons/HelpOutline";
|
|
||||||
import { Tooltip } from "@saleor/macaw-ui/next";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useBasicAttributeStyles } from "./styles";
|
|
||||||
|
|
||||||
interface BasicAttributeRowProps {
|
interface BasicAttributeRowProps {
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
description?: string | React.ReactNode;
|
description?: string | React.ReactNode;
|
||||||
flexValueContainer?: boolean;
|
id?: string;
|
||||||
|
clickableLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicAttributeRow: React.FC<BasicAttributeRowProps> = ({
|
export const BasicAttributeRow: React.FC<BasicAttributeRowProps> = ({
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
children,
|
children,
|
||||||
flexValueContainer,
|
id,
|
||||||
}) => {
|
clickableLabel = false,
|
||||||
const classes = useBasicAttributeStyles();
|
}) => (
|
||||||
|
<Box
|
||||||
return (
|
as="li"
|
||||||
<Grid className={classes.attributeSection} variant="uniform">
|
justifyContent="space-between"
|
||||||
<div
|
alignItems="center"
|
||||||
className={classes.attributeSectionLabel}
|
paddingY={3}
|
||||||
data-test-id="attribute-label"
|
paddingX={2}
|
||||||
>
|
display="grid"
|
||||||
<Typography>
|
gridTemplateColumns={2}
|
||||||
{label}
|
gap={8}
|
||||||
{description && (
|
>
|
||||||
<Tooltip>
|
<Box
|
||||||
<Tooltip.Trigger>
|
data-test-id="attribute-label"
|
||||||
<HelpOutline className={classes.tooltipIcon} />
|
as="label"
|
||||||
</Tooltip.Trigger>
|
htmlFor={id}
|
||||||
<Tooltip.Content side="bottom">
|
display="flex"
|
||||||
<Tooltip.Arrow />
|
gap={3}
|
||||||
{description}
|
cursor={clickableLabel ? "pointer" : "auto"}
|
||||||
</Tooltip.Content>
|
>
|
||||||
</Tooltip>
|
<Text>{label}</Text>
|
||||||
)}
|
{description && (
|
||||||
</Typography>
|
<Tooltip>
|
||||||
</div>
|
<Tooltip.Trigger>
|
||||||
<div
|
<Box>
|
||||||
data-test-id="attribute-value"
|
<InfoIcon
|
||||||
className={clsx(classes.value, {
|
size="small"
|
||||||
[classes.flex]: flexValueContainer,
|
className={sprinkles({
|
||||||
})}
|
display: "block",
|
||||||
>
|
})}
|
||||||
{children}
|
/>
|
||||||
</div>
|
</Box>
|
||||||
</Grid>
|
</Tooltip.Trigger>
|
||||||
);
|
<Tooltip.Content side="top">
|
||||||
};
|
<Tooltip.Arrow />
|
||||||
|
{description}
|
||||||
BasicAttributeRow.displayName = "BasicAttributeRow";
|
</Tooltip.Content>
|
||||||
export default BasicAttributeRow;
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box data-test-id="attribute-value">{children}</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import Grid from "@dashboard/components/Grid";
|
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useExtendedAttributeStyles } from "./styles";
|
|
||||||
|
|
||||||
interface ExtendedAttributeRowProps {
|
interface ExtendedAttributeRowProps {
|
||||||
label: string;
|
label: string;
|
||||||
selectLabel: string;
|
selectLabel: string;
|
||||||
|
@ -14,30 +10,28 @@ interface ExtendedAttributeRowProps {
|
||||||
|
|
||||||
const ExtendedAttributeRow: React.FC<ExtendedAttributeRowProps> = props => {
|
const ExtendedAttributeRow: React.FC<ExtendedAttributeRowProps> = props => {
|
||||||
const { label, selectLabel, disabled, onSelect, children } = props;
|
const { label, selectLabel, disabled, onSelect, children } = props;
|
||||||
const classes = useExtendedAttributeStyles(props);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid className={classes.attributeSection} variant="uniform">
|
<Box
|
||||||
<div
|
as="li"
|
||||||
className={classes.attributeSectionLabel}
|
display="flex"
|
||||||
data-test-id="attribute-label"
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
paddingY={3}
|
||||||
|
>
|
||||||
|
<Text data-test-id="attribute-label">{label}</Text>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
variant="secondary"
|
||||||
|
data-test-id="button-attribute-selector"
|
||||||
|
onClick={onSelect}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Typography>{label}</Typography>
|
{selectLabel}
|
||||||
</div>
|
</Button>
|
||||||
<div data-test-id="attribute-selector">
|
</Box>
|
||||||
<Button
|
<Box data-test-id="attribute-value">{children}</Box>
|
||||||
className={classes.attributeSectionButton}
|
|
||||||
disabled={disabled}
|
|
||||||
variant="tertiary"
|
|
||||||
data-test-id="button-attribute-selector"
|
|
||||||
onClick={onSelect}
|
|
||||||
>
|
|
||||||
{selectLabel}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
<div data-test-id="attribute-value">{children}</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import BasicAttributeRow from "@dashboard/components/Attributes/BasicAttributeRow";
|
import { BasicAttributeRow } from "@dashboard/components/Attributes/BasicAttributeRow";
|
||||||
import {
|
import {
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
getSingleDisplayValue,
|
getSingleDisplayValue,
|
||||||
|
@ -38,7 +38,10 @@ export const SwatchRow: React.FC<SwatchRowProps> = ({
|
||||||
const value = attribute.data.values.find(getBySlug(attribute.value[0]));
|
const value = attribute.data.values.find(getBySlug(attribute.value[0]));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicAttributeRow label={attribute.label}>
|
<BasicAttributeRow
|
||||||
|
label={attribute.label}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
|
>
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
fetchOnFocus
|
fetchOnFocus
|
||||||
allowCustomValues={false}
|
allowCustomValues={false}
|
||||||
|
@ -65,6 +68,7 @@ export const SwatchRow: React.FC<SwatchRowProps> = ({
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
helperText={getErrorMessage(error, intl)}
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
|
id={`attribute:${attribute.label}`}
|
||||||
value={attribute.value[0]}
|
value={attribute.value[0]}
|
||||||
onChange={event => onChange(attribute.id, event.target.value)}
|
onChange={event => onChange(attribute.id, event.target.value)}
|
||||||
fetchChoices={value => fetchAttributeValues(value, attribute.id)}
|
fetchChoices={value => fetchAttributeValues(value, attribute.id)}
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export { default } from "./Attributes";
|
|
||||||
export * from "./Attributes";
|
export * from "./Attributes";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
|
|
@ -2,13 +2,6 @@ import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
export const useStyles = makeStyles(
|
export const useStyles = makeStyles(
|
||||||
() => ({
|
() => ({
|
||||||
fileField: {
|
|
||||||
float: "right",
|
|
||||||
},
|
|
||||||
pullRight: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
},
|
|
||||||
swatchInput: {
|
swatchInput: {
|
||||||
paddingTop: 16.5,
|
paddingTop: 16.5,
|
||||||
paddingBottom: 16.5,
|
paddingBottom: 16.5,
|
||||||
|
@ -23,62 +16,3 @@ export const useStyles = makeStyles(
|
||||||
}),
|
}),
|
||||||
{ name: "AttributeRow" },
|
{ name: "AttributeRow" },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useBasicAttributeStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
attributeSection: {
|
|
||||||
"&:last-of-type": {
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
padding: theme.spacing(2, 0),
|
|
||||||
wordBreak: "break-word",
|
|
||||||
},
|
|
||||||
attributeSectionLabel: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
flex: {
|
|
||||||
columnGap: theme.spacing(2) + "px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
[theme.breakpoints.down("md")]: {
|
|
||||||
flexDirection: "column",
|
|
||||||
rowGap: theme.spacing(2) + "px",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
"&&": {
|
|
||||||
overflow: "visible",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltipIcon: {
|
|
||||||
fill: theme.palette.type === "dark" ? "#FAFAFA" : "#28234A",
|
|
||||||
fillOpacity: 0.6,
|
|
||||||
"&:hover": {
|
|
||||||
fillOpacity: 1,
|
|
||||||
},
|
|
||||||
padding: theme.spacing(0.25),
|
|
||||||
marginLeft: theme.spacing(0.75),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "BasicAttributeRow" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useExtendedAttributeStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
attributeSection: {
|
|
||||||
"&:last-of-type": {
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
padding: theme.spacing(2, 0),
|
|
||||||
},
|
|
||||||
attributeSectionButton: {
|
|
||||||
float: "right",
|
|
||||||
},
|
|
||||||
attributeSectionLabel: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ExtendedAttributeRow" },
|
|
||||||
);
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export enum VariantAttributeScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeRowHandlers {
|
export interface AttributeRowHandlers {
|
||||||
onChange: FormsetChange<string>;
|
onChange: FormsetChange<string | boolean>;
|
||||||
onFileChange: FormsetChange<File>;
|
onFileChange: FormsetChange<File>;
|
||||||
onMultiChange: FormsetChange<string>;
|
onMultiChange: FormsetChange<string>;
|
||||||
onReferencesAddClick: (attribute: AttributeInput) => void;
|
onReferencesAddClick: (attribute: AttributeInput) => void;
|
||||||
|
|
8
src/components/Card/Content.tsx
Normal file
8
src/components/Card/Content.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Box, Sprinkles } from "@saleor/macaw-ui/next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Content: React.FC<Sprinkles> = ({ children, ...rest }) => (
|
||||||
|
<Box paddingX={9} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
8
src/components/Card/Root.tsx
Normal file
8
src/components/Card/Root.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Box, Sprinkles } from "@saleor/macaw-ui/next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Root: React.FC<Sprinkles> = ({ children, ...rest }) => (
|
||||||
|
<Box display="flex" flexDirection="column" gap={9} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
8
src/components/Card/Title.tsx
Normal file
8
src/components/Card/Title.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Title: React.FC = ({ children }) => (
|
||||||
|
<Box paddingX={9} paddingTop={9}>
|
||||||
|
<Text variant="heading">{children}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
5
src/components/Card/index.ts
Normal file
5
src/components/Card/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { Content } from "./Content";
|
||||||
|
import { Root } from "./Root";
|
||||||
|
import { Title } from "./Title";
|
||||||
|
|
||||||
|
export const DashboardCard = Object.assign(Root, { Title, Content });
|
|
@ -1,16 +1,18 @@
|
||||||
import { ChannelData } from "@dashboard/channels/utils";
|
import { ChannelData } from "@dashboard/channels/utils";
|
||||||
import ControlledCheckbox from "@dashboard/components/ControlledCheckbox";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import RadioSwitchField from "@dashboard/components/RadioSwitchField";
|
|
||||||
import useCurrentDate from "@dashboard/hooks/useCurrentDate";
|
import useCurrentDate from "@dashboard/hooks/useCurrentDate";
|
||||||
import useDateLocalize from "@dashboard/hooks/useDateLocalize";
|
import useDateLocalize from "@dashboard/hooks/useDateLocalize";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import { TextField, Typography } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
import clsx from "clsx";
|
import {
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
Divider,
|
||||||
|
RadioGroup,
|
||||||
|
Text,
|
||||||
|
} from "@saleor/macaw-ui/next";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useStyles } from "../styles";
|
|
||||||
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "../types";
|
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "../types";
|
||||||
import { availabilityItemMessages } from "./messages";
|
import { availabilityItemMessages } from "./messages";
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ export interface ChannelContentProps {
|
||||||
onChange: (id: string, data: ChannelOpts) => void;
|
onChange: (id: string, data: ChannelOpts) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChannelContent: React.FC<ChannelContentProps> = ({
|
export const ChannelAvailabilityItemContent: React.FC<ChannelContentProps> = ({
|
||||||
data,
|
data,
|
||||||
disabled,
|
disabled,
|
||||||
errors,
|
errors,
|
||||||
|
@ -55,7 +57,6 @@ const ChannelContent: React.FC<ChannelContentProps> = ({
|
||||||
);
|
);
|
||||||
const [isAvailableDate, setAvailableDate] = useState(false);
|
const [isAvailableDate, setAvailableDate] = useState(false);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
|
||||||
|
|
||||||
const parsedDate = new Date(dateNow);
|
const parsedDate = new Date(dateNow);
|
||||||
const todayDateUTC = parsedDate.toISOString().slice(0, 10);
|
const todayDateUTC = parsedDate.toISOString().slice(0, 10);
|
||||||
|
@ -70,57 +71,64 @@ const ChannelContent: React.FC<ChannelContentProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<Box display="flex" gap={6} paddingTop={6} flexDirection="column">
|
||||||
<RadioSwitchField
|
<RadioGroup
|
||||||
classes={{
|
value={String(isPublished)}
|
||||||
radioLabel: classes.radioLabel,
|
onValueChange={value => {
|
||||||
}}
|
|
||||||
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, {
|
onChange(id, {
|
||||||
...formData,
|
...formData,
|
||||||
isPublished: !isPublished,
|
isPublished: value === "true",
|
||||||
publicationDate:
|
publicationDate:
|
||||||
!isPublished && !publicationDate ? todayDateUTC : publicationDate,
|
!isPublished && !publicationDate ? todayDateUTC : publicationDate,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
disabled={disabled}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
gap={6}
|
||||||
|
>
|
||||||
|
<RadioGroup.Item
|
||||||
|
id={`${id}-isPublished-true`}
|
||||||
|
value="true"
|
||||||
|
name="isPublished"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="baseline" gap={5}>
|
||||||
|
<Text>{messages.visibleLabel}</Text>
|
||||||
|
{isPublished &&
|
||||||
|
publicationDate &&
|
||||||
|
Date.parse(publicationDate) < dateNow && (
|
||||||
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
{messages.visibleSecondLabel ||
|
||||||
|
visibleMessage(publicationDate)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup.Item>
|
||||||
|
<RadioGroup.Item
|
||||||
|
id={`${id}-isPublished-false`}
|
||||||
|
value="false"
|
||||||
|
name="isPublished"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="baseline" gap={5}>
|
||||||
|
<Text>{messages.hiddenLabel}</Text>
|
||||||
|
{publicationDate &&
|
||||||
|
!isPublished &&
|
||||||
|
Date.parse(publicationDate) >= dateNow && (
|
||||||
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
{messages.hiddenSecondLabel}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup.Item>
|
||||||
|
</RadioGroup>
|
||||||
{!isPublished && (
|
{!isPublished && (
|
||||||
<>
|
<Box display="flex" flexDirection="column" alignItems="start" gap={3}>
|
||||||
<Typography
|
<Checkbox
|
||||||
className={classes.setPublicationDate}
|
onCheckedChange={(checked: boolean) => setPublicationDate(checked)}
|
||||||
onClick={() => setPublicationDate(!isPublicationDate)}
|
checked={isPublicationDate}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(availabilityItemMessages.setPublicationDate)}
|
{intl.formatMessage(availabilityItemMessages.setPublicationDate)}
|
||||||
</Typography>
|
</Checkbox>
|
||||||
{isPublicationDate && (
|
{isPublicationDate && (
|
||||||
<TextField
|
<TextField
|
||||||
error={!!formErrors.publicationDate}
|
error={!!formErrors.publicationDate}
|
||||||
|
@ -141,64 +149,75 @@ const ChannelContent: React.FC<ChannelContentProps> = ({
|
||||||
publicationDate: e.target.value || null,
|
publicationDate: e.target.value || null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className={classes.date}
|
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{hasAvailableProps && (
|
{hasAvailableProps && (
|
||||||
<>
|
<>
|
||||||
<Hr />
|
<Divider />
|
||||||
<RadioSwitchField
|
<RadioGroup
|
||||||
classes={{
|
|
||||||
radioLabel: classes.radioLabel,
|
|
||||||
}}
|
|
||||||
className={classes.radioField}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
firstOptionLabel={
|
name={`channel:isAvailableForPurchase:${id}`}
|
||||||
<>
|
value={String(isAvailable)}
|
||||||
<p className={classes.label}>{messages.availableLabel}</p>
|
onValueChange={value =>
|
||||||
|
onChange(id, {
|
||||||
|
...formData,
|
||||||
|
availableForPurchase: !value ? null : availableForPurchase,
|
||||||
|
isAvailableForPurchase: value === "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
gap={6}
|
||||||
|
>
|
||||||
|
<RadioGroup.Item
|
||||||
|
id={`channel:isAvailableForPurchase:${id}-true`}
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
<Box display="flex" __alignItems="baseline" gap={5}>
|
||||||
|
<Text>{messages.availableLabel}</Text>
|
||||||
{isAvailable &&
|
{isAvailable &&
|
||||||
availableForPurchase &&
|
availableForPurchase &&
|
||||||
Date.parse(availableForPurchase) < dateNow && (
|
Date.parse(availableForPurchase) < dateNow && (
|
||||||
<span className={classes.secondLabel}>
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
{visibleMessage(availableForPurchase)}
|
{visibleMessage(availableForPurchase)}
|
||||||
</span>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</>
|
</Box>
|
||||||
}
|
</RadioGroup.Item>
|
||||||
name={`channel:isAvailableForPurchase:${id}`}
|
<RadioGroup.Item
|
||||||
secondOptionLabel={
|
id={`channel:isAvailableForPurchase:${id}-false`}
|
||||||
<>
|
value="false"
|
||||||
<p className={classes.label}>{messages.unavailableLabel}</p>
|
>
|
||||||
|
<Box display="flex" __alignItems="baseline" gap={5}>
|
||||||
|
<Text>{messages.unavailableLabel}</Text>
|
||||||
{availableForPurchase && !isAvailable && (
|
{availableForPurchase && !isAvailable && (
|
||||||
<span className={classes.secondLabel}>
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
{messages.availableSecondLabel}
|
{messages.availableSecondLabel}
|
||||||
</span>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</>
|
</Box>
|
||||||
}
|
</RadioGroup.Item>
|
||||||
value={isAvailable}
|
</RadioGroup>
|
||||||
onChange={e => {
|
|
||||||
const { value } = e.target;
|
|
||||||
return onChange(id, {
|
|
||||||
...formData,
|
|
||||||
availableForPurchase: !value ? null : availableForPurchase,
|
|
||||||
isAvailableForPurchase: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!isAvailable && (
|
{!isAvailable && (
|
||||||
<>
|
<Box
|
||||||
<Typography
|
display="flex"
|
||||||
className={classes.setPublicationDate}
|
gap={3}
|
||||||
onClick={() => setAvailableDate(!isAvailableDate)}
|
flexDirection="column"
|
||||||
|
alignItems="start"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
setAvailableDate(checked)
|
||||||
|
}
|
||||||
|
checked={isAvailableDate}
|
||||||
>
|
>
|
||||||
{messages.setAvailabilityDateLabel}
|
{messages.setAvailabilityDateLabel}
|
||||||
</Typography>
|
</Checkbox>
|
||||||
{isAvailableDate && (
|
{isAvailableDate && (
|
||||||
<TextField
|
<TextField
|
||||||
error={!!formErrors.availableForPurchaseDate}
|
error={!!formErrors.availableForPurchaseDate}
|
||||||
|
@ -224,46 +243,41 @@ const ChannelContent: React.FC<ChannelContentProps> = ({
|
||||||
availableForPurchase: e.target.value,
|
availableForPurchase: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className={classes.date}
|
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{visibleInListings !== undefined && (
|
{visibleInListings !== undefined && (
|
||||||
<>
|
<>
|
||||||
<Hr />
|
<Divider />
|
||||||
<ControlledCheckbox
|
<Checkbox
|
||||||
className={classes.checkbox}
|
|
||||||
name={`channel:visibleInListings:${id}`}
|
name={`channel:visibleInListings:${id}`}
|
||||||
|
id={`channel:visibleInListings:${id}`}
|
||||||
checked={!visibleInListings}
|
checked={!visibleInListings}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={
|
onCheckedChange={checked => {
|
||||||
<>
|
|
||||||
<p className={clsx(classes.label, classes.listingLabel)}>
|
|
||||||
{intl.formatMessage(availabilityItemMessages.hideInListings)}
|
|
||||||
</p>
|
|
||||||
<span className={classes.secondLabel}>
|
|
||||||
{intl.formatMessage(
|
|
||||||
availabilityItemMessages.hideInListingsDescription,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
onChange={e =>
|
|
||||||
onChange(id, {
|
onChange(id, {
|
||||||
...formData,
|
...formData,
|
||||||
visibleInListings: !e.target.value,
|
visibleInListings: !checked,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
/>
|
>
|
||||||
|
<Text cursor="pointer">
|
||||||
|
{intl.formatMessage(availabilityItemMessages.hideInListings)}
|
||||||
|
</Text>
|
||||||
|
</Checkbox>
|
||||||
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
{intl.formatMessage(
|
||||||
|
availabilityItemMessages.hideInListingsDescription,
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ChannelContent;
|
|
||||||
|
|
|
@ -1,94 +1,28 @@
|
||||||
import { ChannelData } from "@dashboard/channels/utils";
|
import { ChannelData } from "@dashboard/channels/utils";
|
||||||
import Label from "@dashboard/orders/components/OrderHistory/Label";
|
import Label from "@dashboard/orders/components/OrderHistory/Label";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Accordion, Text } from "@saleor/macaw-ui/next";
|
||||||
import { Accordion, AccordionSummary, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Messages } from "../types";
|
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),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ChannelContentWrapperExpanderSummary" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
() => ({
|
|
||||||
container: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ChannelWithVariantAvailabilityItemWrapper" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface ChannelContentWrapperProps {
|
export interface ChannelContentWrapperProps {
|
||||||
data: ChannelData;
|
data: ChannelData;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
messages: Messages;
|
messages: Messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChannelContentWrapper: React.FC<ChannelContentWrapperProps> = ({
|
export const ChannelAvailabilityItemWrapper: React.FC<
|
||||||
data,
|
ChannelContentWrapperProps
|
||||||
messages,
|
> = ({ data: { name }, messages, children }) => (
|
||||||
children,
|
<Accordion data-test-id="channel-availability-item">
|
||||||
}) => {
|
<Accordion.Item value="channel-availability-item" gap={12}>
|
||||||
const expanderClasses = useExpanderStyles();
|
<Accordion.Item.Trigger buttonDataTestId="expand-icon">
|
||||||
const summaryClasses = useSummaryStyles();
|
<Text variant={"bodyEmp"}>{name}</Text>
|
||||||
const classes = useStyles();
|
<Label text={messages.availableDateText} />
|
||||||
|
</Accordion.Item.Trigger>
|
||||||
const { name } = data;
|
<Accordion.Item.Content paddingLeft={6}>
|
||||||
|
{children}
|
||||||
return (
|
</Accordion.Item.Content>
|
||||||
<Accordion
|
</Accordion.Item>
|
||||||
classes={expanderClasses}
|
</Accordion>
|
||||||
data-test-id="channel-availability-item"
|
);
|
||||||
>
|
|
||||||
<AccordionSummary className={summaryClasses.root}>
|
|
||||||
<div className={classes.container}>
|
|
||||||
<Typography>{name}</Typography>
|
|
||||||
<Label text={messages.availableDateText} />
|
|
||||||
</div>
|
|
||||||
</AccordionSummary>
|
|
||||||
{children}
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChannelContentWrapper;
|
|
||||||
|
|
2
src/components/ChannelsAvailabilityCard/Channel/index.ts
Normal file
2
src/components/ChannelsAvailabilityCard/Channel/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./ChannelAvailabilityItemContent";
|
||||||
|
export * from "./ChannelAvailabilityItemWrapper";
|
|
@ -1,18 +1,19 @@
|
||||||
import { Channel as ChannelList, ChannelData } from "@dashboard/channels/utils";
|
import { Channel as ChannelList, ChannelData } from "@dashboard/channels/utils";
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import { PermissionEnum } from "@dashboard/graphql";
|
import { PermissionEnum } from "@dashboard/graphql";
|
||||||
import useDateLocalize from "@dashboard/hooks/useDateLocalize";
|
import useDateLocalize from "@dashboard/hooks/useDateLocalize";
|
||||||
import { RequireOnlyOne } from "@dashboard/misc";
|
import { RequireOnlyOne } from "@dashboard/misc";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Box, Divider, Text } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import ChannelAvailabilityItemContent from "./Channel/ChannelAvailabilityItemContent";
|
import {
|
||||||
import ChannelAvailabilityItemWrapper from "./Channel/ChannelAvailabilityItemWrapper";
|
ChannelAvailabilityItemContent,
|
||||||
import ChannelsAvailabilityCardWrapper, {
|
ChannelAvailabilityItemWrapper,
|
||||||
|
} from "./Channel";
|
||||||
|
import {
|
||||||
|
ChannelsAvailabilityCardWrapper,
|
||||||
ChannelsAvailabilityWrapperProps,
|
ChannelsAvailabilityWrapperProps,
|
||||||
} from "./ChannelsAvailabilityCardWrapper";
|
} from "./ChannelsAvailabilityCardWrapper";
|
||||||
import { useStyles } from "./styles";
|
|
||||||
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "./types";
|
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "./types";
|
||||||
import { getChannelsAvailabilityMessages } from "./utils";
|
import { getChannelsAvailabilityMessages } from "./utils";
|
||||||
|
|
||||||
|
@ -36,7 +37,9 @@ export type ChannelsAvailabilityCardProps = RequireOnlyOne<
|
||||||
"channels" | "channelsList"
|
"channels" | "channelsList"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = props => {
|
export const ChannelsAvailability: React.FC<
|
||||||
|
ChannelsAvailabilityCardProps
|
||||||
|
> = props => {
|
||||||
const {
|
const {
|
||||||
channelsList,
|
channelsList,
|
||||||
errors = [],
|
errors = [],
|
||||||
|
@ -49,7 +52,6 @@ export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = pro
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
const localizeDate = useDateLocalize();
|
||||||
const classes = useStyles({});
|
|
||||||
|
|
||||||
const channelsMessages = getChannelsAvailabilityMessages({
|
const channelsMessages = getChannelsAvailabilityMessages({
|
||||||
messages,
|
messages,
|
||||||
|
@ -88,12 +90,10 @@ export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = pro
|
||||||
: channelsList
|
: channelsList
|
||||||
? channelsList.map(data => (
|
? channelsList.map(data => (
|
||||||
<React.Fragment key={data.id}>
|
<React.Fragment key={data.id}>
|
||||||
<div className={classes.channelItem}>
|
<Box>
|
||||||
<div className={classes.channelName}>
|
<Text>{data.name}</Text>
|
||||||
<Typography>{data.name}</Typography>
|
</Box>
|
||||||
</div>
|
<Divider />
|
||||||
</div>
|
|
||||||
<Hr className={classes.hr} />
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
||||||
import { PermissionEnum } from "@dashboard/graphql";
|
import { PermissionEnum } from "@dashboard/graphql";
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useStyles } from "./styles";
|
import { DashboardCard } from "../Card";
|
||||||
|
|
||||||
export interface ChannelsAvailabilityWrapperProps {
|
export interface ChannelsAvailabilityWrapperProps {
|
||||||
selectedChannelsCount: number;
|
selectedChannelsCount: number;
|
||||||
|
@ -17,7 +14,9 @@ export interface ChannelsAvailabilityWrapperProps {
|
||||||
openModal: () => void;
|
openModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperProps> = props => {
|
export const ChannelsAvailabilityCardWrapper: React.FC<
|
||||||
|
ChannelsAvailabilityWrapperProps
|
||||||
|
> = props => {
|
||||||
const {
|
const {
|
||||||
selectedChannelsCount,
|
selectedChannelsCount,
|
||||||
allChannelsCount,
|
allChannelsCount,
|
||||||
|
@ -26,12 +25,11 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
|
||||||
openModal,
|
openModal,
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
|
||||||
const channelsAvailabilityText = intl.formatMessage(
|
const channelsAvailabilityText = intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "vY2lpx",
|
id: "AD1PlC",
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Available at {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}",
|
"In {selectedChannelsCount} out of {allChannelsCount, plural, one {# channel} other {# channels}}",
|
||||||
description: "channels availability text",
|
description: "channels availability text",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,43 +39,42 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DashboardCard>
|
||||||
<Card>
|
<DashboardCard.Title>
|
||||||
<CardTitle
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
title={intl.formatMessage({
|
<Box display={"flex"} flexDirection={"column"} gap={3}>
|
||||||
id: "5A6/2C",
|
<div>
|
||||||
defaultMessage: "Availability",
|
{intl.formatMessage({
|
||||||
description: "section header",
|
id: "5A6/2C",
|
||||||
})}
|
defaultMessage: "Availability",
|
||||||
toolbar={
|
description: "section header",
|
||||||
<RequirePermissions requiredPermissions={managePermissions}>
|
})}
|
||||||
<Button
|
</div>
|
||||||
onClick={openModal}
|
{!!channelsAvailabilityText && (
|
||||||
data-test-id="channels-availability-manage-button"
|
<Text variant={"caption"}>{channelsAvailabilityText}</Text>
|
||||||
>
|
)}
|
||||||
{intl.formatMessage({
|
</Box>
|
||||||
id: "2i81/P",
|
<RequirePermissions requiredPermissions={managePermissions}>
|
||||||
defaultMessage: "Manage",
|
<Button
|
||||||
description: "section header button",
|
onClick={openModal}
|
||||||
})}
|
data-test-id="channels-availability-manage-button"
|
||||||
</Button>
|
type="button"
|
||||||
</RequirePermissions>
|
variant="secondary"
|
||||||
}
|
>
|
||||||
/>
|
{intl.formatMessage({
|
||||||
<CardContent className={classes.card}>
|
id: "2i81/P",
|
||||||
{!!channelsAvailabilityText && (
|
defaultMessage: "Manage",
|
||||||
<>
|
description: "section header button",
|
||||||
<Typography className={classes.channelInfo}>
|
})}
|
||||||
{channelsAvailabilityText}
|
</Button>
|
||||||
</Typography>
|
</RequirePermissions>
|
||||||
<Hr className={classes.hr} />
|
</Box>
|
||||||
</>
|
</DashboardCard.Title>
|
||||||
)}
|
<DashboardCard.Content gap={3} display="flex" flexDirection="column">
|
||||||
|
<Box display="flex" flexDirection="column" gap={8}>
|
||||||
{children}
|
{children}
|
||||||
</CardContent>
|
</Box>
|
||||||
</Card>
|
</DashboardCard.Content>
|
||||||
</>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChannelsAvailabilityWrapper;
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
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: {
|
|
||||||
position: "relative",
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
lineHeight: 1.2,
|
|
||||||
},
|
|
||||||
listingLabel: {
|
|
||||||
marginTop: 9,
|
|
||||||
},
|
|
||||||
radioLabel: {
|
|
||||||
"& > span": {
|
|
||||||
padding: theme.spacing(0, 0.5),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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" },
|
|
||||||
);
|
|
|
@ -7,11 +7,10 @@ import { commonMessages } from "@dashboard/intl";
|
||||||
import { joinDateTime, splitDateTime } from "@dashboard/misc";
|
import { joinDateTime, splitDateTime } from "@dashboard/misc";
|
||||||
import { TextField } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
import { TextFieldProps } from "@material-ui/core/TextField";
|
import { TextFieldProps } from "@material-ui/core/TextField";
|
||||||
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useStyles } from "./styles";
|
|
||||||
|
|
||||||
type DateTimeFieldProps = Omit<TextFieldProps, "label" | "error"> & {
|
type DateTimeFieldProps = Omit<TextFieldProps, "label" | "error"> & {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment;
|
error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment;
|
||||||
|
@ -26,12 +25,11 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
|
||||||
value,
|
value,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
const parsedValue = value ? splitDateTime(value) : { date: "", time: "" };
|
const parsedValue = value ? splitDateTime(value) : { date: "", time: "" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box display="flex" gap={2}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -47,11 +45,6 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
|
||||||
type="date"
|
type="date"
|
||||||
value={parsedValue.date}
|
value={parsedValue.date}
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
InputProps={{
|
|
||||||
classes: {
|
|
||||||
root: classes.dateField,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -68,12 +61,7 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
|
||||||
type="time"
|
type="time"
|
||||||
value={parsedValue.time}
|
value={parsedValue.time}
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
InputProps={{
|
|
||||||
classes: {
|
|
||||||
root: classes.timeField,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
|
|
||||||
export const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
dateField: {
|
|
||||||
borderRadius: "4px 0 0 4px",
|
|
||||||
},
|
|
||||||
timeField: {
|
|
||||||
borderRadius: "0 4px 4px 0",
|
|
||||||
"& > fieldset": {
|
|
||||||
borderLeftWidth: "0 !important",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down("md")]: {
|
|
||||||
dateField: {
|
|
||||||
borderRadius: "4px 4px 0 0",
|
|
||||||
},
|
|
||||||
timeField: {
|
|
||||||
borderRadius: "0 0 4px 4px",
|
|
||||||
"& > fieldset": {
|
|
||||||
borderTopWidth: "0 !important",
|
|
||||||
borderLeftWidth: "1px !important",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "DateTimeField" },
|
|
||||||
);
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import { FileFragment } from "@dashboard/graphql";
|
import { FileFragment } from "@dashboard/graphql";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Box, Button, Text, TrashBinIcon } from "@saleor/macaw-ui/next";
|
||||||
import { DeleteIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -19,7 +17,6 @@ export interface FileUploadFieldProps {
|
||||||
React.InputHTMLAttributes<HTMLInputElement>,
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
HTMLInputElement
|
HTMLInputElement
|
||||||
>;
|
>;
|
||||||
className?: string;
|
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
file: FileChoiceType;
|
file: FileChoiceType;
|
||||||
|
@ -29,44 +26,17 @@ export interface FileUploadFieldProps {
|
||||||
onFileDelete: () => void;
|
onFileDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
errorText: {
|
|
||||||
color: theme.palette.error.light,
|
|
||||||
},
|
|
||||||
fileField: {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
fileUrl: {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
textDecoration: "none",
|
|
||||||
},
|
|
||||||
uploadFileContent: {
|
|
||||||
alignItems: "center",
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
display: "flex",
|
|
||||||
fontSize: theme.typography.body1.fontSize,
|
|
||||||
},
|
|
||||||
uploadFileName: {
|
|
||||||
minWidth: "6rem",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "FileUploadField" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const FileUploadField: React.FC<FileUploadFieldProps> = props => {
|
const FileUploadField: React.FC<FileUploadFieldProps> = props => {
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
disabled,
|
disabled,
|
||||||
file,
|
file,
|
||||||
className,
|
|
||||||
error,
|
error,
|
||||||
helperText,
|
helperText,
|
||||||
onFileUpload,
|
onFileUpload,
|
||||||
onFileDelete,
|
onFileDelete,
|
||||||
inputProps,
|
inputProps,
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles({});
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const fileInputAnchor = React.createRef<HTMLInputElement>();
|
const fileInputAnchor = React.createRef<HTMLInputElement>();
|
||||||
|
@ -85,52 +55,46 @@ const FileUploadField: React.FC<FileUploadFieldProps> = props => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={className}>
|
<Box display="flex" justifyContent="flex-end">
|
||||||
{file.label ? (
|
{file.label ? (
|
||||||
<div className={classes.uploadFileContent}>
|
<Box display="flex" gap={5} alignItems="center">
|
||||||
<div className={classes.uploadFileName}>
|
<Text variant="caption">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : (
|
) : (
|
||||||
<a
|
<a href={file.file?.url} target="blank">
|
||||||
href={file.file?.url}
|
|
||||||
target="blank"
|
|
||||||
className={classes.fileUrl}
|
|
||||||
>
|
|
||||||
{file.label}
|
{file.label}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Text>
|
||||||
<IconButton
|
<Button
|
||||||
|
icon={<TrashBinIcon />}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
color="primary"
|
|
||||||
onClick={handleFileDelete}
|
onClick={handleFileDelete}
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
data-test-id="button-delete-file"
|
data-test-id="button-delete-file"
|
||||||
>
|
type="button"
|
||||||
<DeleteIcon />
|
/>
|
||||||
</IconButton>
|
</Box>
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<Button
|
||||||
<Button
|
onClick={clickFileInput}
|
||||||
onClick={clickFileInput}
|
disabled={disabled || loading}
|
||||||
disabled={disabled || loading}
|
variant="secondary"
|
||||||
variant="secondary"
|
data-test-id="button-upload-file"
|
||||||
data-test-id="button-upload-file"
|
type="button"
|
||||||
>
|
>
|
||||||
{intl.formatMessage(commonMessages.chooseFile)}
|
{intl.formatMessage(commonMessages.chooseFile)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<Typography variant="caption" className={classes.errorText}>
|
<Text variant="caption" color="textCriticalDefault">
|
||||||
{helperText}
|
{helperText}
|
||||||
</Typography>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
<input
|
<input
|
||||||
className={classes.fileField}
|
style={{ display: "none" }}
|
||||||
id="fileUpload"
|
id="fileUpload"
|
||||||
onChange={event => onFileUpload(event.target.files[0])}
|
onChange={event => onFileUpload(event.target.files[0])}
|
||||||
type="file"
|
type="file"
|
||||||
|
|
|
@ -1,32 +1,21 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import { vars } from "@saleor/macaw-ui/next";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface HrProps {
|
/**
|
||||||
|
* @deprecated use `Divider` component from `@saleor/macaw-ui/next`
|
||||||
|
*/
|
||||||
|
export const Hr: React.FC<{
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}> = ({ className }) => (
|
||||||
|
<Box
|
||||||
const useStyles = makeStyles(
|
as="hr"
|
||||||
{
|
className={className}
|
||||||
root: {
|
backgroundColor="surfaceNeutralDepressed"
|
||||||
backgroundColor: vars.colors.border.neutralPlain,
|
borderWidth={0}
|
||||||
border: "none",
|
width="100%"
|
||||||
display: "block",
|
height={1}
|
||||||
height: 1,
|
/>
|
||||||
margin: 0,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ name: "Hr" },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Hr: React.FC<HrProps> = props => {
|
|
||||||
const { className } = props;
|
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
|
|
||||||
return <hr className={clsx(classes.root, className)} />;
|
|
||||||
};
|
|
||||||
Hr.displayName = "Hr";
|
Hr.displayName = "Hr";
|
||||||
export default Hr;
|
export default Hr;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { props } from "./fixtures";
|
import { props } from "./fixtures";
|
||||||
import Metadata from "./Metadata";
|
import { Metadata } from "./Metadata";
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const { change, data } = useForm(props.data, jest.fn());
|
const { change, data } = useForm(props.data, jest.fn());
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { MetadataInput } from "@dashboard/graphql";
|
import { MetadataInput } from "@dashboard/graphql";
|
||||||
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
||||||
import { removeAtIndex, updateAtIndex } from "@dashboard/utils/lists";
|
import { removeAtIndex, updateAtIndex } from "@dashboard/utils/lists";
|
||||||
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import CardSpacer from "../CardSpacer";
|
import { MetadataCard, MetadataCardProps } from "./MetadataCard";
|
||||||
import MetadataCard, { MetadataCardProps } from "./MetadataCard";
|
|
||||||
import { EventDataAction, EventDataField } from "./types";
|
import { EventDataAction, EventDataField } from "./types";
|
||||||
import { getDataKey, parseEventData } from "./utils";
|
import { getDataKey, parseEventData } from "./utils";
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export interface MetadataProps
|
||||||
data: Record<"metadata" | "privateMetadata", MetadataInput[]>;
|
data: Record<"metadata" | "privateMetadata", MetadataInput[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Metadata: React.FC<MetadataProps> = ({ data, onChange }) => {
|
export const Metadata: React.FC<MetadataProps> = ({ data, onChange }) => {
|
||||||
const change = (event: ChangeEvent, isPrivate: boolean) => {
|
const change = (event: ChangeEvent, isPrivate: boolean) => {
|
||||||
const { action, field, fieldIndex, value } = parseEventData(event);
|
const { action, field, fieldIndex, value } = parseEventData(event);
|
||||||
const key = getDataKey(isPrivate);
|
const key = getDataKey(isPrivate);
|
||||||
|
@ -53,21 +53,17 @@ const Metadata: React.FC<MetadataProps> = ({ data, onChange }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box display="grid" gap={5} paddingBottom={9}>
|
||||||
<MetadataCard
|
<MetadataCard
|
||||||
data={data?.metadata}
|
data={data?.metadata}
|
||||||
isPrivate={false}
|
isPrivate={false}
|
||||||
onChange={event => change(event, false)}
|
onChange={event => change(event, false)}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
<MetadataCard
|
<MetadataCard
|
||||||
data={data?.privateMetadata}
|
data={data?.privateMetadata}
|
||||||
isPrivate={true}
|
isPrivate={true}
|
||||||
onChange={event => change(event, true)}
|
onChange={event => change(event, true)}
|
||||||
/>
|
/>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Metadata.displayName = "Metadata";
|
|
||||||
export default Metadata;
|
|
||||||
|
|
|
@ -1,27 +1,44 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||||
import { MetadataInput } from "@dashboard/graphql";
|
import { MetadataInput } from "@dashboard/graphql";
|
||||||
import { FormChange } from "@dashboard/hooks/useForm";
|
import { FormChange } from "@dashboard/hooks/useForm";
|
||||||
import {
|
import {
|
||||||
Card,
|
|
||||||
CardActions,
|
|
||||||
CardContent,
|
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHead,
|
TableHead,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { DeleteIcon, ExpandIcon, IconButton } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import clsx from "clsx";
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ChervonDownIcon,
|
||||||
|
ChervonUpIcon,
|
||||||
|
Text,
|
||||||
|
TrashBinIcon,
|
||||||
|
vars,
|
||||||
|
} from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CardTitle from "../CardTitle";
|
import { DashboardCard } from "../Card";
|
||||||
import Skeleton from "../Skeleton";
|
import Skeleton from "../Skeleton";
|
||||||
import useStyles from "./styles";
|
import { EventDataAction } from "./types";
|
||||||
import { EventDataAction, EventDataField } from "./types";
|
import { nameInputPrefix, nameSeparator, valueInputPrefix } from "./utils";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
input: {
|
||||||
|
padding: theme.spacing(0.5, 2),
|
||||||
|
},
|
||||||
|
nameInput: {
|
||||||
|
padding: "13px 16px",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "Metadata",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export interface MetadataCardProps {
|
export interface MetadataCardProps {
|
||||||
data: MetadataInput[];
|
data: MetadataInput[];
|
||||||
|
@ -29,11 +46,7 @@ export interface MetadataCardProps {
|
||||||
onChange: FormChange;
|
onChange: FormChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nameSeparator = ":";
|
export const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
export const nameInputPrefix = EventDataField.name;
|
|
||||||
export const valueInputPrefix = EventDataField.value;
|
|
||||||
|
|
||||||
const MetadataCard: React.FC<MetadataCardProps> = ({
|
|
||||||
data,
|
data,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -43,49 +56,47 @@ const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<DashboardCard
|
||||||
data-test-id="metadata-editor"
|
data-test-id="metadata-editor"
|
||||||
data-test-is-private={isPrivate}
|
data-test-is-private={isPrivate}
|
||||||
data-test-expanded={expanded}
|
data-test-expanded={expanded}
|
||||||
|
gap={3}
|
||||||
>
|
>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
className={classes.header}
|
<Box
|
||||||
title={
|
display="flex"
|
||||||
<>
|
justifyContent="space-between"
|
||||||
{isPrivate
|
onClick={() => setExpanded(!expanded)}
|
||||||
? intl.formatMessage({
|
cursor="pointer"
|
||||||
id: "ETHnjq",
|
>
|
||||||
defaultMessage: "Private Metadata",
|
{isPrivate
|
||||||
description: "header",
|
? intl.formatMessage({
|
||||||
})
|
id: "ETHnjq",
|
||||||
: intl.formatMessage({
|
defaultMessage: "Private Metadata",
|
||||||
id: "VcI+Zh",
|
description: "header",
|
||||||
defaultMessage: "Metadata",
|
})
|
||||||
description: "header",
|
: intl.formatMessage({
|
||||||
})}
|
id: "VcI+Zh",
|
||||||
<IconButton
|
defaultMessage: "Metadata",
|
||||||
className={clsx(classes.expandBtn, {
|
description: "header",
|
||||||
[classes.rotate]: expanded,
|
|
||||||
})}
|
})}
|
||||||
hoverOutline={false}
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
data-test-id="expand"
|
data-test-id="expand"
|
||||||
onClick={() => setExpanded(!expanded)}
|
type="button"
|
||||||
>
|
icon={expanded ? <ChervonUpIcon /> : <ChervonDownIcon />}
|
||||||
<ExpandIcon />
|
/>
|
||||||
</IconButton>
|
</Box>
|
||||||
</>
|
</DashboardCard.Title>
|
||||||
}
|
|
||||||
/>
|
|
||||||
{data === undefined ? (
|
{data === undefined ? (
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<CardContent className={classes.content}>
|
{data.length > 0 && (
|
||||||
{data.length > 0 && (
|
<DashboardCard.Content>
|
||||||
<Typography color="textSecondary" variant="body2">
|
<Text variant="caption">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="2+v1wX"
|
id="2+v1wX"
|
||||||
defaultMessage="{number,plural,one{{number} string} other{{number} strings}}"
|
defaultMessage="{number,plural,one{{number} string} other{{number} strings}}"
|
||||||
|
@ -94,52 +105,63 @@ const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
number: data.length,
|
number: data.length,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Text>
|
||||||
)}
|
</DashboardCard.Content>
|
||||||
</CardContent>
|
)}
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<>
|
<>
|
||||||
{data.length === 0 ? (
|
{data.length === 0 ? (
|
||||||
<CardContent className={classes.emptyContainer}>
|
<DashboardCard.Content>
|
||||||
<Typography variant="body2" color="textSecondary">
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="cY6H2C"
|
id="cY6H2C"
|
||||||
defaultMessage="No metadata created for this element. Use the button below to add new metadata field."
|
defaultMessage="No metadata created for this element. Use the button below to add new metadata field."
|
||||||
description="empty metadata text"
|
description="empty metadata text"
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Text>
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
) : (
|
) : (
|
||||||
<Table className={classes.table}>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRowLink>
|
<TableRowLink>
|
||||||
<TableCell className={classes.colNameHeader}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
id="nudPsY"
|
<FormattedMessage
|
||||||
defaultMessage="Field"
|
id="nudPsY"
|
||||||
description="metadata field name, header"
|
defaultMessage="Field"
|
||||||
/>
|
description="metadata field name, header"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colValue}>
|
<TableCell style={{ paddingLeft: vars.space[11] }}>
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
id="LkuDEb"
|
<FormattedMessage
|
||||||
defaultMessage="Value"
|
id="LkuDEb"
|
||||||
description="metadata field value, header"
|
defaultMessage="Value"
|
||||||
/>
|
description="metadata field value, header"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colActionHeader}>
|
<TableCell
|
||||||
<FormattedMessage
|
style={{
|
||||||
id="nEixpu"
|
textAlign: "end",
|
||||||
defaultMessage="Actions"
|
paddingRight: vars.space[9],
|
||||||
description="table action"
|
}}
|
||||||
/>
|
>
|
||||||
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage
|
||||||
|
id="nEixpu"
|
||||||
|
defaultMessage="Actions"
|
||||||
|
description="table action"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.map((field, fieldIndex) => (
|
{data.map((field, fieldIndex) => (
|
||||||
<TableRowLink data-test-id="field" key={fieldIndex}>
|
<TableRowLink data-test-id="field" key={fieldIndex}>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<TextField
|
<TextField
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
|
@ -155,7 +177,7 @@ const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
value={field.key}
|
value={field.key}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colValue}>
|
<TableCell>
|
||||||
<TextField
|
<TextField
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
|
@ -172,31 +194,34 @@ const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
value={field.value}
|
value={field.value}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colAction}>
|
<TableCell style={{ paddingRight: vars.space[9] }}>
|
||||||
<IconButton
|
<Box display="flex" justifyContent="flex-end">
|
||||||
variant="secondary"
|
<Button
|
||||||
data-test-id={"delete-field-" + fieldIndex}
|
variant="secondary"
|
||||||
onClick={() =>
|
data-test-id={"delete-field-" + fieldIndex}
|
||||||
onChange({
|
onClick={() =>
|
||||||
target: {
|
onChange({
|
||||||
name: EventDataAction.delete,
|
target: {
|
||||||
value: fieldIndex,
|
name: EventDataAction.delete,
|
||||||
},
|
value: fieldIndex,
|
||||||
})
|
},
|
||||||
}
|
})
|
||||||
>
|
}
|
||||||
<DeleteIcon />
|
type="button"
|
||||||
</IconButton>
|
icon={<TrashBinIcon />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
)}
|
||||||
<CardActions className={classes.actions}>
|
<DashboardCard.Content marginTop={5} paddingLeft={9}>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
data-test-id="add-field"
|
data-test-id="add-field"
|
||||||
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onChange({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
|
@ -212,14 +237,11 @@ const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
description="add metadata field,button"
|
description="add metadata field,button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</CardActions>
|
</DashboardCard.Content>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MetadataCard.displayName = "MetadataCard";
|
|
||||||
export default MetadataCard;
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
export * from "./Metadata";
|
export * from "./Metadata";
|
||||||
export * from "./MetadataCard";
|
export * from "./MetadataCard";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export { default } from "./Metadata";
|
|
||||||
export { default as MetadataCard } from "./MetadataCard";
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => {
|
|
||||||
const colAction: React.CSSProperties = {
|
|
||||||
textAlign: "right",
|
|
||||||
width: 130,
|
|
||||||
};
|
|
||||||
const colName: React.CSSProperties = {
|
|
||||||
width: 220,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
colAction: {
|
|
||||||
"&:last-child": {
|
|
||||||
...colAction,
|
|
||||||
paddingRight: theme.spacing(4),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
colActionHeader: {
|
|
||||||
...colAction,
|
|
||||||
},
|
|
||||||
colName: {
|
|
||||||
...colName,
|
|
||||||
},
|
|
||||||
colNameHeader: {
|
|
||||||
...colName,
|
|
||||||
},
|
|
||||||
colValue: {},
|
|
||||||
actions: {
|
|
||||||
"&&": {
|
|
||||||
paddingBottom: theme.spacing(2),
|
|
||||||
paddingTop: theme.spacing(2),
|
|
||||||
paddingLeft: theme.spacing(4),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
paddingBottom: 0,
|
|
||||||
paddingTop: theme.spacing(),
|
|
||||||
},
|
|
||||||
emptyContainer: {
|
|
||||||
paddingBottom: 0,
|
|
||||||
paddingTop: 0,
|
|
||||||
},
|
|
||||||
expandBtn: {
|
|
||||||
position: "relative",
|
|
||||||
left: theme.spacing(1),
|
|
||||||
top: -2,
|
|
||||||
transition: theme.transitions.create("transform", {
|
|
||||||
duration: theme.transitions.duration.shorter,
|
|
||||||
}),
|
|
||||||
border: 0,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
"&&": {
|
|
||||||
paddingBottom: theme.spacing(1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
padding: theme.spacing(0.5, 2),
|
|
||||||
},
|
|
||||||
nameInput: {
|
|
||||||
padding: `13px 16px`,
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
tableLayout: "fixed",
|
|
||||||
},
|
|
||||||
rotate: {
|
|
||||||
transform: "rotate(-180deg)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Metadata",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default useStyles;
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
||||||
|
|
||||||
import { nameSeparator } from "./MetadataCard";
|
|
||||||
import { EventData, EventDataAction, EventDataField } from "./types";
|
import { EventData, EventDataAction, EventDataField } from "./types";
|
||||||
|
|
||||||
|
export const nameSeparator = ":";
|
||||||
|
export const nameInputPrefix = EventDataField.name;
|
||||||
|
export const valueInputPrefix = EventDataField.value;
|
||||||
|
|
||||||
export function parseEventData(event: ChangeEvent): EventData {
|
export function parseEventData(event: ChangeEvent): EventData {
|
||||||
let action: EventDataAction;
|
let action: EventDataAction;
|
||||||
let field: EventDataField = null;
|
let field: EventDataField = null;
|
||||||
|
|
|
@ -38,11 +38,14 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
helperText,
|
helperText,
|
||||||
editorRef,
|
editorRef,
|
||||||
onInitialize,
|
onInitialize,
|
||||||
|
onChange,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles({});
|
const classes = useStyles({});
|
||||||
const id = useId(defaultId);
|
const id = useId(defaultId);
|
||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
|
const [hasValue, setHasValue] = React.useState(false);
|
||||||
|
const isTyped = Boolean(hasValue || isFocused);
|
||||||
|
|
||||||
const handleInitialize = React.useCallback((editor: EditorCore) => {
|
const handleInitialize = React.useCallback((editor: EditorCore) => {
|
||||||
if (onInitialize) {
|
if (onInitialize) {
|
||||||
|
@ -68,7 +71,17 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
<InputLabel focused={true} shrink={true}>
|
<InputLabel
|
||||||
|
focused={true}
|
||||||
|
shrink={true}
|
||||||
|
classes={{
|
||||||
|
disabled: classes.labelDisabled,
|
||||||
|
error: classes.labelError,
|
||||||
|
root: classes.labelRoot,
|
||||||
|
}}
|
||||||
|
error={error}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
{hasRendered && (
|
{hasRendered && (
|
||||||
|
@ -76,17 +89,25 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
// match with the id of holder div
|
// match with the id of holder div
|
||||||
holder={id}
|
holder={id}
|
||||||
tools={tools}
|
tools={tools}
|
||||||
// LogLeves is undefined at runtime
|
// Log level is undefined at runtime
|
||||||
logLevel={"ERROR" as LogLevels.ERROR}
|
logLevel={"ERROR" as LogLevels.ERROR}
|
||||||
onInitialize={handleInitialize}
|
onInitialize={handleInitialize}
|
||||||
|
onChange={async event => {
|
||||||
|
const editorJsValue = await event.saver.save();
|
||||||
|
setHasValue(editorJsValue.blocks.length > 0);
|
||||||
|
return onChange?.();
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id={id}
|
id={id}
|
||||||
className={clsx(classes.editor, classes.root, {
|
className={clsx(classes.editor, classes.root, {
|
||||||
|
[classes.rootErrorFocus]: isFocused && error,
|
||||||
[classes.rootActive]: isFocused,
|
[classes.rootActive]: isFocused,
|
||||||
[classes.rootDisabled]: disabled,
|
[classes.rootDisabled]: disabled,
|
||||||
[classes.rootError]: error,
|
[classes.rootError]: error,
|
||||||
|
[classes.rootTyped]:
|
||||||
|
isTyped || props.defaultValue?.blocks?.length > 0,
|
||||||
})}
|
})}
|
||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { alpha } from "@material-ui/core/styles";
|
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import { vars } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => {
|
theme => {
|
||||||
const hover = {
|
const hover = {
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
background: alpha(theme.palette.primary.main, 0.1),
|
background: vars.colors.background.interactiveNeutralHighlightHovering,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDarkMode = theme.palette.type === "dark";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
editor: {
|
editor: {
|
||||||
"& .codex-editor": {
|
"& .codex-editor": {
|
||||||
|
@ -20,17 +18,18 @@ const useStyles = makeStyles(
|
||||||
minHeight: 24,
|
minHeight: 24,
|
||||||
},
|
},
|
||||||
"& .ce-block--selected .ce-block__content": {
|
"& .ce-block--selected .ce-block__content": {
|
||||||
background: `${alpha(theme.palette.primary.main, 0.2)} !important`,
|
background: `${vars.colors.background.interactiveNeutralHighlightPressing} !important`,
|
||||||
},
|
},
|
||||||
"& .ce-block__content": {
|
"& .ce-block__content": {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
maxWidth: "unset",
|
maxWidth: "unset",
|
||||||
|
paddingRight: "54px",
|
||||||
},
|
},
|
||||||
"& .ce-conversion-tool": {
|
"& .ce-conversion-tool": {
|
||||||
...hover,
|
...hover,
|
||||||
},
|
},
|
||||||
"& .ce-conversion-tool--focused": {
|
"& .ce-conversion-tool--focused": {
|
||||||
background: `${alpha(theme.palette.primary.main, 0.1)} !important`,
|
background: `${vars.colors.background.interactiveNeutralHighlightHovering} !important`,
|
||||||
},
|
},
|
||||||
"& .ce-conversion-tool__icon": {
|
"& .ce-conversion-tool__icon": {
|
||||||
background: "none",
|
background: "none",
|
||||||
|
@ -77,13 +76,21 @@ const useStyles = makeStyles(
|
||||||
...hover,
|
...hover,
|
||||||
},
|
},
|
||||||
"& .ce-popover": {
|
"& .ce-popover": {
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: vars.colors.background.surfaceNeutralPlain,
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: "-186px",
|
||||||
|
},
|
||||||
|
"& .ce-settings": {
|
||||||
|
position: "absolute",
|
||||||
|
left: "-56px",
|
||||||
},
|
},
|
||||||
"& .ce-popover__item": {
|
"& .ce-popover__item": {
|
||||||
...hover,
|
...hover,
|
||||||
},
|
},
|
||||||
"& .ce-popover__item-icon": {
|
"& .ce-popover__item-icon": {
|
||||||
color: theme.palette.saleor.generic.verydark,
|
color: vars.colors.foreground.iconNeutralDefault,
|
||||||
|
backgroundColor: vars.colors.background.surfaceNeutralPlain,
|
||||||
},
|
},
|
||||||
|
|
||||||
"& .codex-editor__loader": {
|
"& .codex-editor__loader": {
|
||||||
|
@ -97,39 +104,62 @@ const useStyles = makeStyles(
|
||||||
paddingBottom: "0 !important",
|
paddingBottom: "0 !important",
|
||||||
},
|
},
|
||||||
"& a": {
|
"& a": {
|
||||||
color: theme.palette.primary.light,
|
color: vars.colors.foreground.textBrandDefault,
|
||||||
},
|
},
|
||||||
"&:not($rootDisabled):hover": {
|
"& .ce-popover__item--focused": {
|
||||||
borderColor: isDarkMode
|
background: `${vars.colors.background.interactiveNeutralHighlightHovering} !important`,
|
||||||
? theme.palette.saleor.main[2]
|
},
|
||||||
: theme.palette.saleor.main[4],
|
"& .cdx-search-field": {
|
||||||
|
backgroundColor: vars.colors.background.surfaceNeutralPlain,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
border: `1px solid ${theme.palette.saleor.main[4]}`,
|
border: "1px solid transparent",
|
||||||
borderRadius: 4,
|
borderRadius: vars.borderRadius[3],
|
||||||
fontSize: theme.typography.body1.fontSize,
|
fontSize: vars.fontSize.bodyMedium,
|
||||||
|
backgroundColor: vars.colors.background.surfaceNeutralHighlight,
|
||||||
minHeight: 56,
|
minHeight: 56,
|
||||||
padding: theme.spacing(3, 2),
|
|
||||||
paddingBottom: theme.spacing(),
|
|
||||||
paddingLeft: 10,
|
|
||||||
position: "relative",
|
position: "relative",
|
||||||
transition: theme.transitions.duration.short + "ms",
|
transition: theme.transitions.duration.short + "ms",
|
||||||
|
padding: theme.spacing(3, 2),
|
||||||
|
paddingBottom: theme.spacing(),
|
||||||
|
paddingLeft: vars.space[4],
|
||||||
|
"&:hover": {
|
||||||
|
border: `1px solid ${vars.colors.border.neutralHighlight}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rootActive: {
|
rootActive: {
|
||||||
borderColor: theme.palette.saleor.main[1],
|
border: `1px solid ${vars.colors.border.brandSubdued} !important`,
|
||||||
|
backgroundColor: `${vars.colors.background.interactiveNeutralHighlightDefault} !important`,
|
||||||
},
|
},
|
||||||
rootDisabled: {
|
rootDisabled: {
|
||||||
...theme.overrides.MuiOutlinedInput.root["&$disabled"]["& fieldset"],
|
pointerEvents: "none",
|
||||||
background: theme.palette.background.default,
|
backgroundColor: vars.colors.background.surfaceNeutralPlain,
|
||||||
color: theme.palette.saleor.main[4],
|
border: `1px solid ${vars.colors.border.neutralHighlight}`,
|
||||||
|
color: vars.colors.foreground.textNeutralDisabled,
|
||||||
},
|
},
|
||||||
rootError: {
|
rootError: {
|
||||||
borderColor: theme.palette.error.main,
|
backgroundColor: vars.colors.background.surfaceCriticalSubdued,
|
||||||
},
|
},
|
||||||
rootStatic: {
|
rootStatic: {
|
||||||
fontSize: theme.typography.body1.fontSize,
|
fontSize: theme.typography.body1.fontSize,
|
||||||
},
|
},
|
||||||
|
labelRoot: {
|
||||||
|
marginLeft: "-6px",
|
||||||
|
color: `${vars.colors.foreground.textNeutralSubdued} !important`,
|
||||||
|
},
|
||||||
|
labelError: {
|
||||||
|
color: `${vars.colors.foreground.textCriticalSubdued} !important`,
|
||||||
|
},
|
||||||
|
rootErrorFocus: {
|
||||||
|
border: "1px solid transparent !important",
|
||||||
|
},
|
||||||
|
labelDisabled: {
|
||||||
|
color: `${vars.colors.foreground.textNeutralDisabled} !important`,
|
||||||
|
},
|
||||||
|
rootTyped: {
|
||||||
|
backgroundColor: vars.colors.background.surfaceNeutralPlain,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ name: "RichTextEditor" },
|
{ name: "RichTextEditor" },
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import {
|
import {
|
||||||
CollectionErrorFragment,
|
CollectionErrorFragment,
|
||||||
PageErrorFragment,
|
PageErrorFragment,
|
||||||
|
@ -6,15 +5,13 @@ import {
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { getFieldError, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFieldError, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import getPageErrorMessage from "@dashboard/utils/errors/page";
|
import getPageErrorMessage from "@dashboard/utils/errors/page";
|
||||||
import { Card, CardContent, TextField, Typography } from "@material-ui/core";
|
import { TextField } from "@material-ui/core";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import clsx from "clsx";
|
import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||||
import slugify from "slugify";
|
|
||||||
|
|
||||||
import CardTitle from "../CardTitle";
|
import { DashboardCard } from "../Card";
|
||||||
import FormSpacer from "../FormSpacer";
|
|
||||||
|
|
||||||
enum SeoField {
|
enum SeoField {
|
||||||
slug = "slug",
|
slug = "slug",
|
||||||
|
@ -28,28 +25,7 @@ const maxTitleLength = 70;
|
||||||
const maxDescriptionLength = 300;
|
const maxDescriptionLength = 300;
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
{
|
||||||
addressBar: {
|
|
||||||
color: "#006621",
|
|
||||||
fontSize: "13px",
|
|
||||||
lineHeight: "16px",
|
|
||||||
marginBottom: "2px",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
descriptionBar: {
|
|
||||||
color: "#545454",
|
|
||||||
fontSize: "13px",
|
|
||||||
lineHeight: "18px",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
},
|
|
||||||
helperText: {
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
@ -59,21 +35,7 @@ const useStyles = makeStyles(
|
||||||
},
|
},
|
||||||
display: "flex",
|
display: "flex",
|
||||||
},
|
},
|
||||||
preview: {
|
},
|
||||||
minHeight: theme.spacing(10),
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
titleBar: {
|
|
||||||
color: "#1a0dab",
|
|
||||||
fontSize: "18px",
|
|
||||||
lineHeight: "21px",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
textDecoration: "none",
|
|
||||||
wordWrap: "break-word",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "SeoForm" },
|
{ name: "SeoForm" },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -95,7 +57,7 @@ interface SeoFormProps {
|
||||||
onClick?();
|
onClick?();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SeoForm: React.FC<SeoFormProps> = props => {
|
export const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
const {
|
const {
|
||||||
description,
|
description,
|
||||||
descriptionPlaceholder,
|
descriptionPlaceholder,
|
||||||
|
@ -157,17 +119,18 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
const getError = (fieldName: SeoField) => getFieldError(errors, fieldName);
|
const getError = (fieldName: SeoField) => getFieldError(errors, fieldName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage({
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
id: "TGX4T1",
|
<FormattedMessage
|
||||||
defaultMessage: "Search Engine Preview",
|
defaultMessage="Search Engine Preview"
|
||||||
})}
|
id="TGX4T1"
|
||||||
toolbar={
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="secondary"
|
||||||
onClick={toggleExpansion}
|
onClick={toggleExpansion}
|
||||||
data-test-id="edit-seo"
|
data-test-id="edit-seo"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="s5Imt5"
|
id="s5Imt5"
|
||||||
|
@ -175,91 +138,77 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
description="button"
|
description="button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
</Box>
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
{shouldDisplayHelperText && (
|
{shouldDisplayHelperText && <Text>{helperText}</Text>}
|
||||||
<Typography className={clsx({ [classes.helperText]: expanded })}>
|
|
||||||
{helperText}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className={classes.container}>
|
<Box display="grid" gap={5}>
|
||||||
<TextField
|
<Box>
|
||||||
error={!!getError(SeoField.slug) || slug.length > maxSlugLength}
|
<Input
|
||||||
name={SeoField.slug}
|
error={!!getError(SeoField.slug) || slug.length > maxSlugLength}
|
||||||
label={
|
name={SeoField.slug}
|
||||||
<div className={classes.labelContainer}>
|
label={
|
||||||
<div className={classes.label}>
|
<Box display="flex" gap={3}>
|
||||||
<FormattedMessage id="IoDlcd" defaultMessage="Slug" />
|
<Box as="span">
|
||||||
</div>
|
<FormattedMessage defaultMessage="Slug" id="IoDlcd" />
|
||||||
{slug?.length > 0 && (
|
</Box>
|
||||||
<span>
|
{slug?.length > 0 && (
|
||||||
<FormattedMessage
|
<Box as="span">
|
||||||
id="ChAjJu"
|
<FormattedMessage
|
||||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
defaultMessage="({numberOfCharacters} of {maxCharacters} characters)"
|
||||||
description="character limit"
|
id="yi1HSj"
|
||||||
values={{
|
values={{
|
||||||
maxCharacters: maxSlugLength,
|
maxCharacters: maxSlugLength,
|
||||||
numberOfCharacters: slug?.length,
|
numberOfCharacters: slug?.length,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
}
|
}
|
||||||
InputProps={{
|
helperText={getSlugHelperMessage()}
|
||||||
inputProps: {
|
size="small"
|
||||||
maxLength: maxSlugLength,
|
value={slug}
|
||||||
},
|
onChange={handleSlugChange}
|
||||||
}}
|
disabled={loading || disabled}
|
||||||
helperText={getSlugHelperMessage()}
|
maxLength={maxSlugLength}
|
||||||
value={slug}
|
placeholder={slugPlaceholder}
|
||||||
disabled={loading || disabled}
|
/>
|
||||||
placeholder={slug || slugify(slugPlaceholder, { lower: true })}
|
</Box>
|
||||||
onChange={handleSlugChange}
|
<Input
|
||||||
fullWidth
|
size="small"
|
||||||
/>
|
|
||||||
<FormSpacer />
|
|
||||||
<TextField
|
|
||||||
error={title?.length > maxTitleLength}
|
error={title?.length > maxTitleLength}
|
||||||
name={SeoField.title}
|
name={SeoField.title}
|
||||||
label={
|
|
||||||
<div className={classes.labelContainer}>
|
|
||||||
<div className={classes.label}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="w2Cewo"
|
|
||||||
defaultMessage="Search engine title"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{title?.length > 0 && (
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id="ChAjJu"
|
|
||||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
|
||||||
description="character limit"
|
|
||||||
values={{
|
|
||||||
maxCharacters: maxTitleLength,
|
|
||||||
numberOfCharacters: title.length,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
InputProps={{
|
|
||||||
inputProps: {
|
|
||||||
maxLength: maxTitleLength,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
helperText={intl.formatMessage(seoFieldMessage)}
|
|
||||||
value={title ?? ""}
|
value={title ?? ""}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
placeholder={titlePlaceholder}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
fullWidth
|
maxLength={maxTitleLength}
|
||||||
|
placeholder={titlePlaceholder}
|
||||||
|
helperText={intl.formatMessage(seoFieldMessage)}
|
||||||
|
label={
|
||||||
|
<Box display="flex" gap={3}>
|
||||||
|
<Box as="span">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Search engine title"
|
||||||
|
id="w2Cewo"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{title?.length > 0 && (
|
||||||
|
<Box as="span">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="({numberOfCharacters} of {maxCharacters} characters)"
|
||||||
|
id="yi1HSj"
|
||||||
|
values={{
|
||||||
|
maxCharacters: maxTitleLength,
|
||||||
|
numberOfCharacters: title?.length,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
|
||||||
<TextField
|
<TextField
|
||||||
error={description?.length > maxDescriptionLength}
|
error={description?.length > maxDescriptionLength}
|
||||||
name={SeoField.description}
|
name={SeoField.description}
|
||||||
|
@ -300,11 +249,9 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
placeholder={descriptionPlaceholder}
|
placeholder={descriptionPlaceholder}
|
||||||
rows={10}
|
rows={10}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
SeoForm.displayName = "SeoForm";
|
|
||||||
export default SeoForm;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./SeoForm";
|
|
||||||
export * from "./SeoForm";
|
export * from "./SeoForm";
|
||||||
|
|
|
@ -45,13 +45,15 @@ export interface SingleAutocompleteSelectFieldProps
|
||||||
nakedInput?: boolean;
|
nakedInput?: boolean;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
popperPlacement?: PopperPlacementType;
|
popperPlacement?: PopperPlacementType;
|
||||||
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
const DebounceAutocomplete: React.ComponentType<DebounceProps<string>> =
|
||||||
string
|
Debounce;
|
||||||
>> = Debounce;
|
|
||||||
|
|
||||||
const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectFieldProps> = props => {
|
const SingleAutocompleteSelectFieldComponent: React.FC<
|
||||||
|
SingleAutocompleteSelectFieldProps
|
||||||
|
> = props => {
|
||||||
const {
|
const {
|
||||||
add,
|
add,
|
||||||
allowCustomValues,
|
allowCustomValues,
|
||||||
|
@ -78,6 +80,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
nakedInput = false,
|
nakedInput = false,
|
||||||
onBlur,
|
onBlur,
|
||||||
popperPlacement = "bottom-end",
|
popperPlacement = "bottom-end",
|
||||||
|
id,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
@ -243,6 +246,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
inputRef={input}
|
inputRef={input}
|
||||||
|
id={id}
|
||||||
/>
|
/>
|
||||||
{isOpen && (!!inputValue || !!choices.length) && (
|
{isOpen && (!!inputValue || !!choices.length) && (
|
||||||
<Popper
|
<Popper
|
||||||
|
@ -284,11 +288,9 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SingleAutocompleteSelectField: React.FC<SingleAutocompleteSelectFieldProps> = ({
|
const SingleAutocompleteSelectField: React.FC<
|
||||||
choices,
|
SingleAutocompleteSelectFieldProps
|
||||||
fetchChoices,
|
> = ({ choices, fetchChoices, ...rest }) => {
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
const [query, setQuery] = React.useState("");
|
const [query, setQuery] = React.useState("");
|
||||||
|
|
||||||
if (fetchChoices) {
|
if (fetchChoices) {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./TopNavMenu";
|
|
|
@ -9,7 +9,7 @@ import CardMenu from "@dashboard/components/CardMenu/CardMenu";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
||||||
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { createSaleChannelsChangeHandler } from "@dashboard/discounts/handlers";
|
import { createSaleChannelsChangeHandler } from "@dashboard/discounts/handlers";
|
||||||
import { saleListUrl } from "@dashboard/discounts/urls";
|
import { saleListUrl } from "@dashboard/discounts/urls";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
createChannelsChangeHandler,
|
createChannelsChangeHandler,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailability
|
||||||
import CountryList from "@dashboard/components/CountryList";
|
import CountryList from "@dashboard/components/CountryList";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
import { Tab, TabContainer } from "@dashboard/components/Tab";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
OrderDetailsFragment,
|
OrderDetailsFragment,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { Button } from "@dashboard/components/Button";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import CardTitle from "@dashboard/components/CardTitle";
|
||||||
import HorizontalSpacer from "@dashboard/components/HorizontalSpacer";
|
import HorizontalSpacer from "@dashboard/components/HorizontalSpacer";
|
||||||
import { Hr } from "@dashboard/components/Hr";
|
|
||||||
import Money from "@dashboard/components/Money";
|
import Money from "@dashboard/components/Money";
|
||||||
import { Pill } from "@dashboard/components/Pill";
|
import { Pill } from "@dashboard/components/Pill";
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
import Skeleton from "@dashboard/components/Skeleton";
|
||||||
|
@ -12,6 +11,7 @@ import {
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { Card, CardContent } from "@material-ui/core";
|
import { Card, CardContent } from "@material-ui/core";
|
||||||
|
import { Divider } from "@saleor/macaw-ui/next";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
@ -201,7 +201,7 @@ const OrderPayment: React.FC<OrderPaymentProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Hr />
|
<Divider />
|
||||||
<CardContent className={classes.payments}>
|
<CardContent className={classes.payments}>
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
{!!usedGiftCardAmount && (
|
{!!usedGiftCardAmount && (
|
||||||
|
|
|
@ -31,6 +31,7 @@ jest.mock("@saleor/macaw-ui", () => ({
|
||||||
|
|
||||||
jest.mock("@saleor/macaw-ui/next", () => ({
|
jest.mock("@saleor/macaw-ui/next", () => ({
|
||||||
useTheme: jest.fn(() => () => ({})),
|
useTheme: jest.fn(() => () => ({})),
|
||||||
|
Divider: jest.fn(() => <></>),
|
||||||
vars: {
|
vars: {
|
||||||
colors: {
|
colors: {
|
||||||
border: {
|
border: {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { Button } from "@dashboard/components/Button";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import CardTitle from "@dashboard/components/CardTitle";
|
||||||
import { Hr } from "@dashboard/components/Hr";
|
|
||||||
import { Pill } from "@dashboard/components/Pill";
|
import { Pill } from "@dashboard/components/Pill";
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
import Skeleton from "@dashboard/components/Skeleton";
|
||||||
import { OrderAction, OrderDetailsFragment } from "@dashboard/graphql";
|
import { OrderAction, OrderDetailsFragment } from "@dashboard/graphql";
|
||||||
|
@ -10,6 +9,7 @@ import {
|
||||||
orderSendRefundUrl,
|
orderSendRefundUrl,
|
||||||
} from "@dashboard/orders/urls";
|
} from "@dashboard/orders/urls";
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||||
|
import { Divider } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ const OrderPaymentSummaryCard: React.FC<OrderPaymementProps> = ({
|
||||||
<PaymentsSummary order={order} />
|
<PaymentsSummary order={order} />
|
||||||
{canAnyRefund && (
|
{canAnyRefund && (
|
||||||
<>
|
<>
|
||||||
<Hr />
|
<Divider />
|
||||||
<CardTitle
|
<CardTitle
|
||||||
toolbar={
|
toolbar={
|
||||||
<div className={classes.refundsButtons}>
|
<div className={classes.refundsButtons}>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Form from "@dashboard/components/Form";
|
||||||
import Grid from "@dashboard/components/Grid";
|
import Grid from "@dashboard/components/Grid";
|
||||||
import Hr from "@dashboard/components/Hr";
|
import Hr from "@dashboard/components/Hr";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { PageErrorFragment } from "@dashboard/graphql";
|
import { PageErrorFragment } from "@dashboard/graphql";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Form from "@dashboard/components/Form";
|
||||||
import Grid from "@dashboard/components/Grid";
|
import Grid from "@dashboard/components/Grid";
|
||||||
import Hr from "@dashboard/components/Hr";
|
import Hr from "@dashboard/components/Hr";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { SingleAutocompleteChoiceType } from "@dashboard/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@dashboard/components/SingleAutocompleteSelectField";
|
||||||
|
|
|
@ -4,12 +4,12 @@ import {
|
||||||
} from "@dashboard/attributes/utils/data";
|
} from "@dashboard/attributes/utils/data";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
||||||
import Attributes, { AttributeInput } from "@dashboard/components/Attributes";
|
import { AttributeInput, Attributes } from "@dashboard/components/Attributes";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import VisibilityCard from "@dashboard/components/VisibilityCard";
|
import VisibilityCard from "@dashboard/components/VisibilityCard";
|
||||||
import {
|
import {
|
||||||
PageDetailsFragment,
|
PageDetailsFragment,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import { Metadata, MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
ProductTypeKindEnum,
|
ProductTypeKindEnum,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import ControlledSwitch from "@dashboard/components/ControlledSwitch";
|
import ControlledSwitch from "@dashboard/components/ControlledSwitch";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
import { MetadataFormData } from "@dashboard/components/Metadata/types";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -6,14 +6,14 @@ import CannotDefineChannelsAvailabilityCard from "@dashboard/channels/components
|
||||||
import { ChannelData } from "@dashboard/channels/utils";
|
import { ChannelData } from "@dashboard/channels/utils";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
||||||
import Attributes, { AttributeInput } from "@dashboard/components/Attributes";
|
import { AttributeInput, Attributes } from "@dashboard/components/Attributes";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import { MultiAutocompleteChoiceType } from "@dashboard/components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "@dashboard/components/MultiAutocompleteSelectField";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import {
|
import {
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
ProductChannelListingErrorFragment,
|
ProductChannelListingErrorFragment,
|
||||||
|
@ -30,7 +30,7 @@ import {
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
||||||
import ProductVariantPrice from "@dashboard/products/components/ProductVariantPrice";
|
import { ProductVariantPrice } from "@dashboard/products/components/ProductVariantPrice";
|
||||||
import {
|
import {
|
||||||
ProductCreateUrlQueryParams,
|
ProductCreateUrlQueryParams,
|
||||||
productListUrl,
|
productListUrl,
|
||||||
|
@ -41,10 +41,10 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { FetchMoreProps, RelayToFlat } from "../../../types";
|
import { FetchMoreProps, RelayToFlat } from "../../../types";
|
||||||
import ProductDetailsForm from "../ProductDetailsForm";
|
import { ProductDetailsForm } from "../ProductDetailsForm";
|
||||||
import ProductOrganization from "../ProductOrganization";
|
import { ProductOrganization } from "../ProductOrganization";
|
||||||
import ProductShipping from "../ProductShipping/ProductShipping";
|
import { ProductShipping } from "../ProductShipping";
|
||||||
import ProductStocks from "../ProductStocks";
|
import { ProductStocks } from "../ProductStocks";
|
||||||
import ProductTaxes from "../ProductTaxes";
|
import ProductTaxes from "../ProductTaxes";
|
||||||
import ProductCreateForm, {
|
import ProductCreateForm, {
|
||||||
ProductCreateData,
|
ProductCreateData,
|
||||||
|
@ -263,7 +263,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
richTextGetters={attributeRichTextGetters}
|
richTextGetters={attributeRichTextGetters}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<CardSpacer />
|
|
||||||
{isSimpleProduct && (
|
{isSimpleProduct && (
|
||||||
<>
|
<>
|
||||||
<ProductShipping
|
<ProductShipping
|
||||||
|
@ -273,14 +272,12 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
weightUnit={weightUnit}
|
weightUnit={weightUnit}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
<ProductVariantPrice
|
<ProductVariantPrice
|
||||||
ProductVariantChannelListings={data.channelListings}
|
ProductVariantChannelListings={data.channelListings}
|
||||||
errors={channelsErrors}
|
errors={channelsErrors}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={handlers.changeChannelPrice}
|
onChange={handlers.changeChannelPrice}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
<ProductStocks
|
<ProductStocks
|
||||||
data={data}
|
data={data}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
@ -315,7 +312,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||||
</DetailPageLayout.Content>
|
</DetailPageLayout.Content>
|
||||||
<DetailPageLayout.RightSidebar>
|
<DetailPageLayout.RightSidebar>
|
||||||
|
@ -341,7 +337,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
onProductTypeChange={handlers.selectProductType}
|
onProductTypeChange={handlers.selectProductType}
|
||||||
collectionsInputDisplayValue={selectedCollections}
|
collectionsInputDisplayValue={selectedCollections}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
{isSimpleProduct ? (
|
{isSimpleProduct ? (
|
||||||
<ChannelsAvailabilityCard
|
<ChannelsAvailabilityCard
|
||||||
managePermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
managePermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
|
||||||
import Grid from "@dashboard/components/Grid";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import RichTextEditor from "@dashboard/components/RichTextEditor";
|
import RichTextEditor from "@dashboard/components/RichTextEditor";
|
||||||
import { RichTextEditorLoading } from "@dashboard/components/RichTextEditor/RichTextEditorLoading";
|
import { RichTextEditorLoading } from "@dashboard/components/RichTextEditor/RichTextEditorLoading";
|
||||||
import { ProductErrorFragment } from "@dashboard/graphql";
|
import { ProductErrorFragment } from "@dashboard/graphql";
|
||||||
|
@ -9,7 +6,7 @@ import { commonMessages } from "@dashboard/intl";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import { useRichTextContext } from "@dashboard/utils/richText/context";
|
import { useRichTextContext } from "@dashboard/utils/richText/context";
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
import { Card, CardContent, TextField } from "@material-ui/core";
|
import { Box, Input } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -27,36 +24,36 @@ interface ProductDetailsFormProps {
|
||||||
|
|
||||||
export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
||||||
data,
|
data,
|
||||||
disabled,
|
|
||||||
errors,
|
|
||||||
onChange,
|
onChange,
|
||||||
|
errors,
|
||||||
|
disabled,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const formErrors = getFormErrors(["name", "description", "rating"], errors);
|
||||||
const { editorRef, defaultValue, isReadyForMount, handleChange } =
|
const { editorRef, defaultValue, isReadyForMount, handleChange } =
|
||||||
useRichTextContext();
|
useRichTextContext();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["name", "description", "rating"], errors);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
{intl.formatMessage(commonMessages.generalInformations)}
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content display="grid" gap={5} paddingX={8}>
|
||||||
<TextField
|
<Input
|
||||||
error={!!formErrors.name}
|
|
||||||
helperText={getProductErrorMessage(formErrors.name, intl)}
|
|
||||||
fullWidth
|
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "6AMFki",
|
id: "6AMFki",
|
||||||
defaultMessage: "Name",
|
defaultMessage: "Name",
|
||||||
description: "product name",
|
description: "product name",
|
||||||
})}
|
})}
|
||||||
name="name"
|
size="small"
|
||||||
value={data.name}
|
value={data.name || ""}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
error={!!formErrors.name}
|
||||||
|
name="name"
|
||||||
|
disabled={disabled}
|
||||||
|
helperText={getProductErrorMessage(formErrors.name, intl)}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
|
||||||
{isReadyForMount ? (
|
{isReadyForMount ? (
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
|
@ -74,27 +71,24 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
||||||
name="description"
|
name="description"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FormSpacer />
|
<Box __width="25%">
|
||||||
<Hr />
|
<Input
|
||||||
<FormSpacer />
|
|
||||||
<Grid variant="uniform">
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
error={!!formErrors.rating}
|
|
||||||
helperText={getProductErrorMessage(formErrors.rating, intl)}
|
|
||||||
disabled={disabled}
|
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "L7N+0y",
|
id: "L7N+0y",
|
||||||
defaultMessage: "Product Rating",
|
defaultMessage: "Product Rating",
|
||||||
description: "product rating",
|
description: "product rating",
|
||||||
})}
|
})}
|
||||||
name="rating"
|
size="small"
|
||||||
value={data.rating || ""}
|
value={data.rating || ""}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
error={!!formErrors.rating}
|
||||||
|
name="rating"
|
||||||
|
type="number"
|
||||||
|
disabled={disabled}
|
||||||
|
helperText={getProductErrorMessage(formErrors.rating, intl)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Box>
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductDetailsForm;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./ProductDetailsForm";
|
|
||||||
export * from "./ProductDetailsForm";
|
export * from "./ProductDetailsForm";
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import BackButton from "@dashboard/components/BackButton";
|
|
||||||
import { Button } from "@dashboard/components/Button";
|
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
import FormSpacer from "@dashboard/components/FormSpacer";
|
||||||
import { ProductFragment } from "@dashboard/graphql";
|
import { ProductFragment } from "@dashboard/graphql";
|
||||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
||||||
|
import { buttonMessages } from "@dashboard/intl";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
import { Button, Input, Text } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -52,33 +50,37 @@ const ProductExternalMediaDialog: React.FC<ProductExternalMediaDialogProps> = ({
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open}>
|
<Dialog onClose={onClose} open={open}>
|
||||||
<DialogTitle disableTypography>
|
<DialogTitle disableTypography>
|
||||||
{intl.formatMessage(messages.buttonMessage)}
|
<Text variant="heading">
|
||||||
|
{intl.formatMessage(messages.buttonMessage)}
|
||||||
|
</Text>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<Form initial={initialValues} onSubmit={handleOnSubmit}>
|
<Form initial={initialValues} onSubmit={handleOnSubmit}>
|
||||||
{({ change, data, submit }) => (
|
{({ change, data, submit }) => (
|
||||||
<>
|
<>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography>
|
<Text variant="caption">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="zDvDnG"
|
id="zDvDnG"
|
||||||
defaultMessage="Media from the URL you supply will be shown in the media gallery. You will be able to define the order of the gallery."
|
defaultMessage="Media from the URL you supply will be shown in the media gallery. You will be able to define the order of the gallery."
|
||||||
description="modal header"
|
description="modal header"
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Text>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<TextField
|
<Input
|
||||||
label="URL"
|
label="URL"
|
||||||
value={data.mediaUrl}
|
value={data.mediaUrl}
|
||||||
name="mediaUrl"
|
name="mediaUrl"
|
||||||
type="url"
|
type="text"
|
||||||
onChange={change}
|
onChange={change}
|
||||||
autoFocus
|
autoFocus
|
||||||
fullWidth
|
size="medium"
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<BackButton onClick={onClose} />
|
<Button variant="secondary" onClick={onClose}>
|
||||||
|
<FormattedMessage {...buttonMessages.back} />
|
||||||
|
</Button>
|
||||||
<Button onClick={submit}>
|
<Button onClick={submit}>
|
||||||
{intl.formatMessage(messages.buttonMessage)}
|
{intl.formatMessage(messages.buttonMessage)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { getByName } from "@dashboard/components/Filter/utils";
|
||||||
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
import { ListPageLayout } from "@dashboard/components/Layouts";
|
import { ListPageLayout } from "@dashboard/components/Layouts";
|
||||||
import LimitReachedAlert from "@dashboard/components/LimitReachedAlert";
|
import LimitReachedAlert from "@dashboard/components/LimitReachedAlert";
|
||||||
import { TopNavMenu } from "@dashboard/components/TopNavMenu";
|
|
||||||
import { ProductListColumns } from "@dashboard/config";
|
import { ProductListColumns } from "@dashboard/config";
|
||||||
import {
|
import {
|
||||||
GridAttributesQuery,
|
GridAttributesQuery,
|
||||||
|
@ -188,7 +187,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<TopNavMenu
|
<TopNav.Menu
|
||||||
dataTestId="menu"
|
dataTestId="menu"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,121 +1,23 @@
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
|
||||||
import ImageUpload from "@dashboard/components/ImageUpload";
|
import ImageUpload from "@dashboard/components/ImageUpload";
|
||||||
import MediaTile from "@dashboard/components/MediaTile";
|
import MediaTile from "@dashboard/components/MediaTile";
|
||||||
|
import Skeleton from "@dashboard/components/Skeleton";
|
||||||
import { ProductMediaFragment, ProductMediaType } from "@dashboard/graphql";
|
import { ProductMediaFragment, ProductMediaType } from "@dashboard/graphql";
|
||||||
import { ProductMediaPopper } from "@dashboard/products/components/ProductMediaPopper/ProductMediaPopper";
|
|
||||||
import { ReorderAction } from "@dashboard/types";
|
import { ReorderAction } from "@dashboard/types";
|
||||||
import createMultiFileUploadHandler from "@dashboard/utils/handlers/multiFileUploadHandler";
|
import createMultiFileUploadHandler from "@dashboard/utils/handlers/multiFileUploadHandler";
|
||||||
import { Card, CardContent } from "@material-ui/core";
|
import {
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
Box,
|
||||||
import { vars } from "@saleor/macaw-ui/next";
|
Button,
|
||||||
import clsx from "clsx";
|
Dropdown,
|
||||||
|
List,
|
||||||
|
sprinkles,
|
||||||
|
Text,
|
||||||
|
} from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { SortableContainer, SortableElement } from "react-sortable-hoc";
|
import { SortableContainer, SortableElement } from "react-sortable-hoc";
|
||||||
|
|
||||||
const messages = defineMessages({
|
import { messages } from "./messages";
|
||||||
media: {
|
|
||||||
id: "/Mcvt4",
|
|
||||||
defaultMessage: "Media",
|
|
||||||
description: "section header",
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
id: "mGiA6q",
|
|
||||||
defaultMessage: "Upload",
|
|
||||||
description: "modal button upload",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
card: {
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
[theme.breakpoints.down("sm")]: {
|
|
||||||
marginTop: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fileField: {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
color: "rgba(255, 255, 255, 0.54)",
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain",
|
|
||||||
userSelect: "none",
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
"&:hover, &.dragged": {
|
|
||||||
"& $imageOverlay": {
|
|
||||||
display: "block",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
background: "#ffffff",
|
|
||||||
border: `1px solid ${vars.colors.border.neutralPlain}`,
|
|
||||||
borderRadius: theme.spacing(),
|
|
||||||
height: 140,
|
|
||||||
margin: "auto",
|
|
||||||
overflow: "hidden",
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
position: "relative",
|
|
||||||
width: 140,
|
|
||||||
},
|
|
||||||
imageGridContainer: {
|
|
||||||
position: "relative",
|
|
||||||
},
|
|
||||||
imageOverlay: {
|
|
||||||
background: "rgba(0, 0, 0, 0.6)",
|
|
||||||
cursor: "move",
|
|
||||||
display: "none",
|
|
||||||
height: 140,
|
|
||||||
left: 0,
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
width: 140,
|
|
||||||
},
|
|
||||||
imageOverlayToolbar: {
|
|
||||||
alignContent: "flex-end",
|
|
||||||
display: "flex",
|
|
||||||
position: "relative",
|
|
||||||
right: theme.spacing(-3),
|
|
||||||
top: theme.spacing(-2),
|
|
||||||
},
|
|
||||||
imageUpload: {
|
|
||||||
height: "100%",
|
|
||||||
left: 0,
|
|
||||||
outline: 0,
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
imageUploadActive: {
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
imageUploadIconActive: {
|
|
||||||
display: "block",
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
display: "grid",
|
|
||||||
gridColumnGap: theme.spacing(2),
|
|
||||||
gridRowGap: theme.spacing(2),
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
[theme.breakpoints.down("sm")]: {
|
|
||||||
gridTemplateColumns: "repeat(3, 1fr)",
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down("xs")]: {
|
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rootDragActive: {
|
|
||||||
opacity: 0.2,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ProductMedia" },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface SortableMediaProps {
|
interface SortableMediaProps {
|
||||||
media: {
|
media: {
|
||||||
|
@ -163,7 +65,6 @@ const MediaListContainer = SortableContainer<MediaListContainerProps>(
|
||||||
);
|
);
|
||||||
|
|
||||||
interface ProductMediaProps {
|
interface ProductMediaProps {
|
||||||
placeholderImage?: string;
|
|
||||||
media: ProductMediaFragment[];
|
media: ProductMediaFragment[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
getImageEditUrl: (id: string) => string;
|
getImageEditUrl: (id: string) => string;
|
||||||
|
@ -176,7 +77,6 @@ interface ProductMediaProps {
|
||||||
const ProductMedia: React.FC<ProductMediaProps> = props => {
|
const ProductMedia: React.FC<ProductMediaProps> = props => {
|
||||||
const {
|
const {
|
||||||
media,
|
media,
|
||||||
placeholderImage,
|
|
||||||
getImageEditUrl,
|
getImageEditUrl,
|
||||||
onImageDelete,
|
onImageDelete,
|
||||||
onImageReorder,
|
onImageReorder,
|
||||||
|
@ -184,14 +84,12 @@ const ProductMedia: React.FC<ProductMediaProps> = props => {
|
||||||
openMediaUrlModal,
|
openMediaUrlModal,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const imagesUpload = React.useRef<HTMLInputElement>(null);
|
const imagesUpload = React.useRef<HTMLInputElement>(null);
|
||||||
const anchor = React.useRef<HTMLButtonElement>();
|
const anchor = React.useRef<HTMLButtonElement>();
|
||||||
const [imagesToUpload, setImagesToUpload] = React.useState<
|
const [imagesToUpload, setImagesToUpload] = React.useState<
|
||||||
ProductMediaFragment[]
|
ProductMediaFragment[]
|
||||||
>([]);
|
>([]);
|
||||||
const [popperOpenStatus, setPopperOpenStatus] = React.useState(false);
|
|
||||||
|
|
||||||
const handleImageUpload = createMultiFileUploadHandler(onImageUpload, {
|
const handleImageUpload = createMultiFileUploadHandler(onImageUpload, {
|
||||||
onAfterUpload: () =>
|
onAfterUpload: () =>
|
||||||
|
@ -219,61 +117,92 @@ const ProductMedia: React.FC<ProductMediaProps> = props => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.card}>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage(messages.media)}
|
<Box display="flex" justifyContent="space-between" cursor="pointer">
|
||||||
toolbar={
|
<FormattedMessage {...messages.media} />
|
||||||
<>
|
<Dropdown>
|
||||||
<Button
|
<Dropdown.Trigger>
|
||||||
onClick={() => setPopperOpenStatus(true)}
|
<Button
|
||||||
variant="tertiary"
|
variant="secondary"
|
||||||
data-test-id="button-upload-image"
|
type="button"
|
||||||
ref={anchor}
|
data-test-id="button-upload-image"
|
||||||
>
|
ref={anchor}
|
||||||
{intl.formatMessage(messages.upload)}
|
>
|
||||||
</Button>
|
{intl.formatMessage(messages.upload)}
|
||||||
|
</Button>
|
||||||
<ProductMediaPopper
|
</Dropdown.Trigger>
|
||||||
anchorRef={anchor.current}
|
<Dropdown.Content align="end">
|
||||||
imagesUploadRef={imagesUpload.current}
|
<List
|
||||||
setPopperStatus={setPopperOpenStatus}
|
padding={5}
|
||||||
popperStatus={popperOpenStatus}
|
borderRadius={4}
|
||||||
openMediaUrlModal={openMediaUrlModal}
|
boxShadow="overlay"
|
||||||
/>
|
backgroundColor="surfaceNeutralPlain"
|
||||||
|
>
|
||||||
<input
|
<Dropdown.Item>
|
||||||
className={classes.fileField}
|
<List.Item
|
||||||
id="fileUpload"
|
borderRadius={4}
|
||||||
onChange={event => handleImageUpload(event.target.files)}
|
paddingX={4}
|
||||||
multiple
|
paddingY={5}
|
||||||
type="file"
|
onClick={() => imagesUpload.current.click()}
|
||||||
ref={imagesUpload}
|
data-test-id="upload-images"
|
||||||
accept="image/*"
|
>
|
||||||
/>
|
<Text>{intl.formatMessage(messages.uploadImages)}</Text>
|
||||||
</>
|
</List.Item>
|
||||||
}
|
</Dropdown.Item>
|
||||||
/>
|
<Dropdown.Item>
|
||||||
<div className={classes.imageGridContainer}>
|
<List.Item
|
||||||
{media === undefined ? (
|
borderRadius={4}
|
||||||
<CardContent>
|
paddingX={4}
|
||||||
<div className={classes.root}>
|
paddingY={5}
|
||||||
<div className={classes.imageContainer}>
|
onClick={openMediaUrlModal}
|
||||||
<img className={classes.image} src={placeholderImage} />
|
data-test-id="upload-media-url"
|
||||||
</div>
|
>
|
||||||
</div>
|
<Text>{intl.formatMessage(messages.uploadUrl)}</Text>
|
||||||
</CardContent>
|
</List.Item>
|
||||||
) : media.length > 0 ? (
|
</Dropdown.Item>
|
||||||
<>
|
</List>
|
||||||
<ImageUpload
|
</Dropdown.Content>
|
||||||
className={classes.imageUpload}
|
</Dropdown>
|
||||||
isActiveClassName={classes.imageUploadActive}
|
</Box>
|
||||||
disableClick={true}
|
</DashboardCard.Title>
|
||||||
hideUploadIcon={true}
|
<DashboardCard.Content>
|
||||||
iconContainerActiveClassName={classes.imageUploadIconActive}
|
<Box>
|
||||||
onImageUpload={handleImageUpload}
|
<Box
|
||||||
>
|
as="input"
|
||||||
{({ isDragActive }) => (
|
display="none"
|
||||||
<CardContent>
|
id="fileUpload"
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
handleImageUpload(event.target.files)
|
||||||
|
}
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
ref={imagesUpload}
|
||||||
|
accept="image/*"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box position="relative">
|
||||||
|
{media === undefined ? (
|
||||||
|
<Box padding={8}>
|
||||||
|
<Skeleton />
|
||||||
|
</Box>
|
||||||
|
) : media.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<ImageUpload
|
||||||
|
className={sprinkles({
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
})}
|
||||||
|
isActiveClassName={sprinkles({ zIndex: "1" })}
|
||||||
|
disableClick={true}
|
||||||
|
hideUploadIcon={true}
|
||||||
|
iconContainerActiveClassName={sprinkles({ display: "block" })}
|
||||||
|
onImageUpload={handleImageUpload}
|
||||||
|
>
|
||||||
|
{({ isDragActive }) => (
|
||||||
<MediaListContainer
|
<MediaListContainer
|
||||||
distance={20}
|
distance={20}
|
||||||
helperClass="dragged"
|
helperClass="dragged"
|
||||||
|
@ -281,22 +210,24 @@ const ProductMedia: React.FC<ProductMediaProps> = props => {
|
||||||
media={media}
|
media={media}
|
||||||
preview={imagesToUpload}
|
preview={imagesToUpload}
|
||||||
onSortEnd={onImageReorder}
|
onSortEnd={onImageReorder}
|
||||||
className={clsx({
|
className={sprinkles({
|
||||||
[classes.root]: true,
|
display: "grid",
|
||||||
[classes.rootDragActive]: isDragActive,
|
gap: 8,
|
||||||
|
gridTemplateColumns: { mobile: 2, tablet: 3, desktop: 4 },
|
||||||
|
opacity: isDragActive ? "0.2" : "1",
|
||||||
})}
|
})}
|
||||||
onDelete={onImageDelete}
|
onDelete={onImageDelete}
|
||||||
getEditHref={getImageEditUrl}
|
getEditHref={getImageEditUrl}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
)}
|
||||||
)}
|
</ImageUpload>
|
||||||
</ImageUpload>
|
</>
|
||||||
</>
|
) : (
|
||||||
) : (
|
<ImageUpload onImageUpload={handleImageUpload} />
|
||||||
<ImageUpload onImageUpload={handleImageUpload} />
|
)}
|
||||||
)}
|
</Box>
|
||||||
</div>
|
</DashboardCard.Content>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ProductMedia.displayName = "ProductMedia";
|
ProductMedia.displayName = "ProductMedia";
|
||||||
|
|
24
src/products/components/ProductMedia/messages.ts
Normal file
24
src/products/components/ProductMedia/messages.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
media: {
|
||||||
|
id: "/Mcvt4",
|
||||||
|
defaultMessage: "Media",
|
||||||
|
description: "section header",
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
id: "mGiA6q",
|
||||||
|
defaultMessage: "Upload",
|
||||||
|
description: "modal button upload",
|
||||||
|
},
|
||||||
|
uploadImages: {
|
||||||
|
id: "9CEu8k",
|
||||||
|
defaultMessage: "Upload Images",
|
||||||
|
description: "modal button images upload",
|
||||||
|
},
|
||||||
|
uploadUrl: {
|
||||||
|
id: "Q2UXlW",
|
||||||
|
defaultMessage: "Upload URL",
|
||||||
|
description: "modal button url upload",
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,78 +0,0 @@
|
||||||
import {
|
|
||||||
ClickAwayListener,
|
|
||||||
Grow,
|
|
||||||
MenuItem,
|
|
||||||
MenuList as Menu,
|
|
||||||
Paper,
|
|
||||||
Popper,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
interface ProductMediaPopperProps {
|
|
||||||
anchorRef: HTMLButtonElement;
|
|
||||||
imagesUploadRef: HTMLInputElement;
|
|
||||||
openMediaUrlModal: () => void;
|
|
||||||
popperStatus: boolean;
|
|
||||||
setPopperStatus: (popperStatus: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
uploadImages: {
|
|
||||||
id: "9CEu8k",
|
|
||||||
defaultMessage: "Upload Images",
|
|
||||||
description: "modal button images upload",
|
|
||||||
},
|
|
||||||
uploadUrl: {
|
|
||||||
id: "Q2UXlW",
|
|
||||||
defaultMessage: "Upload URL",
|
|
||||||
description: "modal button url upload",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ProductMediaPopper = ({
|
|
||||||
anchorRef,
|
|
||||||
imagesUploadRef,
|
|
||||||
setPopperStatus,
|
|
||||||
openMediaUrlModal,
|
|
||||||
popperStatus,
|
|
||||||
}: ProductMediaPopperProps) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popper
|
|
||||||
open={popperStatus}
|
|
||||||
anchorEl={anchorRef}
|
|
||||||
transition
|
|
||||||
placement="bottom-end"
|
|
||||||
>
|
|
||||||
{({ TransitionProps }) => (
|
|
||||||
<Grow {...TransitionProps}>
|
|
||||||
<Paper>
|
|
||||||
<ClickAwayListener
|
|
||||||
onClickAway={() => setPopperStatus(false)}
|
|
||||||
mouseEvent="onClick"
|
|
||||||
>
|
|
||||||
<Menu>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => imagesUploadRef.click()}
|
|
||||||
data-test-id="upload-images"
|
|
||||||
key="upload-images"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.uploadImages)}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
onClick={openMediaUrlModal}
|
|
||||||
data-test-id="upload-media-url"
|
|
||||||
key="upload-media-url"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.uploadUrl)}
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</ClickAwayListener>
|
|
||||||
</Paper>
|
|
||||||
</Grow>
|
|
||||||
)}
|
|
||||||
</Popper>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,7 +1,4 @@
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
|
||||||
import { FormSpacer } from "@dashboard/components/FormSpacer";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import Link from "@dashboard/components/Link";
|
import Link from "@dashboard/components/Link";
|
||||||
import MultiAutocompleteSelectField, {
|
import MultiAutocompleteSelectField, {
|
||||||
MultiAutocompleteChoiceType,
|
MultiAutocompleteChoiceType,
|
||||||
|
@ -16,12 +13,10 @@ import {
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
import { ChangeEvent } from "@dashboard/hooks/useForm";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { maybe } from "@dashboard/misc";
|
|
||||||
import { productTypeUrl } from "@dashboard/productTypes/urls";
|
import { productTypeUrl } from "@dashboard/productTypes/urls";
|
||||||
import { FetchMoreProps } from "@dashboard/types";
|
import { FetchMoreProps } from "@dashboard/types";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -31,22 +26,6 @@ interface ProductType {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
card: {
|
|
||||||
overflow: "visible",
|
|
||||||
},
|
|
||||||
cardSubtitle: {
|
|
||||||
fontSize: theme.typography.body1.fontSize,
|
|
||||||
marginBottom: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
marginBottom: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ProductOrganization" },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface ProductOrganizationProps {
|
interface ProductOrganizationProps {
|
||||||
canChangeType: boolean;
|
canChangeType: boolean;
|
||||||
categories?: SingleAutocompleteChoiceType[];
|
categories?: SingleAutocompleteChoiceType[];
|
||||||
|
@ -74,7 +53,9 @@ interface ProductOrganizationProps {
|
||||||
onProductTypeChange?: (event: ChangeEvent) => void;
|
onProductTypeChange?: (event: ChangeEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
export const ProductOrganization: React.FC<
|
||||||
|
ProductOrganizationProps
|
||||||
|
> = props => {
|
||||||
const {
|
const {
|
||||||
canChangeType,
|
canChangeType,
|
||||||
categories,
|
categories,
|
||||||
|
@ -98,7 +79,6 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
||||||
onProductTypeChange,
|
onProductTypeChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const formErrors = getFormErrors(
|
const formErrors = getFormErrors(
|
||||||
|
@ -111,15 +91,15 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={classes.card}>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "JjeZEG",
|
id: "JjeZEG",
|
||||||
defaultMessage: "Organize Product",
|
defaultMessage: "Organize Product",
|
||||||
description: "section header",
|
description: "section header",
|
||||||
})}
|
})}
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content gap={8} display="flex" flexDirection="column">
|
||||||
{canChangeType ? (
|
{canChangeType ? (
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
displayValue={productTypeInputDisplayValue}
|
displayValue={productTypeInputDisplayValue}
|
||||||
|
@ -139,36 +119,33 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
||||||
{...fetchMoreProductTypes}
|
{...fetchMoreProductTypes}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<Box display="flex" flexDirection="column" gap={6}>
|
||||||
<Typography className={classes.label} variant="caption">
|
<Box display="flex" flexDirection="column">
|
||||||
<FormattedMessage id="anK7jD" defaultMessage="Product Type" />
|
<Text variant="bodyEmp">
|
||||||
</Typography>
|
<FormattedMessage id="anK7jD" defaultMessage="Product Type" />
|
||||||
<Typography>
|
</Text>
|
||||||
<Link
|
<Text variant="caption">
|
||||||
href={productTypeUrl(productType?.id) ?? ""}
|
<Link
|
||||||
disabled={!productType?.id}
|
href={productTypeUrl(productType?.id) ?? ""}
|
||||||
>
|
disabled={!productType?.id}
|
||||||
{productType?.name ?? "..."}
|
>
|
||||||
</Link>
|
{productType?.name ?? "..."}
|
||||||
</Typography>
|
</Link>
|
||||||
<CardSpacer />
|
</Text>
|
||||||
<Typography className={classes.label} variant="caption">
|
</Box>
|
||||||
<FormattedMessage id="Be+J13" defaultMessage="Configurable" />
|
|
||||||
</Typography>
|
<Box display="flex" flexDirection="column">
|
||||||
<Typography>
|
<Text variant="bodyEmp">
|
||||||
{maybe(
|
<FormattedMessage id="Be+J13" defaultMessage="Configurable" />
|
||||||
() =>
|
</Text>
|
||||||
productType.hasVariants
|
<Text variant="caption">
|
||||||
? intl.formatMessage(commonMessages.yes)
|
{productType?.hasVariants
|
||||||
: intl.formatMessage(commonMessages.no),
|
? intl.formatMessage(commonMessages.yes)
|
||||||
"...",
|
: intl.formatMessage(commonMessages.no)}
|
||||||
)}
|
</Text>
|
||||||
</Typography>
|
</Box>
|
||||||
</>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<FormSpacer />
|
|
||||||
<Hr />
|
|
||||||
<FormSpacer />
|
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
displayValue={categoryInputDisplayValue}
|
displayValue={categoryInputDisplayValue}
|
||||||
error={!!(formErrors.category || noCategoryError)}
|
error={!!(formErrors.category || noCategoryError)}
|
||||||
|
@ -189,9 +166,6 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
||||||
data-test-id="category"
|
data-test-id="category"
|
||||||
{...fetchMoreCategories}
|
{...fetchMoreCategories}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
|
||||||
<Hr />
|
|
||||||
<FormSpacer />
|
|
||||||
<MultiAutocompleteSelectField
|
<MultiAutocompleteSelectField
|
||||||
displayValues={collectionsInputDisplayValue}
|
displayValues={collectionsInputDisplayValue}
|
||||||
error={!!formErrors.collections}
|
error={!!formErrors.collections}
|
||||||
|
@ -217,9 +191,7 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
|
||||||
testId="collection"
|
testId="collection"
|
||||||
{...fetchMoreCollections}
|
{...fetchMoreCollections}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ProductOrganization.displayName = "ProductOrganization";
|
|
||||||
export default ProductOrganization;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./ProductOrganization";
|
|
||||||
export * from "./ProductOrganization";
|
export * from "./ProductOrganization";
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import Grid from "@dashboard/components/Grid";
|
|
||||||
import { ProductErrorFragment } from "@dashboard/graphql";
|
import { ProductErrorFragment } from "@dashboard/graphql";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import createNonNegativeValueChangeHandler from "@dashboard/utils/handlers/nonNegativeValueChangeHandler";
|
import createNonNegativeValueChangeHandler from "@dashboard/utils/handlers/nonNegativeValueChangeHandler";
|
||||||
import {
|
import { InputAdornment, TextField } from "@material-ui/core";
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
InputAdornment,
|
|
||||||
TextField,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -22,7 +16,7 @@ interface ProductShippingProps {
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductShipping: React.FC<ProductShippingProps> = props => {
|
export const ProductShipping: React.FC<ProductShippingProps> = props => {
|
||||||
const { data, disabled, errors, weightUnit, onChange } = props;
|
const { data, disabled, errors, weightUnit, onChange } = props;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -31,43 +25,38 @@ const ProductShipping: React.FC<ProductShippingProps> = props => {
|
||||||
const handleChange = createNonNegativeValueChangeHandler(onChange);
|
const handleChange = createNonNegativeValueChangeHandler(onChange);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "3rIMq/",
|
id: "3rIMq/",
|
||||||
defaultMessage: "Shipping",
|
defaultMessage: "Shipping",
|
||||||
description: "product shipping",
|
description: "product shipping",
|
||||||
})}
|
})}
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
<Grid variant="uniform">
|
<TextField
|
||||||
<TextField
|
disabled={disabled}
|
||||||
disabled={disabled}
|
label={intl.formatMessage({
|
||||||
label={intl.formatMessage({
|
id: "SUbxSK",
|
||||||
id: "SUbxSK",
|
defaultMessage: "Weight",
|
||||||
defaultMessage: "Weight",
|
description: "product weight",
|
||||||
description: "product weight",
|
})}
|
||||||
})}
|
error={!!formErrors.weight}
|
||||||
error={!!formErrors.weight}
|
helperText={getProductErrorMessage(formErrors.weight, intl)}
|
||||||
helperText={getProductErrorMessage(formErrors.weight, intl)}
|
name="weight"
|
||||||
name="weight"
|
type="number"
|
||||||
value={data.weight}
|
value={data.weight}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">{weightUnit || ""}</InputAdornment>
|
||||||
{weightUnit || ""}
|
),
|
||||||
</InputAdornment>
|
inputProps: {
|
||||||
),
|
min: 0,
|
||||||
inputProps: {
|
},
|
||||||
min: 0,
|
}}
|
||||||
},
|
/>
|
||||||
}}
|
</DashboardCard.Content>
|
||||||
/>
|
</DashboardCard>
|
||||||
</Grid>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ProductShipping.displayName = "ProductShipping";
|
|
||||||
export default ProductShipping;
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./ProductShipping";
|
|
@ -2,11 +2,8 @@ import {
|
||||||
ChannelData,
|
ChannelData,
|
||||||
ChannelPriceAndPreorderArgs,
|
ChannelPriceAndPreorderArgs,
|
||||||
} from "@dashboard/channels/utils";
|
} from "@dashboard/channels/utils";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import ControlledCheckbox from "@dashboard/components/ControlledCheckbox";
|
|
||||||
import { DateTimeTimezoneField } from "@dashboard/components/DateTimeTimezoneField";
|
import { DateTimeTimezoneField } from "@dashboard/components/DateTimeTimezoneField";
|
||||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
|
||||||
import Hr from "@dashboard/components/Hr";
|
|
||||||
import Link from "@dashboard/components/Link";
|
import Link from "@dashboard/components/Link";
|
||||||
import PreviewPill from "@dashboard/components/PreviewPill";
|
import PreviewPill from "@dashboard/components/PreviewPill";
|
||||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||||
|
@ -17,22 +14,20 @@ import { sectionNames } from "@dashboard/intl";
|
||||||
import { renderCollection } from "@dashboard/misc";
|
import { renderCollection } from "@dashboard/misc";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import createNonNegativeValueChangeHandler from "@dashboard/utils/handlers/nonNegativeValueChangeHandler";
|
import createNonNegativeValueChangeHandler from "@dashboard/utils/handlers/nonNegativeValueChangeHandler";
|
||||||
|
import { Table, TableBody, TableCell, TableHead } from "@material-ui/core";
|
||||||
import {
|
import {
|
||||||
Card,
|
Box,
|
||||||
CardContent,
|
Button,
|
||||||
ClickAwayListener,
|
Checkbox,
|
||||||
Grow,
|
Dropdown,
|
||||||
MenuItem,
|
Input,
|
||||||
Paper,
|
List,
|
||||||
Popper,
|
PlusIcon,
|
||||||
Table,
|
sprinkles,
|
||||||
TableBody,
|
Text,
|
||||||
TableCell,
|
TrashBinIcon,
|
||||||
TableHead,
|
vars,
|
||||||
TextField,
|
} from "@saleor/macaw-ui/next";
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import { Button, DeleteIcon, IconButton, PlusIcon } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -40,7 +35,6 @@ import { ProductCreateData } from "../ProductCreatePage";
|
||||||
import { ProductVariantCreateData } from "../ProductVariantCreatePage/form";
|
import { ProductVariantCreateData } from "../ProductVariantCreatePage/form";
|
||||||
import { ProductVariantUpdateData } from "../ProductVariantPage/form";
|
import { ProductVariantUpdateData } from "../ProductVariantPage/form";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
import { useStyles } from "./styles";
|
|
||||||
|
|
||||||
export interface ProductStockFormsetData {
|
export interface ProductStockFormsetData {
|
||||||
quantityAllocated: number;
|
quantityAllocated: number;
|
||||||
|
@ -84,7 +78,7 @@ export interface ProductStocksProps {
|
||||||
onWarehouseConfigure: () => void;
|
onWarehouseConfigure: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductStocks: React.FC<ProductStocksProps> = ({
|
export const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
data,
|
data,
|
||||||
disabled,
|
disabled,
|
||||||
hasVariants,
|
hasVariants,
|
||||||
|
@ -102,11 +96,8 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
onWarehouseStockDelete,
|
onWarehouseStockDelete,
|
||||||
onWarehouseConfigure,
|
onWarehouseConfigure,
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles();
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
|
||||||
const [lastStockRowFocus, setLastStockRowFocus] = React.useState(false);
|
const [lastStockRowFocus, setLastStockRowFocus] = React.useState(false);
|
||||||
const [isExpanded, setExpansionState] = React.useState(false);
|
|
||||||
const unitsLeft = parseInt(data.globalThreshold, 10) - data.globalSoldUnits;
|
const unitsLeft = parseInt(data.globalThreshold, 10) - data.globalSoldUnits;
|
||||||
|
|
||||||
const warehousesToAssign =
|
const warehousesToAssign =
|
||||||
|
@ -141,131 +132,129 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle title={intl.formatMessage(messages.title)} />
|
<DashboardCard.Title>
|
||||||
<CardContent>
|
{intl.formatMessage(messages.title)}
|
||||||
<div className={classes.skuInputContainer}>
|
</DashboardCard.Title>
|
||||||
<TextField
|
<DashboardCard.Content>
|
||||||
|
<Box __width="50%">
|
||||||
|
<Input
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!formErrors.sku}
|
error={!!formErrors.sku}
|
||||||
fullWidth
|
|
||||||
helperText={getProductErrorMessage(formErrors.sku, intl)}
|
|
||||||
label={intl.formatMessage(messages.sku)}
|
label={intl.formatMessage(messages.sku)}
|
||||||
name="sku"
|
name="sku"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
value={data.sku}
|
value={data.sku}
|
||||||
data-test-id="sku"
|
data-test-id="sku"
|
||||||
|
size="small"
|
||||||
|
helperText={getProductErrorMessage(formErrors.sku, intl)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<ControlledCheckbox
|
|
||||||
checked={data.isPreorder}
|
|
||||||
name="isPreorder"
|
|
||||||
onChange={
|
|
||||||
onEndPreorderTrigger && data.isPreorder
|
|
||||||
? onEndPreorderTrigger
|
|
||||||
: onFormDataChange
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<FormattedMessage {...messages.variantInPreorder} />
|
|
||||||
<PreviewPill className={classes.preview} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!data.isPreorder && (
|
<Box paddingY={5} display="grid" gap={5}>
|
||||||
<>
|
<Checkbox
|
||||||
<FormSpacer />
|
checked={data.isPreorder}
|
||||||
<ControlledCheckbox
|
name="isPreorder"
|
||||||
|
onCheckedChange={value => {
|
||||||
|
if (onEndPreorderTrigger && data.isPreorder) {
|
||||||
|
onEndPreorderTrigger();
|
||||||
|
} else {
|
||||||
|
onFormDataChange({ target: { name: "isPreorder", value } });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Box display="flex" gap={3} paddingY={4}>
|
||||||
|
<Text>
|
||||||
|
<FormattedMessage {...messages.variantInPreorder} />
|
||||||
|
</Text>
|
||||||
|
<PreviewPill />
|
||||||
|
</Box>
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
{!data.isPreorder && (
|
||||||
|
<Checkbox
|
||||||
checked={data.trackInventory}
|
checked={data.trackInventory}
|
||||||
name="trackInventory"
|
name="trackInventory"
|
||||||
onChange={onFormDataChange}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={
|
onCheckedChange={value =>
|
||||||
<>
|
onFormDataChange({ target: { name: "trackInventory", value } })
|
||||||
<FormattedMessage {...messages.trackInventory} />
|
|
||||||
<Typography variant="caption">
|
|
||||||
<FormattedMessage {...messages.trackInventoryDescription} />
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
</>
|
<Box display="flex" flexDirection="column">
|
||||||
)}
|
<Text>
|
||||||
</CardContent>
|
<FormattedMessage {...messages.trackInventory} />
|
||||||
<Hr />
|
</Text>
|
||||||
{!data.isPreorder && (
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
<CardContent className={classes.quantityContainer}>
|
<FormattedMessage {...messages.trackInventoryDescription} />
|
||||||
<Typography>
|
</Text>
|
||||||
<div className={classes.quantityHeader}>
|
</Box>
|
||||||
<span>
|
</Checkbox>
|
||||||
<FormattedMessage {...messages.quantity} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Typography>
|
|
||||||
{!productVariantChannelListings?.length && (
|
|
||||||
<>
|
|
||||||
<FormSpacer />
|
|
||||||
<Typography variant="caption">
|
|
||||||
<FormattedMessage {...messages.noChannelWarehousesAllocation} />
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!warehouses?.length && (
|
{!data.isPreorder && (
|
||||||
<Typography
|
<Box display="grid" gap={5}>
|
||||||
color="textSecondary"
|
<Box display="flex" flexDirection="column">
|
||||||
className={classes.noWarehouseInfo}
|
<Text>
|
||||||
>
|
<FormattedMessage {...messages.quantity} />
|
||||||
{hasVariants ? (
|
</Text>
|
||||||
<>
|
{!productVariantChannelListings?.length && (
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
{...messages.configureWarehouseForVariant}
|
<FormattedMessage
|
||||||
values={{
|
{...messages.noChannelWarehousesAllocation}
|
||||||
a: chunks => (
|
/>
|
||||||
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
</Text>
|
||||||
),
|
)}
|
||||||
}}
|
</Box>
|
||||||
/>
|
{!warehouses?.length && (
|
||||||
</>
|
<Text color="textNeutralSubdued">
|
||||||
) : (
|
{hasVariants ? (
|
||||||
<>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
{...messages.configureWarehouseForVariant}
|
||||||
{...messages.configureWarehouseForProduct}
|
values={{
|
||||||
values={{
|
a: chunks => (
|
||||||
a: chunks => (
|
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
||||||
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
),
|
||||||
),
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
) : (
|
||||||
</>
|
<FormattedMessage
|
||||||
|
{...messages.configureWarehouseForProduct}
|
||||||
|
values={{
|
||||||
|
a: chunks => (
|
||||||
|
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</Box>
|
||||||
)}
|
</DashboardCard.Content>
|
||||||
{productVariantChannelListings?.length > 0 &&
|
{productVariantChannelListings?.length > 0 &&
|
||||||
warehouses?.length > 0 &&
|
warehouses?.length > 0 &&
|
||||||
!data.isPreorder && (
|
!data.isPreorder && (
|
||||||
<Table>
|
<Table>
|
||||||
<colgroup>
|
|
||||||
<col className={classes.colName} />
|
|
||||||
<col className={classes.colQuantity} />
|
|
||||||
<col className={classes.colQuantity} />
|
|
||||||
</colgroup>
|
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRowLink>
|
<TableRowLink>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<FormattedMessage {...messages.warehouseName} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.warehouseName} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage {...messages.allocated} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.allocated} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage {...messages.quantity} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.quantity} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colAction} />
|
<TableCell />
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
@ -277,195 +266,192 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowLink key={stock.id}>
|
<TableRowLink key={stock.id}>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
{stock.label}
|
<Text>{stock.label}</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell>
|
||||||
{stock.data?.quantityAllocated || 0}
|
<Text>{stock.data?.quantityAllocated || 0}</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell>
|
||||||
<TextField
|
<Input
|
||||||
data-test-id="stock-input"
|
data-test-id="stock-input"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
fullWidth
|
|
||||||
inputProps={{
|
|
||||||
className: classes.input,
|
|
||||||
min: 0,
|
|
||||||
type: "number",
|
|
||||||
}}
|
|
||||||
onChange={handleQuantityChange}
|
onChange={handleQuantityChange}
|
||||||
value={stock.value}
|
value={stock.value}
|
||||||
inputRef={input =>
|
size="small"
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
ref={input =>
|
||||||
stocks.length === index + 1 &&
|
stocks.length === index + 1 &&
|
||||||
handleStockInputFocus(input)
|
handleStockInputFocus(input)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colAction}>
|
<TableCell>
|
||||||
<IconButton
|
<Button
|
||||||
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
color="primary"
|
icon={<TrashBinIcon />}
|
||||||
onClick={() => onWarehouseStockDelete(stock.id)}
|
onClick={() => onWarehouseStockDelete(stock.id)}
|
||||||
>
|
/>
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{warehousesToAssign.length > 0 && (
|
{warehousesToAssign.length > 0 && (
|
||||||
<ClickAwayListener onClickAway={() => setExpansionState(false)}>
|
<Dropdown>
|
||||||
<TableRowLink
|
<Dropdown.Trigger>
|
||||||
className={classes.addRow}
|
<TableRowLink className={sprinkles({ cursor: "pointer" })}>
|
||||||
onClick={() => setExpansionState(!isExpanded)}
|
<TableCell
|
||||||
>
|
colSpan={3}
|
||||||
<TableCell colSpan={3} className={classes.actionableText}>
|
style={{ paddingLeft: vars.space[9] }}
|
||||||
<Typography variant="body2">
|
>
|
||||||
<FormattedMessage {...messages.assignWarehouse} />
|
<Text>
|
||||||
</Typography>
|
<FormattedMessage {...messages.assignWarehouse} />
|
||||||
</TableCell>
|
</Text>
|
||||||
<TableCell className={classes.colAction}>
|
</TableCell>
|
||||||
<div ref={anchor}>
|
<TableCell style={{ paddingRight: vars.space[9] }}>
|
||||||
<IconButton
|
<Button
|
||||||
data-test-id="add-warehouse"
|
type="button"
|
||||||
color="primary"
|
icon={<PlusIcon />}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={classes.actionableText}
|
/>
|
||||||
>
|
</TableCell>
|
||||||
<PlusIcon />
|
</TableRowLink>
|
||||||
</IconButton>
|
</Dropdown.Trigger>
|
||||||
<Popper
|
|
||||||
className={classes.popper}
|
<Dropdown.Content align="end">
|
||||||
open={isExpanded}
|
<Box>
|
||||||
anchorEl={anchor.current}
|
<List
|
||||||
transition
|
padding={5}
|
||||||
placement="top-end"
|
borderRadius={4}
|
||||||
>
|
boxShadow="overlay"
|
||||||
{({ TransitionProps }) => (
|
backgroundColor="surfaceNeutralPlain"
|
||||||
<Grow
|
>
|
||||||
{...TransitionProps}
|
{warehousesToAssign.map(warehouse => (
|
||||||
style={{
|
<Dropdown.Item key={warehouse.id}>
|
||||||
transformOrigin: "right top",
|
<List.Item
|
||||||
}}
|
paddingX={4}
|
||||||
|
paddingY={5}
|
||||||
|
borderRadius={4}
|
||||||
|
onClick={() =>
|
||||||
|
handleWarehouseStockAdd(warehouse.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Paper className={classes.paper} elevation={8}>
|
<Text>{warehouse.name}</Text>
|
||||||
{warehousesToAssign.map(warehouse => (
|
</List.Item>
|
||||||
<MenuItem
|
</Dropdown.Item>
|
||||||
className={classes.menuItem}
|
))}
|
||||||
onClick={() =>
|
</List>
|
||||||
handleWarehouseStockAdd(warehouse.id)
|
</Box>
|
||||||
}
|
</Dropdown.Content>
|
||||||
>
|
</Dropdown>
|
||||||
{warehouse.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Paper>
|
|
||||||
</Grow>
|
|
||||||
)}
|
|
||||||
</Popper>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRowLink>
|
|
||||||
</ClickAwayListener>
|
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
)}
|
||||||
{data.isPreorder && (
|
{data.isPreorder && (
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
<Typography variant="caption" className={classes.caption}>
|
<Box display="grid" gap={5}>
|
||||||
<FormattedMessage {...messages.preorderEndDateSetup} />
|
<Text variant="caption">
|
||||||
</Typography>
|
<FormattedMessage {...messages.preorderEndDateSetup} />
|
||||||
|
</Text>
|
||||||
{data.hasPreorderEndDate && (
|
{data.hasPreorderEndDate && (
|
||||||
<div className={classes.dateTimeInputs}>
|
<Box>
|
||||||
<DateTimeTimezoneField
|
<DateTimeTimezoneField
|
||||||
name={"preorderEndDateTime"}
|
name={"preorderEndDateTime"}
|
||||||
|
disabled={disabled}
|
||||||
|
futureDatesOnly
|
||||||
|
fullWidth={false}
|
||||||
|
error={localFormErrors.preorderEndDateTime}
|
||||||
|
value={data?.preorderEndDateTime}
|
||||||
|
onChange={event =>
|
||||||
|
onChangePreorderEndDate({
|
||||||
|
target: {
|
||||||
|
name: "preorderEndDateTime",
|
||||||
|
value: event,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Box __alignSelf="end">
|
||||||
|
<Button
|
||||||
|
name="hasPreorderEndDate"
|
||||||
|
variant="secondary"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
futureDatesOnly
|
type="button"
|
||||||
fullWidth={false}
|
onClick={() =>
|
||||||
error={localFormErrors.preorderEndDateTime}
|
onFormDataChange({
|
||||||
value={data?.preorderEndDateTime}
|
|
||||||
onChange={event =>
|
|
||||||
onChangePreorderEndDate({
|
|
||||||
target: {
|
target: {
|
||||||
name: "preorderEndDateTime",
|
name: "hasPreorderEndDate",
|
||||||
value: event,
|
value: !data.hasPreorderEndDate,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
name="hasPreorderEndDate"
|
|
||||||
variant="tertiary"
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={() =>
|
|
||||||
onFormDataChange({
|
|
||||||
target: {
|
|
||||||
name: "hasPreorderEndDate",
|
|
||||||
value: !data.hasPreorderEndDate,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{data.hasPreorderEndDate
|
|
||||||
? intl.formatMessage(messages.endDateCancel)
|
|
||||||
: intl.formatMessage(messages.endDateSetup)}
|
|
||||||
</Button>
|
|
||||||
<Typography variant="caption" className={classes.preorderLimitInfo}>
|
|
||||||
<FormattedMessage {...messages.preorderProductsAvailability} />
|
|
||||||
</Typography>
|
|
||||||
<div className={classes.thresholdRow}>
|
|
||||||
<TextField
|
|
||||||
inputProps={{
|
|
||||||
min: 0,
|
|
||||||
}}
|
|
||||||
disabled={disabled}
|
|
||||||
fullWidth
|
|
||||||
helperText={intl.formatMessage(
|
|
||||||
messages.preorderTresholdDescription,
|
|
||||||
)}
|
|
||||||
label={intl.formatMessage(messages.preorderTresholdLabel)}
|
|
||||||
name="globalThreshold"
|
|
||||||
onChange={onThresholdChange}
|
|
||||||
value={data.globalThreshold ?? ""}
|
|
||||||
className={classes.thresholdInput}
|
|
||||||
/>
|
|
||||||
{productVariantChannelListings?.length > 0 && (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
className={classes.preorderItemsLeftCount}
|
|
||||||
>
|
>
|
||||||
{data.globalThreshold
|
{data.hasPreorderEndDate
|
||||||
? intl.formatMessage(messages.preorderTresholdUnitsLeft, {
|
? intl.formatMessage(messages.endDateCancel)
|
||||||
unitsLeft,
|
: intl.formatMessage(messages.endDateSetup)}
|
||||||
})
|
</Button>
|
||||||
: intl.formatMessage(messages.preorderTresholdUnlimited)}
|
</Box>
|
||||||
</Typography>
|
</Box>
|
||||||
)}
|
|
||||||
</div>
|
<Box display="grid" gap={3} paddingTop={5}>
|
||||||
</CardContent>
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.preorderProductsAvailability} />
|
||||||
|
</Text>
|
||||||
|
<Box display="grid" gap={4}>
|
||||||
|
<Box __width="50%">
|
||||||
|
<Input
|
||||||
|
min={0}
|
||||||
|
type="text"
|
||||||
|
disabled={disabled}
|
||||||
|
label={intl.formatMessage(messages.preorderTresholdLabel)}
|
||||||
|
name="globalThreshold"
|
||||||
|
onChange={onThresholdChange}
|
||||||
|
value={data.globalThreshold ?? ""}
|
||||||
|
size="small"
|
||||||
|
helperText={intl.formatMessage(
|
||||||
|
messages.preorderTresholdDescription,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{productVariantChannelListings?.length > 0 && (
|
||||||
|
<Text variant="caption">
|
||||||
|
{data.globalThreshold
|
||||||
|
? intl.formatMessage(messages.preorderTresholdUnitsLeft, {
|
||||||
|
unitsLeft,
|
||||||
|
})
|
||||||
|
: intl.formatMessage(messages.preorderTresholdUnlimited)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</DashboardCard.Content>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{productVariantChannelListings?.length > 0 && data.isPreorder && (
|
{productVariantChannelListings?.length > 0 && data.isPreorder && (
|
||||||
<Table>
|
<Table>
|
||||||
<colgroup>
|
|
||||||
<col className={classes.colName} />
|
|
||||||
<col className={classes.colSoldUnits} />
|
|
||||||
<col className={classes.colThreshold} />
|
|
||||||
</colgroup>
|
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRowLink>
|
<TableRowLink>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<FormattedMessage {...sectionNames.channels} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...sectionNames.channels} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colSoldUnits}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage {...messages.soldUnits} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.soldUnits} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colThreshold}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage {...messages.channelTreshold} />
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
|
<FormattedMessage {...messages.channelTreshold} />
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
@ -477,25 +463,18 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowLink key={listing.id}>
|
<TableRowLink key={listing.id}>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
{listing.name}
|
<Text>{listing.name}</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell>
|
||||||
{listing?.unitsSold || 0}
|
<Text>{listing?.unitsSold || 0}</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colQuantity}>
|
<TableCell>
|
||||||
<TextField
|
<Input
|
||||||
|
min={0}
|
||||||
|
type="number"
|
||||||
name="channel-threshold"
|
name="channel-threshold"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
fullWidth
|
|
||||||
inputProps={{
|
|
||||||
className: classes.input,
|
|
||||||
min: 0,
|
|
||||||
type: "number",
|
|
||||||
}}
|
|
||||||
placeholder={intl.formatMessage(
|
|
||||||
messages.preorderTresholdUnlimited,
|
|
||||||
)}
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
onVariantChannelListingChange(listing.id, {
|
onVariantChannelListingChange(listing.id, {
|
||||||
costPrice: listing.costPrice,
|
costPrice: listing.costPrice,
|
||||||
|
@ -507,6 +486,10 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
value={listing?.preorderThreshold ?? ""}
|
value={listing?.preorderThreshold ?? ""}
|
||||||
|
size="small"
|
||||||
|
placeholder={intl.formatMessage(
|
||||||
|
messages.preorderTresholdUnlimited,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
|
@ -515,9 +498,6 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProductStocks.displayName = "ProductStocks";
|
|
||||||
export default ProductStocks;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export * from "./ProductStocks";
|
export * from "./ProductStocks";
|
||||||
export { default } from "./ProductStocks";
|
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
import { ICONBUTTON_SIZE, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import { vars } from "@saleor/macaw-ui/next";
|
|
||||||
|
|
||||||
export const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
colAction: {
|
|
||||||
padding: 0,
|
|
||||||
width: `calc(${ICONBUTTON_SIZE}px + ${theme.spacing(1)})`,
|
|
||||||
},
|
|
||||||
colName: {},
|
|
||||||
colQuantity: {
|
|
||||||
textAlign: "right",
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
colSoldUnits: {
|
|
||||||
textAlign: "right",
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
colThreshold: {
|
|
||||||
textAlign: "right",
|
|
||||||
width: 180,
|
|
||||||
},
|
|
||||||
editWarehouses: {
|
|
||||||
marginRight: theme.spacing(-1),
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
padding: theme.spacing(1.5),
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
|
||||||
menuItem: {
|
|
||||||
"&:not(:last-of-type)": {
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
noWarehouseInfo: {
|
|
||||||
marginTop: theme.spacing(),
|
|
||||||
},
|
|
||||||
paper: {
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
maxHeight: 400,
|
|
||||||
overflow: "scroll",
|
|
||||||
border: `1px solid ${vars.colors.border.neutralDefault}`,
|
|
||||||
borderRadius: vars.borderRadius[3],
|
|
||||||
},
|
|
||||||
popper: {
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
zIndex: 2,
|
|
||||||
},
|
|
||||||
quantityContainer: {
|
|
||||||
paddingTop: theme.spacing(),
|
|
||||||
},
|
|
||||||
quantityHeader: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
},
|
|
||||||
skuInputContainer: {
|
|
||||||
display: "grid",
|
|
||||||
gridColumnGap: theme.spacing(3),
|
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
|
||||||
},
|
|
||||||
dateTimeInputs: {
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
preorderInfo: {
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
display: "block",
|
|
||||||
},
|
|
||||||
caption: {
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
thresholdRow: {
|
|
||||||
display: "grid",
|
|
||||||
gridColumnGap: theme.spacing(3),
|
|
||||||
gridTemplateColumns: "3fr 1fr",
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
},
|
|
||||||
thresholdInput: {
|
|
||||||
maxWidth: 400,
|
|
||||||
},
|
|
||||||
addRow: {
|
|
||||||
"&:hover": {
|
|
||||||
cursor: "pointer",
|
|
||||||
"& $actionableText": {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actionableText: {},
|
|
||||||
preorderItemsLeftCount: {
|
|
||||||
fontSize: 14,
|
|
||||||
paddingTop: theme.spacing(2),
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
preorderLimitInfo: {
|
|
||||||
marginTop: theme.spacing(3),
|
|
||||||
},
|
|
||||||
preview: {
|
|
||||||
marginLeft: theme.spacing(1),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "ProductStocks",
|
|
||||||
},
|
|
||||||
);
|
|
|
@ -56,7 +56,6 @@ const props: ProductUpdatePageProps = {
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => undefined,
|
||||||
onVariantShow: () => undefined,
|
onVariantShow: () => undefined,
|
||||||
refetch: () => undefined,
|
refetch: () => undefined,
|
||||||
placeholderImage,
|
|
||||||
product,
|
product,
|
||||||
referencePages: [],
|
referencePages: [],
|
||||||
referenceProducts: [],
|
referenceProducts: [],
|
||||||
|
|
|
@ -66,7 +66,6 @@ const props: ProductUpdatePageProps = {
|
||||||
onMediaUrlUpload: () => undefined,
|
onMediaUrlUpload: () => undefined,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onVariantShow: () => undefined,
|
onVariantShow: () => undefined,
|
||||||
placeholderImage,
|
|
||||||
product,
|
product,
|
||||||
referencePages: [],
|
referencePages: [],
|
||||||
referenceProducts: [],
|
referenceProducts: [],
|
||||||
|
|
|
@ -10,15 +10,14 @@ import {
|
||||||
import { ChannelData } from "@dashboard/channels/utils";
|
import { ChannelData } from "@dashboard/channels/utils";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
||||||
import Attributes, { AttributeInput } from "@dashboard/components/Attributes";
|
import { AttributeInput, Attributes } from "@dashboard/components/Attributes";
|
||||||
import CardMenu from "@dashboard/components/CardMenu";
|
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import SeoForm from "@dashboard/components/SeoForm";
|
import { SeoForm } from "@dashboard/components/SeoForm";
|
||||||
import { Choice } from "@dashboard/components/SingleSelectField";
|
import { Choice } from "@dashboard/components/SingleSelectField";
|
||||||
import {
|
import {
|
||||||
ChannelFragment,
|
ChannelFragment,
|
||||||
|
@ -52,9 +51,9 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { getChoices } from "../../utils/data";
|
import { getChoices } from "../../utils/data";
|
||||||
import ProductDetailsForm from "../ProductDetailsForm";
|
import { ProductDetailsForm } from "../ProductDetailsForm";
|
||||||
import ProductMedia from "../ProductMedia";
|
import ProductMedia from "../ProductMedia";
|
||||||
import ProductOrganization from "../ProductOrganization";
|
import { ProductOrganization } from "../ProductOrganization";
|
||||||
import ProductTaxes from "../ProductTaxes";
|
import ProductTaxes from "../ProductTaxes";
|
||||||
import ProductVariants from "../ProductVariants";
|
import ProductVariants from "../ProductVariants";
|
||||||
import ProductUpdateForm from "./form";
|
import ProductUpdateForm from "./form";
|
||||||
|
@ -72,7 +71,6 @@ export interface ProductUpdatePageProps {
|
||||||
channelsErrors: ProductChannelListingErrorFragment[];
|
channelsErrors: ProductChannelListingErrorFragment[];
|
||||||
variantListErrors: ProductVariantListError[];
|
variantListErrors: ProductVariantListError[];
|
||||||
errors: UseProductUpdateHandlerError[];
|
errors: UseProductUpdateHandlerError[];
|
||||||
placeholderImage: string;
|
|
||||||
collections: RelayToFlat<SearchCollectionsQuery["search"]>;
|
collections: RelayToFlat<SearchCollectionsQuery["search"]>;
|
||||||
categories: RelayToFlat<SearchCategoriesQuery["search"]>;
|
categories: RelayToFlat<SearchCategoriesQuery["search"]>;
|
||||||
attributeValues: RelayToFlat<
|
attributeValues: RelayToFlat<
|
||||||
|
@ -139,7 +137,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
media,
|
media,
|
||||||
header,
|
header,
|
||||||
limits,
|
limits,
|
||||||
placeholderImage,
|
|
||||||
product,
|
product,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
variants,
|
variants,
|
||||||
|
@ -326,8 +323,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
return (
|
return (
|
||||||
<DetailPageLayout>
|
<DetailPageLayout>
|
||||||
<TopNav href={productListUrl()} title={header}>
|
<TopNav href={productListUrl()} title={header}>
|
||||||
<CardMenu
|
<TopNav.Menu
|
||||||
menuItems={[
|
items={[
|
||||||
...extensionMenuItems,
|
...extensionMenuItems,
|
||||||
{
|
{
|
||||||
label: intl.formatMessage(messages.openGraphiQL),
|
label: intl.formatMessage(messages.openGraphiQL),
|
||||||
|
@ -335,7 +332,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
testId: "graphiql-redirect",
|
testId: "graphiql-redirect",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
data-test-id="menu"
|
dataTestId="menu"
|
||||||
/>
|
/>
|
||||||
</TopNav>
|
</TopNav>
|
||||||
|
|
||||||
|
@ -349,7 +346,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<ProductMedia
|
<ProductMedia
|
||||||
media={media}
|
media={media}
|
||||||
placeholderImage={placeholderImage}
|
|
||||||
onImageDelete={onImageDelete}
|
onImageDelete={onImageDelete}
|
||||||
onImageReorder={onImageReorder}
|
onImageReorder={onImageReorder}
|
||||||
onImageUpload={onImageUpload}
|
onImageUpload={onImageUpload}
|
||||||
|
|
|
@ -4,14 +4,15 @@ import {
|
||||||
} from "@dashboard/attributes/utils/data";
|
} from "@dashboard/attributes/utils/data";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
||||||
import Attributes, {
|
import {
|
||||||
AttributeInput,
|
AttributeInput,
|
||||||
|
Attributes,
|
||||||
VariantAttributeScope,
|
VariantAttributeScope,
|
||||||
} from "@dashboard/components/Attributes";
|
} from "@dashboard/components/Attributes";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import Grid from "@dashboard/components/Grid";
|
import Grid from "@dashboard/components/Grid";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
ProductErrorWithAttributesFragment,
|
ProductErrorWithAttributesFragment,
|
||||||
|
@ -30,14 +31,14 @@ import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
import ProductShipping from "../ProductShipping/ProductShipping";
|
import { ProductShipping } from "../ProductShipping";
|
||||||
import ProductStocks from "../ProductStocks";
|
import { ProductStocks } from "../ProductStocks";
|
||||||
import { useManageChannels } from "../ProductVariantChannels/useManageChannels";
|
import { useManageChannels } from "../ProductVariantChannels/useManageChannels";
|
||||||
import { VariantChannelsDialog } from "../ProductVariantChannels/VariantChannelsDialog";
|
import { VariantChannelsDialog } from "../ProductVariantChannels/VariantChannelsDialog";
|
||||||
import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings";
|
import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings";
|
||||||
import ProductVariantName from "../ProductVariantName";
|
import ProductVariantName from "../ProductVariantName";
|
||||||
import ProductVariantNavigation from "../ProductVariantNavigation";
|
import ProductVariantNavigation from "../ProductVariantNavigation";
|
||||||
import ProductVariantPrice from "../ProductVariantPrice";
|
import { ProductVariantPrice } from "../ProductVariantPrice";
|
||||||
import ProductVariantCreateForm, {
|
import ProductVariantCreateForm, {
|
||||||
ProductVariantCreateData,
|
ProductVariantCreateData,
|
||||||
ProductVariantCreateHandlers,
|
ProductVariantCreateHandlers,
|
||||||
|
|
|
@ -5,15 +5,16 @@ import {
|
||||||
import { ChannelPriceData } from "@dashboard/channels/utils";
|
import { ChannelPriceData } from "@dashboard/channels/utils";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
import AssignAttributeValueDialog from "@dashboard/components/AssignAttributeValueDialog";
|
||||||
import Attributes, {
|
import {
|
||||||
AttributeInput,
|
AttributeInput,
|
||||||
|
Attributes,
|
||||||
VariantAttributeScope,
|
VariantAttributeScope,
|
||||||
} from "@dashboard/components/Attributes";
|
} from "@dashboard/components/Attributes";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import Grid from "@dashboard/components/Grid";
|
import Grid from "@dashboard/components/Grid";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import { MetadataFormData } from "@dashboard/components/Metadata";
|
import { MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
ProductChannelListingErrorFragment,
|
ProductChannelListingErrorFragment,
|
||||||
|
@ -33,8 +34,8 @@ import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
import ProductShipping from "../ProductShipping/ProductShipping";
|
import { ProductShipping } from "../ProductShipping";
|
||||||
import ProductStocks, { ProductStockInput } from "../ProductStocks";
|
import { ProductStockInput, ProductStocks } from "../ProductStocks";
|
||||||
import { useManageChannels } from "../ProductVariantChannels/useManageChannels";
|
import { useManageChannels } from "../ProductVariantChannels/useManageChannels";
|
||||||
import { VariantChannelsDialog } from "../ProductVariantChannels/VariantChannelsDialog";
|
import { VariantChannelsDialog } from "../ProductVariantChannels/VariantChannelsDialog";
|
||||||
import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings";
|
import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings";
|
||||||
|
@ -43,7 +44,7 @@ import ProductVariantMediaSelectDialog from "../ProductVariantImageSelectDialog"
|
||||||
import ProductVariantMedia from "../ProductVariantMedia";
|
import ProductVariantMedia from "../ProductVariantMedia";
|
||||||
import ProductVariantName from "../ProductVariantName";
|
import ProductVariantName from "../ProductVariantName";
|
||||||
import ProductVariantNavigation from "../ProductVariantNavigation";
|
import ProductVariantNavigation from "../ProductVariantNavigation";
|
||||||
import ProductVariantPrice from "../ProductVariantPrice";
|
import { ProductVariantPrice } from "../ProductVariantPrice";
|
||||||
import ProductVariantSetDefault from "../ProductVariantSetDefault";
|
import ProductVariantSetDefault from "../ProductVariantSetDefault";
|
||||||
import ProductVariantUpdateForm, {
|
import ProductVariantUpdateForm, {
|
||||||
ProductVariantUpdateData,
|
ProductVariantUpdateData,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
ChannelPriceAndPreorderArgs,
|
ChannelPriceAndPreorderArgs,
|
||||||
ChannelPriceArgs,
|
ChannelPriceArgs,
|
||||||
} from "@dashboard/channels/utils";
|
} from "@dashboard/channels/utils";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import { DashboardCard } from "@dashboard/components/Card";
|
||||||
import PriceField from "@dashboard/components/PriceField";
|
import PriceField from "@dashboard/components/PriceField";
|
||||||
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
import Skeleton from "@dashboard/components/Skeleton";
|
||||||
|
@ -15,47 +15,11 @@ import {
|
||||||
getFormChannelErrors,
|
getFormChannelErrors,
|
||||||
} from "@dashboard/utils/errors";
|
} from "@dashboard/utils/errors";
|
||||||
import getProductErrorMessage from "@dashboard/utils/errors/product";
|
import getProductErrorMessage from "@dashboard/utils/errors/product";
|
||||||
import {
|
import { TableBody, TableCell, TableHead } from "@material-ui/core";
|
||||||
Card,
|
import { Text, vars } from "@saleor/macaw-ui/next";
|
||||||
CardContent,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, MessageDescriptor, useIntl } from "react-intl";
|
import { FormattedMessage, MessageDescriptor, useIntl } from "react-intl";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
() => ({
|
|
||||||
colPrice: {
|
|
||||||
textAlign: "right",
|
|
||||||
verticalAlign: "middle",
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
colType: {
|
|
||||||
fontSize: 14,
|
|
||||||
textAlign: "right",
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
textAlign: "left",
|
|
||||||
},
|
|
||||||
pricingContent: {
|
|
||||||
"&:last-child": {
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0,
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
tableLayout: "fixed",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "ProductVariantPrice" },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface ProductVariantPriceProps {
|
interface ProductVariantPriceProps {
|
||||||
ProductVariantChannelListings?: ChannelData[];
|
ProductVariantChannelListings?: ChannelData[];
|
||||||
errors?: ProductChannelListingErrorFragment[];
|
errors?: ProductChannelListingErrorFragment[];
|
||||||
|
@ -70,7 +34,9 @@ interface ProductVariantPriceProps {
|
||||||
|
|
||||||
const numberOfColumns = 2;
|
const numberOfColumns = 2;
|
||||||
|
|
||||||
const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
export const ProductVariantPrice: React.FC<
|
||||||
|
ProductVariantPriceProps
|
||||||
|
> = props => {
|
||||||
const {
|
const {
|
||||||
disabled = false,
|
disabled = false,
|
||||||
errors = [],
|
errors = [],
|
||||||
|
@ -79,22 +45,21 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
onChange,
|
onChange,
|
||||||
disabledMessage,
|
disabledMessage,
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const formErrors = getFormChannelErrors(["price", "costPrice"], errors);
|
const formErrors = getFormChannelErrors(["price", "costPrice"], errors);
|
||||||
|
|
||||||
if (disabled || !ProductVariantChannelListings.length) {
|
if (disabled || !ProductVariantChannelListings.length) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "Xm9qOu",
|
id: "Xm9qOu",
|
||||||
defaultMessage: "Pricing",
|
defaultMessage: "Pricing",
|
||||||
description: "product pricing, section header",
|
description: "product pricing, section header",
|
||||||
})}
|
})}
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
<Typography variant="caption">
|
<Text variant="caption">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
disabledMessage || {
|
disabledMessage || {
|
||||||
id: "e48Igh",
|
id: "e48Igh",
|
||||||
|
@ -103,54 +68,60 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
description: "variant pricing section subtitle",
|
description: "variant pricing section subtitle",
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Text>
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<DashboardCard>
|
||||||
<CardTitle
|
<DashboardCard.Title>
|
||||||
title={intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "Xm9qOu",
|
id: "Xm9qOu",
|
||||||
defaultMessage: "Pricing",
|
defaultMessage: "Pricing",
|
||||||
description: "product pricing, section header",
|
description: "product pricing, section header",
|
||||||
})}
|
})}
|
||||||
/>
|
</DashboardCard.Title>
|
||||||
<CardContent>
|
<DashboardCard.Content>
|
||||||
<Typography variant="body2">
|
<Text variant="body">
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "VvA7ai",
|
id: "VvA7ai",
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency",
|
"Channels that don’t have assigned prices will use their parent channel to define the price. Price will be converted to channel’s currency",
|
||||||
description: "info text",
|
description: "info text",
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Text>
|
||||||
</CardContent>
|
</DashboardCard.Content>
|
||||||
<ResponsiveTable className={classes.table}>
|
<ResponsiveTable>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRowLink>
|
<TableRowLink>
|
||||||
<TableCell>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
id="c8UT0c"
|
<FormattedMessage
|
||||||
defaultMessage="Channel Name"
|
id="c8UT0c"
|
||||||
description="tabel column header"
|
defaultMessage="Channel Name"
|
||||||
/>
|
description="tabel column header"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colType}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
id="JFtFgc"
|
<FormattedMessage
|
||||||
defaultMessage="Selling Price"
|
id="JFtFgc"
|
||||||
description="tabel column header"
|
defaultMessage="Selling Price"
|
||||||
/>
|
description="tabel column header"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colType}>
|
<TableCell style={{ width: 200, verticalAlign: "middle" }}>
|
||||||
<FormattedMessage
|
<Text variant="caption" color="textNeutralSubdued">
|
||||||
id="2zCmiR"
|
<FormattedMessage
|
||||||
defaultMessage="Cost price"
|
id="2zCmiR"
|
||||||
description="tabel column header"
|
defaultMessage="Cost price"
|
||||||
/>
|
description="tabel column header"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
@ -169,11 +140,12 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowLink key={listing?.id || `skeleton-${index}`}>
|
<TableRowLink key={listing?.id || `skeleton-${index}`}>
|
||||||
<TableCell>{listing?.name || <Skeleton />}</TableCell>
|
<TableCell style={{ paddingLeft: vars.space[9] }}>
|
||||||
<TableCell className={classes.colPrice}>
|
<Text>{listing?.name || <Skeleton />}</Text>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
{listing ? (
|
{listing ? (
|
||||||
<PriceField
|
<PriceField
|
||||||
className={classes.input}
|
|
||||||
error={!!priceError}
|
error={!!priceError}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "b1zuN9",
|
id: "b1zuN9",
|
||||||
|
@ -199,10 +171,9 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colPrice}>
|
<TableCell>
|
||||||
{listing ? (
|
{listing ? (
|
||||||
<PriceField
|
<PriceField
|
||||||
className={classes.input}
|
|
||||||
error={!!costPriceError}
|
error={!!costPriceError}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "KQSONM",
|
id: "KQSONM",
|
||||||
|
@ -236,18 +207,18 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
() => (
|
() => (
|
||||||
<TableRowLink>
|
<TableRowLink>
|
||||||
<TableCell colSpan={numberOfColumns}>
|
<TableCell colSpan={numberOfColumns}>
|
||||||
<FormattedMessage
|
<Text>
|
||||||
id="/glQgs"
|
<FormattedMessage
|
||||||
defaultMessage="No channels found"
|
id="/glQgs"
|
||||||
/>
|
defaultMessage="No channels found"
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRowLink>
|
</TableRowLink>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</ResponsiveTable>
|
</ResponsiveTable>
|
||||||
</Card>
|
</DashboardCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ProductVariantPrice.displayName = "ProductVariantPrice";
|
|
||||||
export default ProductVariantPrice;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./ProductVariantPrice";
|
|
||||||
export * from "./ProductVariantPrice";
|
export * from "./ProductVariantPrice";
|
||||||
|
|
|
@ -250,7 +250,7 @@ export const getPreorderEndHourFormData = (endDate?: string) =>
|
||||||
endDate ? moment(endDate).format("HH:mm") : "";
|
endDate ? moment(endDate).format("HH:mm") : "";
|
||||||
|
|
||||||
export const getSelectedMedia = <
|
export const getSelectedMedia = <
|
||||||
T extends Pick<ProductMediaFragment, "id" | "sortOrder">
|
T extends Pick<ProductMediaFragment, "id" | "sortOrder">,
|
||||||
>(
|
>(
|
||||||
media: T[] = [],
|
media: T[] = [],
|
||||||
selectedMediaIds: string[],
|
selectedMediaIds: string[],
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import placeholderImg from "@assets/images/placeholder255x255.png";
|
|
||||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
import { AttributeInput } from "@dashboard/components/Attributes";
|
import { AttributeInput } from "@dashboard/components/Attributes";
|
||||||
|
@ -114,10 +113,8 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const [reorderProductImages, reorderProductImagesOpts] =
|
||||||
reorderProductImages,
|
useProductMediaReorderMutation({});
|
||||||
reorderProductImagesOpts,
|
|
||||||
] = useProductMediaReorderMutation({});
|
|
||||||
|
|
||||||
const [deleteProduct, deleteProductOpts] = useProductDeleteMutation({
|
const [deleteProduct, deleteProductOpts] = useProductDeleteMutation({
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
|
@ -132,25 +129,23 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const [createProductImage, createProductImageOpts] =
|
||||||
createProductImage,
|
useProductMediaCreateMutation({
|
||||||
createProductImageOpts,
|
onCompleted: data => {
|
||||||
] = useProductMediaCreateMutation({
|
const imageError = data.productMediaCreate.errors.find(
|
||||||
onCompleted: data => {
|
error =>
|
||||||
const imageError = data.productMediaCreate.errors.find(
|
error.field ===
|
||||||
error =>
|
("image" as keyof ProductMediaCreateMutationVariables),
|
||||||
error.field ===
|
);
|
||||||
("image" as keyof ProductMediaCreateMutationVariables),
|
if (imageError) {
|
||||||
);
|
notify({
|
||||||
if (imageError) {
|
status: "error",
|
||||||
notify({
|
title: intl.formatMessage(errorMessages.imgageUploadErrorTitle),
|
||||||
status: "error",
|
text: intl.formatMessage(errorMessages.imageUploadErrorText),
|
||||||
title: intl.formatMessage(errorMessages.imgageUploadErrorTitle),
|
});
|
||||||
text: intl.formatMessage(errorMessages.imageUploadErrorText),
|
}
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [deleteProductImage] = useProductMediaDeleteMutation({
|
const [deleteProductImage] = useProductMediaDeleteMutation({
|
||||||
onCompleted: () =>
|
onCompleted: () =>
|
||||||
|
@ -175,28 +170,26 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const [createProductMedia, createProductMediaOpts] =
|
||||||
createProductMedia,
|
useProductMediaCreateMutation({
|
||||||
createProductMediaOpts,
|
onCompleted: data => {
|
||||||
] = useProductMediaCreateMutation({
|
const errors = data.productMediaCreate.errors;
|
||||||
onCompleted: data => {
|
|
||||||
const errors = data.productMediaCreate.errors;
|
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
errors.map(error =>
|
errors.map(error =>
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: getProductErrorMessage(error, intl),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "success",
|
||||||
text: getProductErrorMessage(error, intl),
|
text: intl.formatMessage(commonMessages.savedChanges),
|
||||||
}),
|
});
|
||||||
);
|
}
|
||||||
} else {
|
},
|
||||||
notify({
|
});
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleMediaUrlUpload = (mediaUrl: string) => {
|
const handleMediaUrlUpload = (mediaUrl: string) => {
|
||||||
const variables = {
|
const variables = {
|
||||||
|
@ -282,8 +275,9 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchMoreAttributeValues = {
|
const fetchMoreAttributeValues = {
|
||||||
hasMore: !!searchAttributeValuesOpts.data?.attribute?.choices?.pageInfo
|
hasMore:
|
||||||
?.hasNextPage,
|
!!searchAttributeValuesOpts.data?.attribute?.choices?.pageInfo
|
||||||
|
?.hasNextPage,
|
||||||
loading: !!searchAttributeValuesOpts.loading,
|
loading: !!searchAttributeValuesOpts.loading,
|
||||||
onFetchMore: loadMoreAttributeValues,
|
onFetchMore: loadMoreAttributeValues,
|
||||||
};
|
};
|
||||||
|
@ -316,7 +310,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
saveButtonBarState={formTransitionState}
|
saveButtonBarState={formTransitionState}
|
||||||
media={data?.product?.media}
|
media={data?.product?.media}
|
||||||
header={product?.name}
|
header={product?.name}
|
||||||
placeholderImage={placeholderImg}
|
|
||||||
product={product}
|
product={product}
|
||||||
warehouses={warehouses}
|
warehouses={warehouses}
|
||||||
taxClasses={taxClasses ?? []}
|
taxClasses={taxClasses ?? []}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import CountryList from "@dashboard/components/CountryList";
|
import CountryList from "@dashboard/components/CountryList";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import { Metadata } from "@dashboard/components/Metadata/Metadata";
|
||||||
import { MultiAutocompleteChoiceType } from "@dashboard/components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "@dashboard/components/MultiAutocompleteSelectField";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import { SingleAutocompleteChoiceType } from "@dashboard/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@dashboard/components/SingleAutocompleteSelectField";
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue