From e4dfc30a2083f120e4d3cf9d82a7dc58f4ef26cb Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 5 Oct 2017 14:56:00 -0700 Subject: [PATCH 1/9] Add ability to enable and disable a marketplace shop --- .../marketplace/client/components/index.js | 2 + .../components/marketplaceShopTableCell.js | 77 +++++++++++++++++++ .../client/components/marketplaceShops.js | 52 +++++++++++++ .../marketplace/client/containers/index.js | 1 + .../client/containers/marketplaceShops.js | 22 ++++++ .../included/marketplace/client/index.js | 3 + .../included/marketplace/lib/constants.js | 4 + .../plugins/included/marketplace/register.js | 17 ++++ .../included/marketplace/server/methods.js | 66 ++++++++++------ lib/collections/schemas/shops.js | 5 ++ server/publications/collections/shops.js | 20 +++-- 11 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 imports/plugins/included/marketplace/client/components/index.js create mode 100644 imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js create mode 100644 imports/plugins/included/marketplace/client/components/marketplaceShops.js create mode 100644 imports/plugins/included/marketplace/client/containers/index.js create mode 100644 imports/plugins/included/marketplace/client/containers/marketplaceShops.js create mode 100644 imports/plugins/included/marketplace/lib/constants.js diff --git a/imports/plugins/included/marketplace/client/components/index.js b/imports/plugins/included/marketplace/client/components/index.js new file mode 100644 index 00000000000..a5729666ecd --- /dev/null +++ b/imports/plugins/included/marketplace/client/components/index.js @@ -0,0 +1,2 @@ +export { default as MarketplaceShops } from "./marketplaceShops"; +export { default as MarketplaceShopTableCell } from "./marketplaceShopTableCell"; diff --git a/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js b/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js new file mode 100644 index 00000000000..44ab6e8631c --- /dev/null +++ b/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js @@ -0,0 +1,77 @@ +import { Meteor } from "meteor/meteor"; +import _ from "lodash"; +import React, { Component } from "react"; +import classnames from "classnames"; +import PropTypes from "prop-types"; +import { Components, registerComponent } from "@reactioncommerce/reaction-components"; +import { SHOP_WORKFLOW_STATUS_ACTIVE, SHOP_WORKFLOW_STATUS_DISABLED } from "../../lib/constants"; + +class MarketplaceShopTableCell extends Component { + static propTypes = { + data: PropTypes.object, + field: PropTypes.string, + onWorkflowChange: PropTypes.func + } + + get shop() { + return this.props.data.original; + } + + get status() { + if (this.shop.workflow) { + return this.shop.workflow.status; + } + + return SHOP_WORKFLOW_STATUS_ACTIVE; + } + + handleWorkflowChange = (event, value) => { + if (this.props.onWorkflowChange) { + // Get the shop id from the original document + const { _id } = this.shop; + + this.props.onWorkflowChange(_id, value); + } + } + + render() { + const { field, data } = this.props; + + if (field === "emails" && Array.isArray(data.value) && data.value.length) { + return ( +
+ {data.value[0].address} +
+ ); + } + + if (field === "workflow") { + return ( + + + + + ); + } + + + return ( +
+ {data.value} +
+ ); + } +} + +registerComponent("MarketplaceShopTableCell", MarketplaceShopTableCell); + +export default MarketplaceShopTableCell; diff --git a/imports/plugins/included/marketplace/client/components/marketplaceShops.js b/imports/plugins/included/marketplace/client/components/marketplaceShops.js new file mode 100644 index 00000000000..60efcacb6d9 --- /dev/null +++ b/imports/plugins/included/marketplace/client/components/marketplaceShops.js @@ -0,0 +1,52 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Components } from "@reactioncommerce/reaction-components"; + +class MarketplaceShops extends Component { + static propTypes = { + onWorkflowChange: PropTypes.func, + shops: PropTypes.arrayOf(PropTypes.object) + } + + renderShopsTable() { + const fields = ["name", "emails", "workflow"]; + + const columnMetadata = fields.map((field) => { + return { + Header: , + accessor: field, + Cell: (data) => ( // eslint-disable-line + + ) + }; + }); + + return ( + + ); + } + + render() { + return ( +
+
+ {/* */} + {this.renderShopsTable()} +
+
+ ); + } +} + +export default MarketplaceShops; diff --git a/imports/plugins/included/marketplace/client/containers/index.js b/imports/plugins/included/marketplace/client/containers/index.js new file mode 100644 index 00000000000..36f908fcf20 --- /dev/null +++ b/imports/plugins/included/marketplace/client/containers/index.js @@ -0,0 +1 @@ +export { default as MarketplaceShopsContainer } from "./marketplaceShops"; diff --git a/imports/plugins/included/marketplace/client/containers/marketplaceShops.js b/imports/plugins/included/marketplace/client/containers/marketplaceShops.js new file mode 100644 index 00000000000..e0251438587 --- /dev/null +++ b/imports/plugins/included/marketplace/client/containers/marketplaceShops.js @@ -0,0 +1,22 @@ +import { composeWithTracker, registerComponent } from "@reactioncommerce/reaction-components"; +import { Meteor } from "meteor/meteor"; +import { Shops } from "/lib/collections"; +import MarketplaceShops from "../components/marketplaceShops"; + + +const onWorkflowChange = (shopId, value) => { + Meteor.call("marketplace/updateShopWorkflow", shopId, value); +}; + +const composer = (props, onData) => { + const shops = Shops.find({}).fetch(); + + onData(null, { + shops, + onWorkflowChange + }); +}; + +registerComponent("MarketplaceShops", MarketplaceShops, composeWithTracker(composer)); + +export default composeWithTracker(composer)(MarketplaceShops); diff --git a/imports/plugins/included/marketplace/client/index.js b/imports/plugins/included/marketplace/client/index.js index 3bc69da6614..a855d2a83b1 100644 --- a/imports/plugins/included/marketplace/client/index.js +++ b/imports/plugins/included/marketplace/client/index.js @@ -17,3 +17,6 @@ import "./templates/stripeConnectSignupButton/stripeConnectSignupButton.html"; import "./templates/stripeConnectSignupButton/stripeConnectSignupButton.js"; export { default as InviteOwner } from "./components/inviteOwner"; + +export * from "./components"; +export * from "./containers"; diff --git a/imports/plugins/included/marketplace/lib/constants.js b/imports/plugins/included/marketplace/lib/constants.js new file mode 100644 index 00000000000..0ac48f73b51 --- /dev/null +++ b/imports/plugins/included/marketplace/lib/constants.js @@ -0,0 +1,4 @@ + +export const SHOP_WORKFLOW_STATUS_ACTIVE = "active"; +export const SHOP_WORKFLOW_STATUS_DISABLED = "disabled"; +export const SHOP_WORKFLOW_STATUS_ARCHIVED = "archived"; diff --git a/imports/plugins/included/marketplace/register.js b/imports/plugins/included/marketplace/register.js index a38a4ce489a..61d0aa64f56 100644 --- a/imports/plugins/included/marketplace/register.js +++ b/imports/plugins/included/marketplace/register.js @@ -67,6 +67,23 @@ Reaction.registerPackage({ container: "dashboard", template: "marketplaceShopSettings", showForShopTypes: ["primary"] + }, { + route: "shop/settings/shops", + template: "MarketplaceShops", + name: "marketplaceShops", + label: "Marketplace Shops", + icon: "fa fa-globe", + provides: ["settings"], + container: "dashboard", + meta: { + actionView: { + dashboardSize: "lg" + } + }, + permissions: [{ + label: "Marketplace Shops", + permission: "marketplaceShops" + }] }, { // does this work? // override default shop settings diff --git a/imports/plugins/included/marketplace/server/methods.js b/imports/plugins/included/marketplace/server/methods.js index ff30a4d3e2b..ecf895784d6 100644 --- a/imports/plugins/included/marketplace/server/methods.js +++ b/imports/plugins/included/marketplace/server/methods.js @@ -1,23 +1,43 @@ -// import { Meteor } from "meteor/meteor"; -// // import { check } from "meteor/check"; -// // import { Reaction } from "/lib/api"; -// // import { Shops } from "/lib/collections"; -// -// -// Meteor.methods({ -// // "marketplace/updateShopDetails": function (doc, _id) { -// // check(_id, String); -// // check(doc, Object); -// // -// // if (!Reaction.hasPermission("admin", this.userId, Reaction.getSellerShopId(this.userId))) { -// // return; -// // } -// // -// // Shops.update(_id, doc, function (error) { -// // if (error) { -// // throw new Meteor.Error(500, error.message); -// // } -// // }); -// // } -// // -// }); +import { Meteor } from "meteor/meteor"; +import { check } from "meteor/check"; +import { Reaction } from "/lib/api"; +import { Shops } from "/lib/collections"; +import { SHOP_WORKFLOW_STATUS_ACTIVE, SHOP_WORKFLOW_STATUS_DISABLED } from "../lib/constants"; + +const status = [ + SHOP_WORKFLOW_STATUS_ACTIVE, + SHOP_WORKFLOW_STATUS_DISABLED +]; + +export function marketplaceUpdateShopWorkflow(shopId, workflowStatus) { + check(shopId, String); + check(workflowStatus, String); + + if (shopId === Reaction.getPrimaryShopId()) { + throw new Meteor.Error(403, "Cannot change shop status"); + } + + if (!Reaction.hasPermission("admin", this.userId, Reaction.getPrimaryShopId())) { + throw new Meteor.Error(403, "Cannot change shop status"); + } + + if (status.includes(workflowStatus)) { + return Shops.update({ + _id: shopId + }, { + $set: { + "workflow.status": workflowStatus + } + }, function (error) { + if (error) { + throw new Meteor.Error(500, error.message); + } + }); + } + + throw new Meteor.Error(500, "Workflow status could not be updated should be one if active or disabled"); +} + +Meteor.methods({ + "marketplace/updateShopWorkflow": marketplaceUpdateShopWorkflow +}); diff --git a/lib/collections/schemas/shops.js b/lib/collections/schemas/shops.js index b7f8cd62488..9480a628161 100644 --- a/lib/collections/schemas/shops.js +++ b/lib/collections/schemas/shops.js @@ -4,6 +4,7 @@ import { Email } from "./accounts"; import { Address } from "./address"; import { Layout } from "./layouts"; import { Metafield } from "./metafield"; +import { Workflow } from "./workflow"; /** * CustomEmailSettings Schema @@ -357,5 +358,9 @@ export const Shop = new SimpleSchema({ type: [Object], blackbox: true, defaultValue: [] + }, + "workflow": { + type: Workflow, + optional: true } }); diff --git a/server/publications/collections/shops.js b/server/publications/collections/shops.js index 98d2510382d..35fb47a2e89 100644 --- a/server/publications/collections/shops.js +++ b/server/publications/collections/shops.js @@ -42,14 +42,20 @@ Meteor.publish("MerchantShops", function () { delete fields.locales; } - // Return all non-primary shops for this domain that are active - return Shops.find({ + let selector = { domains: domain, shopType: { $ne: "primary" - }, - active: true - }, { - fields - }); + } + }; + + if (!Reaction.hasPermission("admin", this.userId, Reaction.getPrimaryShopId())) { + selector = { + ...selector, + "workflow.status": "active" + }; + } + + // Return all non-primary shops for this domain that are active + return Shops.find(selector, { fields }); }); From 5ef65433e7249f7f5b86ecbbdfa165c66a93a701 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 08:58:15 -0700 Subject: [PATCH 2/9] Add generic stylesheet for sortable tables --- .../client/styles/dashboard/tables.less | 39 +++++++++++++++++++ .../default-theme/client/styles/main.less | 1 + 2 files changed, 40 insertions(+) create mode 100644 imports/plugins/included/default-theme/client/styles/dashboard/tables.less diff --git a/imports/plugins/included/default-theme/client/styles/dashboard/tables.less b/imports/plugins/included/default-theme/client/styles/dashboard/tables.less new file mode 100644 index 00000000000..0fb8a1fa0aa --- /dev/null +++ b/imports/plugins/included/default-theme/client/styles/dashboard/tables.less @@ -0,0 +1,39 @@ +.rui.sortable-table-container { + position: relative; + padding: 20px; +} + +.sortable-table .title { + text-transform: capitalize; +} + +.sortable-table .col-dropdown, +.sortable-table .col-button { + width: 100%; + font-weight: @badge-font-weight; + display: inherit; + text-align: center; +} + +.sortable-table .rt-td span { + line-height: 2; +} + +.sortable-table .-odd { + background: transparent; +} + +.sortable-table .rt-td .btn { + padding: 0 5%; + width: 80%; +} + +.sortable-table .rt-thead.-header .rt-th { + text-align: left; + margin-left: 20px; + line-height: 2; +} + +.sortable-table .rt-tr-group:last-child { + border-bottom: 1px solid @rui-default-disabled; +} diff --git a/imports/plugins/included/default-theme/client/styles/main.less b/imports/plugins/included/default-theme/client/styles/main.less index 5a4f43ec23d..b09c11b8c9b 100644 --- a/imports/plugins/included/default-theme/client/styles/main.less +++ b/imports/plugins/included/default-theme/client/styles/main.less @@ -124,6 +124,7 @@ @import "dashboard/package.less"; @import "dashboard/accounts.less"; @import "dashboard/settings.less"; +@import "dashboard/tables.less"; @import "dashboard/widget.less"; // Layout From 31b2065ba8c6c8feddbaea64f8294a71dc5d7c7d Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 08:59:09 -0700 Subject: [PATCH 3/9] Adjust classnames and translation keys --- .../components/marketplaceShopTableCell.js | 20 ++++++++++++------- .../client/components/marketplaceShops.js | 7 +++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js b/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js index 44ab6e8631c..08e375adfcb 100644 --- a/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js +++ b/imports/plugins/included/marketplace/client/components/marketplaceShopTableCell.js @@ -1,7 +1,5 @@ -import { Meteor } from "meteor/meteor"; import _ from "lodash"; import React, { Component } from "react"; -import classnames from "classnames"; import PropTypes from "prop-types"; import { Components, registerComponent } from "@reactioncommerce/reaction-components"; import { SHOP_WORKFLOW_STATUS_ACTIVE, SHOP_WORKFLOW_STATUS_DISABLED } from "../../lib/constants"; @@ -39,8 +37,8 @@ class MarketplaceShopTableCell extends Component { if (field === "emails" && Array.isArray(data.value) && data.value.length) { return ( -
- {data.value[0].address} +
+ {data.value[0].address}
); } @@ -48,8 +46,16 @@ class MarketplaceShopTableCell extends Component { if (field === "workflow") { return ( + +   + + +
+ } onChange={this.handleWorkflowChange} + value={this.status} > - {data.value} +
+ {data.value}
); } diff --git a/imports/plugins/included/marketplace/client/components/marketplaceShops.js b/imports/plugins/included/marketplace/client/components/marketplaceShops.js index 60efcacb6d9..5dfd5ca82f0 100644 --- a/imports/plugins/included/marketplace/client/components/marketplaceShops.js +++ b/imports/plugins/included/marketplace/client/components/marketplaceShops.js @@ -13,7 +13,7 @@ class MarketplaceShops extends Component { const columnMetadata = fields.map((field) => { return { - Header: , + Header: , accessor: field, Cell: (data) => ( // eslint-disable-line -
- {/* */} +
+
{this.renderShopsTable()}
From 22f66840dda881f1c8e4b5a609adbb63bd051eac Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 09:01:25 -0700 Subject: [PATCH 4/9] Add translations for markeplace shops table --- imports/plugins/included/marketplace/server/i18n/en.json | 7 +++++++ imports/plugins/included/marketplace/server/index.js | 1 + 2 files changed, 8 insertions(+) diff --git a/imports/plugins/included/marketplace/server/i18n/en.json b/imports/plugins/included/marketplace/server/i18n/en.json index 1eac4aeada8..edd5f95125b 100644 --- a/imports/plugins/included/marketplace/server/i18n/en.json +++ b/imports/plugins/included/marketplace/server/i18n/en.json @@ -8,6 +8,13 @@ "errorCannotCreateShop": "Could not create shop for current user {{user}}", "yourShopIsReady": "Your shop is now ready!" }, + "marketplaceShops": { + "headers": { + "name": "Shop name", + "emails": "Contact email", + "workflow": "Status" + } + }, "app": { "shops": "Shops", "myShop": "My Shop" diff --git a/imports/plugins/included/marketplace/server/index.js b/imports/plugins/included/marketplace/server/index.js index 722ede3a0d7..3c1b57c34d3 100644 --- a/imports/plugins/included/marketplace/server/index.js +++ b/imports/plugins/included/marketplace/server/index.js @@ -1,2 +1,3 @@ import "./methods.js"; +import "./i18n"; import "./publications.js"; From 360370b6cec301bb54b96c18acdee1f60e0842c2 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 09:04:57 -0700 Subject: [PATCH 5/9] Filter out the primary shop out of the list of marketplace shops --- .../marketplace/client/containers/marketplaceShops.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/imports/plugins/included/marketplace/client/containers/marketplaceShops.js b/imports/plugins/included/marketplace/client/containers/marketplaceShops.js index e0251438587..abf86624fb4 100644 --- a/imports/plugins/included/marketplace/client/containers/marketplaceShops.js +++ b/imports/plugins/included/marketplace/client/containers/marketplaceShops.js @@ -1,15 +1,20 @@ import { composeWithTracker, registerComponent } from "@reactioncommerce/reaction-components"; import { Meteor } from "meteor/meteor"; import { Shops } from "/lib/collections"; +import { Reaction } from "/client/api"; import MarketplaceShops from "../components/marketplaceShops"; - const onWorkflowChange = (shopId, value) => { Meteor.call("marketplace/updateShopWorkflow", shopId, value); }; const composer = (props, onData) => { - const shops = Shops.find({}).fetch(); + // Get all shops, excluding the primary shop + const shops = Shops.find({ + _id: { + $nin: [Reaction.getPrimaryShopId()] + } + }).fetch(); onData(null, { shops, From 4f340df0c917fad4cdc111fbe83c1d0a660a5155 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 09:05:21 -0700 Subject: [PATCH 6/9] Don't show products for disabled shops --- server/publications/collections/products.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/publications/collections/products.js b/server/publications/collections/products.js index 55231b46bd9..635b5cff07b 100644 --- a/server/publications/collections/products.js +++ b/server/publications/collections/products.js @@ -513,6 +513,24 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so }] }); } + + // Get disabled shop id's to use for filtering + const disabledShopIds = Shops.find({ + "workflow.status": { + $in: ["disabled", "archived"] + } + }, { + fields: { _id: 1 } + }).map((shop) => shop._id); + + // Adjust the selector to exclude all disabled shops + newSelector = { + ...newSelector, + shopId: { + $nin: disabledShopIds + } + }; + // Returning Complete product tree for top level products to avoid sold out warning. const productCursor = Products.find(newSelector, { sort: sort From 0a9ff576513d662576a532a9cbf05bb8f9c4e5ea Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 6 Oct 2017 09:11:12 -0700 Subject: [PATCH 7/9] Load all translations for the reaction-marketplace package --- .../included/marketplace/server/i18n/index.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/imports/plugins/included/marketplace/server/i18n/index.js b/imports/plugins/included/marketplace/server/i18n/index.js index ec421000e9f..54444fb4957 100644 --- a/imports/plugins/included/marketplace/server/i18n/index.js +++ b/imports/plugins/included/marketplace/server/i18n/index.js @@ -1,10 +1,33 @@ import { loadTranslations } from "/server/startup/i18n"; +import ar from "./ar.json"; +import bg from "./bg.json"; +import cs from "./cs.json"; +import de from "./de.json"; +import el from "./el.json"; import en from "./en.json"; +import es from "./es.json"; +import fr from "./fr.json"; +import he from "./he.json"; +import hr from "./hr.json"; +import hu from "./hu.json"; +import it from "./it.json"; +import my from "./my.json"; +import nb from "./nb.json"; +import nl from "./nl.json"; +import pl from "./pl.json"; +import pt from "./pt.json"; +import ro from "./ro.json"; +import ru from "./ru.json"; +import sl from "./sl.json"; +import sv from "./sv.json"; +import tr from "./tr.json"; +import vi from "./vi.json"; +import zh from "./zh.json"; // // we want all the files in individual // imports for easier handling by // automated translation software // -loadTranslations([en]); +loadTranslations([ar, bg, cs, de, el, en, es, fr, he, hr, hu, it, my, nb, nl, pl, pt, ro, ru, sl, sv, tr, vi, zh]); From 9ef7a4e29bb6302354ed765c0cd1dc6edaf6143c Mon Sep 17 00:00:00 2001 From: Spencer Norman Date: Fri, 6 Oct 2017 12:35:47 -0600 Subject: [PATCH 8/9] Change numerical error codes to strings --- imports/plugins/included/marketplace/server/methods.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imports/plugins/included/marketplace/server/methods.js b/imports/plugins/included/marketplace/server/methods.js index ecf895784d6..bc5ace60135 100644 --- a/imports/plugins/included/marketplace/server/methods.js +++ b/imports/plugins/included/marketplace/server/methods.js @@ -14,11 +14,11 @@ export function marketplaceUpdateShopWorkflow(shopId, workflowStatus) { check(workflowStatus, String); if (shopId === Reaction.getPrimaryShopId()) { - throw new Meteor.Error(403, "Cannot change shop status"); + throw new Meteor.Error("access-denied", "Cannot change shop status"); } if (!Reaction.hasPermission("admin", this.userId, Reaction.getPrimaryShopId())) { - throw new Meteor.Error(403, "Cannot change shop status"); + throw new Meteor.Error("access-denied", "Cannot change shop status"); } if (status.includes(workflowStatus)) { @@ -30,12 +30,12 @@ export function marketplaceUpdateShopWorkflow(shopId, workflowStatus) { } }, function (error) { if (error) { - throw new Meteor.Error(500, error.message); + throw new Meteor.Error("server-error", error.message); } }); } - throw new Meteor.Error(500, "Workflow status could not be updated should be one if active or disabled"); + throw new Meteor.Error("server-error", "Workflow status could not be updated, should be 'active' or 'disabled'"); } Meteor.methods({ From a6a291d6e9f4041d2ad6b52de4aae37bfd09864b Mon Sep 17 00:00:00 2001 From: Spencer Norman Date: Fri, 6 Oct 2017 12:36:02 -0600 Subject: [PATCH 9/9] Remove eslint-disable line as it wasn't throwing any lint errors on my machine --- .../included/marketplace/client/components/marketplaceShops.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/included/marketplace/client/components/marketplaceShops.js b/imports/plugins/included/marketplace/client/components/marketplaceShops.js index 5dfd5ca82f0..cf0901ba43c 100644 --- a/imports/plugins/included/marketplace/client/components/marketplaceShops.js +++ b/imports/plugins/included/marketplace/client/components/marketplaceShops.js @@ -15,7 +15,7 @@ class MarketplaceShops extends Component { return { Header: , accessor: field, - Cell: (data) => ( // eslint-disable-line + Cell: (data) => (