- {{>productGrid products=products}}
+
+ {{>React component=component}}
+
{{#if hasPermission "createProduct"}}
{{>productList}}
{{/if}}
diff --git a/imports/plugins/included/product-variant/client/templates/products/products.js b/imports/plugins/included/product-variant/client/templates/products/products.js
index 5aeab9d02cb..9da7f89ddf8 100644
--- a/imports/plugins/included/product-variant/client/templates/products/products.js
+++ b/imports/plugins/included/product-variant/client/templates/products/products.js
@@ -1,175 +1,11 @@
-import _ from "lodash";
-import { Reaction } from "/lib/api";
-import { ReactionProduct } from "/lib/api";
-import { applyProductRevision } from "/lib/api/products";
-import { Products, Tags } from "/lib/collections";
-import { Session } from "meteor/session";
+import { $ } from "meteor/jquery";
import { Template } from "meteor/templating";
-import { ITEMS_INCREMENT } from "/client/config/defaults";
-
-/**
- * loadMoreProducts
- * @summary whenever #productScrollLimitLoader becomes visible, retrieve more results
- * this basically runs this:
- * Session.set('productScrollLimit', Session.get('productScrollLimit') + ITEMS_INCREMENT);
- * @return {undefined}
- */
-function loadMoreProducts() {
- let threshold;
- const target = $("#productScrollLimitLoader");
- let scrollContainer = $("#reactionAppContainer");
-
- if (scrollContainer.length === 0) {
- scrollContainer = $(window);
- }
-
- if (target.length) {
- threshold = scrollContainer.scrollTop() + scrollContainer.height() - target.height();
-
- if (target.offset().top < threshold) {
- if (!target.data("visible")) {
- target.data("productScrollLimit", true);
- Session.set("productScrollLimit", Session.get("productScrollLimit") + ITEMS_INCREMENT || 24);
- }
- } else {
- if (target.data("visible")) {
- target.data("visible", false);
- }
- }
- }
-}
-
-
-Template.products.onCreated(function () {
- this.products = ReactiveVar();
- this.state = new ReactiveDict();
- this.state.setDefault({
- initialLoad: true,
- slug: "",
- canLoadMoreProducts: false
- });
-
- // We're not ready to serve prerendered page until products have loaded
- window.prerenderReady = false;
-
- // Update product subscription
- this.autorun(() => {
- const slug = Reaction.Router.getParam("slug");
- const tag = Tags.findOne({ slug: slug }) || Tags.findOne(slug);
- const scrollLimit = Session.get("productScrollLimit");
- const options = {}; // this could be shop default implementation needed
-
- if (tag) {
- _.extend(options, { tags: [tag._id] });
- }
-
- // if we get an invalid slug, don't return all products
- if (!tag && slug) {
- return;
- }
-
- const hasMarketPlaceAccess = Reaction.hasMarketplaceAccess(["anonymous", "guest"]);
-
- // allow published content from all sellers for everyone
- if (hasMarketPlaceAccess) {
- // show all shops
- _.extend(options, { marketplace: true });
-
- // check for single shop page and pass it as shops to productFilters
- const shopId = Reaction.Router.current().params.shopId;
- if (shopId) {
- options.shops = [shopId];
- }
- }
-
- if (this.state.equals("slug", slug) === false && this.state.equals("initialLoad", false)) {
- this.state.set("initialLoad", true);
- }
-
- this.state.set("slug", slug);
-
- const queryParams = Object.assign({}, options, Reaction.Router.current().queryParams);
- const productsSubscription = this.subscribe("Products", scrollLimit, queryParams);
-
- // Once our products subscription is ready, we are ready to render.
- if (productsSubscription.ready() && (!hasMarketPlaceAccess)) {
- window.prerenderReady = true;
- }
-
- // we are caching `currentTag` or if we are not inside tag route, we will
- // use shop name as `base` name for `positions` object
- const currentTag = ReactionProduct.getTag();
- const productCursor = Products.find({
- ancestors: [],
- type: { $in: ["simple"] }
- }, {
- sort: {
- [`positions.${currentTag}.position`]: 1,
- [`positions.${currentTag}.createdAt`]: 1,
- createdAt: 1
- }
- });
-
- const products = productCursor.map((product) => {
- return applyProductRevision(product);
- });
-
- const sortedProducts = ReactionProduct.sortProducts(products, currentTag);
-
- this.state.set("canLoadMoreProducts", productCursor.count() >= Session.get("productScrollLimit"));
- this.products.set(sortedProducts);
- Session.set("productGrid/products", sortedProducts);
- });
-
- this.autorun(() => {
- const isActionViewOpen = Reaction.isActionViewOpen();
- if (isActionViewOpen === false) {
- Session.set("productGrid/selectedProducts", []);
- }
- });
-});
-
-Template.products.onRendered(() => {
- // run the above func every time the user scrolls
- $("#reactionAppContainer").on("scroll", loadMoreProducts);
- $(window).on("scroll", loadMoreProducts);
-});
+import { Reaction } from "/client/api";
+import ProductsContainer from "/imports/plugins/included/product-variant/containers/productsContainer";
Template.products.helpers({
- tag: function () {
- const id = Reaction.Router.getParam("_tag");
- return {
- tag: Tags.findOne({ slug: id }) || Tags.findOne(id)
- };
- },
-
- products() {
- return Template.instance().products.get();
- },
-
- loadMoreProducts() {
- return Template.instance().state.equals("canLoadMoreProducts", true);
- },
-
- initialLoad() {
- return Template.instance().state.set("initialLoad", true);
- },
-
- ready() {
- const instance = Template.instance();
- const isInitialLoad = instance.state.equals("initialLoad", true);
- const isReady = instance.subscriptionsReady();
-
- if (isInitialLoad === false) {
- return true;
- }
-
- if (isReady) {
- instance.state.set("initialLoad", false);
- return true;
- }
-
- return false;
+ component() {
+ return ProductsContainer;
}
});
@@ -191,9 +27,5 @@ Template.products.events({
Reaction.Router.go("product", {
handle: this._id
});
- },
- "click [data-event-action=loadMoreProducts]": (event) => {
- event.preventDefault();
- loadMoreProducts();
}
});
diff --git a/imports/plugins/included/product-variant/components/gridItemControls.js b/imports/plugins/included/product-variant/components/gridItemControls.js
new file mode 100644
index 00000000000..0277c0ac623
--- /dev/null
+++ b/imports/plugins/included/product-variant/components/gridItemControls.js
@@ -0,0 +1,61 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { IconButton, Translation } from "@reactioncommerce/reaction-ui";
+
+class GridItemControls extends Component {
+ static propTypes = {
+ checked: PropTypes.func,
+ hasChanges: PropTypes.func,
+ hasCreateProductPermission: PropTypes.func,
+ product: PropTypes.object
+ }
+
+ renderArchived() {
+ if (this.props.product.isDeleted) {
+ return (
+
+
+
+ );
+ }
+ }
+
+ renderVisibilityButton() {
+ if (this.props.hasChanges()) {
+ return (
+
+
+
+ );
+ }
+ }
+
+ render() {
+ if (this.props.hasCreateProductPermission()) {
+ return (
+
+
+
+ {this.renderArchived()}
+ {this.renderVisibilityButton()}
+
+ );
+ }
+ return null;
+ }
+}
+
+export default GridItemControls;
diff --git a/imports/plugins/included/product-variant/components/gridItemNotice.js b/imports/plugins/included/product-variant/components/gridItemNotice.js
new file mode 100644
index 00000000000..1c7522886d8
--- /dev/null
+++ b/imports/plugins/included/product-variant/components/gridItemNotice.js
@@ -0,0 +1,43 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Translation } from "@reactioncommerce/reaction-ui";
+
+class GridItemNotice extends Component {
+ static propTypes = {
+ isBackorder: PropTypes.func,
+ isLowQuantity: PropTypes.func,
+ isSoldOut: PropTypes.func
+ }
+
+ renderNotice() {
+ if (this.props.isSoldOut()) {
+ if (this.props.isBackorder()) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ } else if (this.props.isLowQuantity()) {
+ return (
+
+
+
+ );
+ }
+ }
+ render() {
+ return (
+
+ {this.renderNotice()}
+
+ );
+ }
+}
+
+export default GridItemNotice;
diff --git a/imports/plugins/included/product-variant/components/productGrid.js b/imports/plugins/included/product-variant/components/productGrid.js
new file mode 100644
index 00000000000..e65eabe138a
--- /dev/null
+++ b/imports/plugins/included/product-variant/components/productGrid.js
@@ -0,0 +1,49 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Translation } from "@reactioncommerce/reaction-ui";
+import ProductGridItemsContainer from "../containers/productGridItemsContainer";
+import { DragDropProvider } from "/imports/plugins/core/ui/client/providers";
+
+class ProductGrid extends Component {
+ static propTypes = {
+ products: PropTypes.array
+ }
+
+ renderProductGridItems = (products) => {
+ if (Array.isArray(products)) {
+ return products.map((product, index) => {
+ return (
+
+ );
+ });
+ }
+ return (
+
+ );
+ }
+
+ render() {
+ return (
+
+
+
+
+ {this.renderProductGridItems(this.props.products)}
+
+
+
+
+ );
+ }
+}
+
+export default ProductGrid;
diff --git a/imports/plugins/included/product-variant/components/productGridItems.js b/imports/plugins/included/product-variant/components/productGridItems.js
new file mode 100644
index 00000000000..352c1c8e66b
--- /dev/null
+++ b/imports/plugins/included/product-variant/components/productGridItems.js
@@ -0,0 +1,162 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { formatPriceString } from "/client/api";
+import GridItemNoticeContainer from "../containers/gridItemNoticeContainer";
+import GridItemControlsContainer from "../containers/gridItemControlsContainer";
+
+class ProductGridItems extends Component {
+ static propTypes = {
+ additionalMedia: PropTypes.func,
+ canEdit: PropTypes.bool,
+ connectDragSource: PropTypes.func,
+ connectDropTarget: PropTypes.func,
+ displayPrice: PropTypes.func,
+ isMediumWeight: PropTypes.func,
+ isSearch: PropTypes.bool,
+ isSelected: PropTypes.func,
+ media: PropTypes.func,
+ onClick: PropTypes.func,
+ onDoubleClick: PropTypes.func,
+ pdpPath: PropTypes.func,
+ positions: PropTypes.func,
+ product: PropTypes.object,
+ weightClass: PropTypes.func
+ }
+
+ handleDoubleClick = (event) => {
+ if (this.props.onDoubleClick) {
+ this.props.onDoubleClick(event);
+ }
+ }
+
+ handleClick = (event) => {
+ if (this.props.onClick) {
+ this.props.onClick(event);
+ }
+ }
+
+ renderPinned() {
+ return this.props.positions().pinned ? "pinned" : "";
+ }
+
+ renderVisible() {
+ return this.props.product.isVisible ? "" : "not-visible";
+ }
+
+ renderOverlay() {
+ if (this.props.product.isVisible === false) {
+ return (
+
+ );
+ }
+ }
+
+ renderMedia() {
+ if (this.props.media() === false) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ }
+
+ renderAdditionalMedia() {
+ if (this.props.additionalMedia() !== false) {
+ if (this.props.isMediumWeight()) {
+ return (
+
+ {this.props.additionalMedia().map((media) => {
+ ;
+ })}
+ {this.renderOverlay()}
+
+ );
+ }
+ }
+ }
+
+ renderNotices() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ renderGridContent() {
+ return (
+
+ );
+ }
+
+ renderHoverClassName() {
+ return this.props.isSearch ? "item-content" : "";
+ }
+
+ render() {
+ const productItem = (
+
+
+
+ );
+
+ if (this.props.canEdit) {
+ return (
+ this.props.connectDropTarget(
+ this.props.connectDragSource(productItem)
+ )
+ );
+ }
+
+ return productItem;
+ }
+}
+
+export default ProductGridItems;
diff --git a/imports/plugins/included/product-variant/components/products.js b/imports/plugins/included/product-variant/components/products.js
new file mode 100644
index 00000000000..8ebfdbe9dca
--- /dev/null
+++ b/imports/plugins/included/product-variant/components/products.js
@@ -0,0 +1,87 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Reaction } from "/client/api";
+import { getTagIds as getIds } from "/lib/selectors/tags";
+import { Translation } from "@reactioncommerce/reaction-ui";
+import ProductGridContainer from "../containers/productGridContainer";
+
+class ProductsComponent extends Component {
+ static propTypes = {
+ loadMoreProducts: PropTypes.func,
+ loadProducts: PropTypes.func,
+ products: PropTypes.array,
+ productsSubscription: PropTypes.object,
+ ready: PropTypes.func
+ };
+
+ handleClick = (event) => {
+ if (this.props.loadProducts) {
+ this.props.loadProducts(event);
+ }
+ }
+
+ renderProductGrid() {
+ const products = this.props.products;
+
+ const productsByKey = {};
+
+ if (Array.isArray(products)) {
+ for (const product of products) {
+ productsByKey[product._id] = product;
+ }
+ }
+
+ return (
+
+ );
+ }
+
+ renderSpinner() {
+ if (this.props.productsSubscription.ready() === false) {
+ return (
+
+ );
+ }
+ }
+
+ renderLoadMoreProductsButton() {
+ if (this.props.loadMoreProducts()) {
+ return (
+
+
+
+ );
+ }
+ }
+
+ render() {
+ if (this.props.ready()) {
+ return (
+
+ {this.renderProductGrid()}
+ {this.renderLoadMoreProductsButton()}
+ {this.renderSpinner()}
+
+ );
+ }
+ return (
+
+ );
+ }
+}
+
+export default ProductsComponent;
diff --git a/imports/plugins/included/product-variant/client/components/variantForm.js b/imports/plugins/included/product-variant/components/variantForm.js
similarity index 99%
rename from imports/plugins/included/product-variant/client/components/variantForm.js
rename to imports/plugins/included/product-variant/components/variantForm.js
index e87542d8047..3bff0a07544 100644
--- a/imports/plugins/included/product-variant/client/components/variantForm.js
+++ b/imports/plugins/included/product-variant/components/variantForm.js
@@ -1,5 +1,6 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import { isEqual } from "lodash";
-import React, { Component, PropTypes } from "react";
import Velocity from "velocity-animate";
import "velocity-animate/velocity.ui";
import update from "react/lib/update";
diff --git a/imports/plugins/included/product-variant/containers/gridItemControlsContainer.js b/imports/plugins/included/product-variant/containers/gridItemControlsContainer.js
new file mode 100644
index 00000000000..78a3f25aec9
--- /dev/null
+++ b/imports/plugins/included/product-variant/containers/gridItemControlsContainer.js
@@ -0,0 +1,61 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Session } from "meteor/session";
+import { composeWithTracker } from "/lib/api/compose";
+import { Reaction } from "/client/api";
+import GridItemControls from "../components/gridItemControls";
+
+class GridItemControlsContainer extends Component {
+ static propTypes = {
+ isSelected: PropTypes.bool,
+ product: PropTypes.object
+ }
+
+ constructor() {
+ super();
+
+ this.hasCreateProductPermission = this.hasCreateProductPermission.bind(this);
+ this.hasChanges = this.hasChanges.bind(this);
+ this.checked = this.checked.bind(this);
+ }
+
+ hasCreateProductPermission = () => {
+ return Reaction.hasPermission("createProduct");
+ }
+
+ hasChanges =() => {
+ return this.props.product.__draft ? true : false;
+ }
+
+ checked = () => {
+ return this.props.isSelected === true;
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+function composer(props, onData) {
+ const product = props.product;
+ let isSelected;
+
+ if (product) {
+ const selectedProducts = Session.get("productGrid/selectedProducts");
+ isSelected = Array.isArray(selectedProducts) ? selectedProducts.indexOf(product._id) >= 0 : false;
+ }
+
+ onData(null, {
+ product,
+ isSelected
+ });
+}
+
+export default composeWithTracker(composer)(GridItemControlsContainer);
diff --git a/imports/plugins/included/product-variant/containers/gridItemNoticeContainer.js b/imports/plugins/included/product-variant/containers/gridItemNoticeContainer.js
new file mode 100644
index 00000000000..e0e526e3c94
--- /dev/null
+++ b/imports/plugins/included/product-variant/containers/gridItemNoticeContainer.js
@@ -0,0 +1,69 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { ReactionProduct } from "/lib/api";
+import { composeWithTracker } from "/lib/api/compose";
+import GridItemNotice from "../components/gridItemNotice";
+
+class GridItemNoticeController extends Component {
+ static propTypes = {
+ product: PropTypes.object
+ }
+ constructor() {
+ super();
+
+ this.isLowQuantity = this.isLowQuantity.bind(this);
+ this.isSoldOut = this.isSoldOut.bind(this);
+ this.isBackorder = this.isBackorder.bind(this);
+ }
+
+ isLowQuantity = () => {
+ const topVariants = ReactionProduct.getTopVariants(this.props.product._id);
+
+ for (const topVariant of topVariants) {
+ const inventoryThreshold = topVariant.lowInventoryWarningThreshold;
+ const inventoryQuantity = ReactionProduct.getVariantQuantity(topVariant);
+
+ if (inventoryQuantity !== 0 && inventoryThreshold >= inventoryQuantity) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ isSoldOut = () => {
+ const topVariants = ReactionProduct.getTopVariants(this.props.product._id);
+
+ for (const topVariant of topVariants) {
+ const inventoryQuantity = ReactionProduct.getVariantQuantity(topVariant);
+
+ if (inventoryQuantity > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ isBackorder = () => {
+ return this.props.product.isBackorder;
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+function composer(props, onData) {
+ const product = props.product;
+
+ onData(null, {
+ product
+ });
+}
+
+export default composeWithTracker(composer)(GridItemNoticeController);
diff --git a/imports/plugins/included/product-variant/client/containers/gridPublishContainer.js b/imports/plugins/included/product-variant/containers/gridPublishContainer.js
similarity index 91%
rename from imports/plugins/included/product-variant/client/containers/gridPublishContainer.js
rename to imports/plugins/included/product-variant/containers/gridPublishContainer.js
index 689c205ba8f..a1f82cf0783 100644
--- a/imports/plugins/included/product-variant/client/containers/gridPublishContainer.js
+++ b/imports/plugins/included/product-variant/containers/gridPublishContainer.js
@@ -1,4 +1,7 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Meteor } from "meteor/meteor";
+import { Session } from "meteor/session";
import { composeWithTracker } from "/lib/api/compose";
import { ReactionProduct } from "/lib/api";
import { Products } from "/lib/collections";
diff --git a/imports/plugins/included/product-variant/containers/productGridContainer.js b/imports/plugins/included/product-variant/containers/productGridContainer.js
new file mode 100644
index 00000000000..ac7238c0868
--- /dev/null
+++ b/imports/plugins/included/product-variant/containers/productGridContainer.js
@@ -0,0 +1,165 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import update from "react/lib/update";
+import _ from "lodash";
+import { Meteor } from "meteor/meteor";
+import { Session } from "meteor/session";
+import { Reaction } from "/client/api";
+import Logger from "/client/modules/logger";
+import { ReactionProduct } from "/lib/api";
+import { composeWithTracker } from "/lib/api/compose";
+import { DragDropProvider } from "/imports/plugins/core/ui/client/providers";
+import ProductGrid from "../components/productGrid";
+
+class ProductGridContainer extends Component {
+ static propTypes = {
+ canEdit: PropTypes.bool,
+ isSearch: PropTypes.bool,
+ productIds: PropTypes.array,
+ products: PropTypes.array,
+ productsByKey: PropTypes.object,
+ unmountMe: PropTypes.func
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ products: props.products,
+ productsByKey: props.productsByKey,
+ productIds: props.productIds,
+ initialLoad: true,
+ slug: "",
+ canLoadMoreProducts: false
+ };
+ }
+
+ componentWillMount() {
+ const selectedProducts = Reaction.getUserPreferences("reaction-product-variant", "selectedGridItems");
+ const products = this.products;
+
+ if (_.isEmpty(selectedProducts)) {
+ return Reaction.hideActionView();
+ }
+
+ // Save the selected items to the Session
+ Session.set("productGrid/selectedProducts", _.uniq(selectedProducts));
+
+ if (products) {
+ const filteredProducts = _.filter(products, (product) => {
+ return _.includes(selectedProducts, product._id);
+ });
+
+ if (Reaction.isPreview() === false) {
+ Reaction.showActionView({
+ label: "Grid Settings",
+ i18nKeyLabel: "gridSettingsPanel.title",
+ template: "productSettings",
+ type: "product",
+ data: { products: filteredProducts }
+ });
+ }
+ }
+ }
+
+ componentWillReceiveProps = (nextProps) => {
+ this.setState({
+ products: nextProps.products,
+ productIds: nextProps.productIds,
+ productsByKey: nextProps.productsByKey
+ });
+ }
+
+ handleSelectProductItem = (isChecked, productId) => {
+ let selectedProducts = Session.get("productGrid/selectedProducts");
+
+ if (isChecked) {
+ selectedProducts.push(productId);
+ } else {
+ selectedProducts = _.without(selectedProducts, productId);
+ }
+
+ Reaction.setUserPreferences("reaction-product-variant", "selectedGridItems", selectedProducts);
+
+ // Save the selected items to the Session
+ Session.set("productGrid/selectedProducts", _.uniq(selectedProducts));
+
+ const products = this.products;
+
+ if (products) {
+ const filteredProducts = _.filter(products, (product) => {
+ return _.includes(selectedProducts, product._id);
+ });
+
+ Reaction.showActionView({
+ label: "Grid Settings",
+ i18nKeyLabel: "gridSettingsPanel.title",
+ template: "productSettings",
+ type: "product",
+ data: { products: filteredProducts }
+ });
+ }
+ }
+
+ handleProductDrag = (dragIndex, hoverIndex) => {
+ const newState = this.changeProductOrderOnState(dragIndex, hoverIndex);
+ this.setState(newState, this.callUpdateMethod);
+ }
+
+ changeProductOrderOnState(dragIndex, hoverIndex) {
+ const product = this.state.productIds[dragIndex];
+
+ return update(this.state, {
+ productIds: {
+ $splice: [
+ [dragIndex, 1],
+ [hoverIndex, 0, product]
+ ]
+ }
+ });
+ }
+
+ callUpdateMethod() {
+ const tag = ReactionProduct.getTag();
+
+ this.state.productIds.map((productId, index) => {
+ const position = { position: index, updatedAt: new Date() };
+
+ Meteor.call("products/updateProductPosition", productId, position, tag, error => {
+ if (error) {
+ Logger.error(error);
+ throw new Meteor.Error(error);
+ }
+ });
+ });
+ }
+
+ get products() {
+ if (this.props.isSearch) {
+ return this.state.products;
+ }
+ return this.state.productIds.map((id) => this.state.productsByKey[id]);
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+
+function composer(props, onData) {
+ onData(null, {});
+}
+
+export default composeWithTracker(composer)(ProductGridContainer);
diff --git a/imports/plugins/included/product-variant/containers/productGridItemsContainer.js b/imports/plugins/included/product-variant/containers/productGridItemsContainer.js
new file mode 100644
index 00000000000..061319a8570
--- /dev/null
+++ b/imports/plugins/included/product-variant/containers/productGridItemsContainer.js
@@ -0,0 +1,271 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import _ from "lodash";
+import { Session } from "meteor/session";
+import { composeWithTracker } from "/lib/api/compose";
+import { Reaction } from "/client/api";
+import { ReactionProduct } from "/lib/api";
+import { Media } from "/lib/collections";
+import { SortableItem } from "/imports/plugins/core/ui/client/containers";
+import ProductGridItems from "../components/productGridItems";
+
+class ProductGridItemsContainer extends Component {
+ static propTypes = {
+ connectDragSource: PropTypes.func,
+ connectDropTarget: PropTypes.func,
+ isSearch: PropTypes.bool,
+ itemSelectHandler: PropTypes.func,
+ product: PropTypes.object,
+ unmountMe: PropTypes.func
+ }
+
+ constructor() {
+ super();
+
+ this.productPath = this.productPath.bind(this);
+ this.positions = this.positions.bind(this);
+ this.weightClass = this.weightClass.bind(this);
+ this.isSelected = this.isSelected.bind(this);
+ this.productMedia = this.productMedia.bind(this);
+ this.additionalProductMedia = this.additionalProductMedia.bind(this);
+ this.isMediumWeight = this.isMediumWeight.bind(this);
+ this.displayPrice = this.displayPrice.bind(this);
+ this.onDoubleClick = this.onDoubleClick.bind(this);
+ this.onClick = this.onClick.bind(this);
+ this.onPageClick = this.onPageClick.bind(this);
+ }
+
+ componentDidMount() {
+ document.querySelector(".page > main").addEventListener("click", this.onPageClick);
+ }
+
+ componentWillUnmount() {
+ document.querySelector(".page > main").removeEventListener("click", this.onPageClick);
+ }
+
+ onPageClick = () => {
+ // Do nothing if we are in preview mode
+ if (Reaction.isPreview() === false) {
+ // Don't trigger the clear selection if we're clicking on a grid item.
+ if (event.target.closest(".product-grid-item") === null) {
+ const selectedProducts = Session.get("productGrid/selectedProducts");
+
+ // Do we have any selected products?
+ // If we do then lets reset the Grid Settings ActionView
+ if (Array.isArray(selectedProducts) && selectedProducts.length) {
+ // Reset sessions ver of selected products
+ Session.set("productGrid/selectedProducts", []);
+
+ // Reset the action view of selected products
+ Reaction.setActionView({
+ label: "Grid Settings",
+ i18nKeyLabel: "gridSettingsPanel.title",
+ template: "productSettings",
+ type: "product",
+ data: {}
+ });
+ }
+ }
+ }
+ }
+
+ productPath = () => {
+ if (this.props.product) {
+ let handle = this.props.product.handle;
+
+ if (this.props.product.__published) {
+ handle = this.props.product.__published.handle;
+ }
+
+ return Reaction.Router.pathFor("product", {
+ hash: {
+ handle
+ }
+ });
+ }
+
+ return "/";
+ }
+
+ positions = () => {
+ const tag = ReactionProduct.getTag();
+ return this.props.product.positions && this.props.product.positions[tag] || {};
+ }
+
+ weightClass = () => {
+ const positions = this.positions();
+ const weight = positions.weight || 0;
+ switch (weight) {
+ case 1:
+ return "product-medium";
+ case 2:
+ return "product-large";
+ default:
+ return "product-small";
+ }
+ }
+
+ isSelected = () => {
+ if (Reaction.isPreview() === false) {
+ return _.includes(Session.get("productGrid/selectedProducts"), this.props.product._id) ? "active" : "";
+ }
+ return false;
+ }
+
+ productMedia = () => {
+ const media = Media.findOne({
+ "metadata.productId": this.props.product._id,
+ "metadata.toGrid": 1
+ }, {
+ sort: { "metadata.priority": 1, "uploadedAt": 1 }
+ });
+
+ return media instanceof FS.File ? media : false;
+ }
+
+ additionalProductMedia = () => {
+ const mediaArray = Media.find({
+ "metadata.productId": this.props.product._id,
+ "metadata.priority": {
+ $gt: 0
+ },
+ "metadata.toGrid": 1
+ }, { limit: 3 });
+
+ return mediaArray.count() > 1 ? mediaArray : false;
+ }
+
+ isMediumWeight = () => {
+ const positions = this.positions();
+ const weight = positions.weight || 0;
+
+ return weight === 1;
+ }
+
+ displayPrice = () => {
+ if (this.props.product.price && this.props.product.price.range) {
+ return this.props.product.price.range;
+ }
+ }
+
+ handleCheckboxSelect = (list, product) => {
+ let checkbox = list.querySelector(`input[type=checkbox][value="${product._id}"]`);
+ const items = document.querySelectorAll("li.product-grid-item");
+ const activeItems = document.querySelectorAll("li.product-grid-item.active");
+ const selected = activeItems.length;
+
+ if (event.shiftKey && selected > 0) {
+ const indexes = [
+ Array.prototype.indexOf.call(items, document.querySelector(`li.product-grid-item[id="${product._id}"]`)),
+ Array.prototype.indexOf.call(items, activeItems[0]),
+ Array.prototype.indexOf.call(items, activeItems[selected - 1])
+ ];
+ for (let i = _.min(indexes); i <= _.max(indexes); i++) {
+ checkbox = items[i].querySelector("input[type=checkbox]");
+ if (checkbox.checked === false) {
+ checkbox.checked = true;
+ this.props.itemSelectHandler(checkbox.checked, product._id);
+ }
+ }
+ } else {
+ checkbox.checked = !checkbox.checked;
+ this.props.itemSelectHandler(checkbox.checked, product._id);
+ }
+ }
+
+ onDoubleClick = () => {
+ const product = this.props.product;
+ const handle = product.__published && product.__published.handle || product.handle;
+
+ Reaction.Router.go("product", {
+ handle: handle
+ });
+
+ Reaction.setActionView({
+ i18nKeyLabel: "productDetailEdit.productSettings",
+ label: "Product Settings",
+ template: "ProductAdmin"
+ });
+
+ if (this.props.isSearch) {
+ this.props.unmountMe();
+ }
+ }
+
+ onClick = (event) => {
+ event.preventDefault();
+ const product = this.props.product;
+
+ if (Reaction.hasPermission("createProduct") && Reaction.isPreview() === false) {
+ if (this.props.isSearch) {
+ let handle = product.handle;
+ if (product.__published) {
+ handle = product.__published.handle;
+ }
+
+ Reaction.Router.go("product", {
+ handle: handle
+ });
+
+ this.props.unmountMe();
+ }
+
+ const isSelected = event.target.closest("li.product-grid-item.active");
+ const list = document.getElementById("product-grid-list");
+
+ if (isSelected) {
+ // If a product is already selected, and you are single clicking on another product(s)
+ // WITH command key, the product(s) are added to the selected products Session array
+ this.handleCheckboxSelect(list, product);
+ if (event.metaKey || event.ctrlKey || event.shiftKey) {
+ this.handleCheckboxSelect(list, product);
+ }
+ } else {
+ if (event.metaKey || event.ctrlKey || event.shiftKey) {
+ this.handleCheckboxSelect(list, product);
+ } else {
+ const checkbox = list.querySelector(`input[type=checkbox][value="${product._id}"]`);
+ Session.set("productGrid/selectedProducts", []);
+ checkbox.checked = true;
+ this.props.itemSelectHandler(checkbox.checked, product._id);
+ }
+ }
+ } else {
+ const handle = product.__published && product.__published.handle || product.handle;
+
+ Reaction.Router.go("product", {
+ handle: handle
+ });
+
+ if (this.props.isSearch) {
+ this.props.unmountMe();
+ }
+ }
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+function composer(props, onData) {
+ onData(null, {});
+}
+
+const container = composeWithTracker(composer)(ProductGridItemsContainer);
+export default SortableItem("productGridItem", container);
diff --git a/imports/plugins/included/product-variant/containers/productsContainer.js b/imports/plugins/included/product-variant/containers/productsContainer.js
new file mode 100644
index 00000000000..93448b4f7cf
--- /dev/null
+++ b/imports/plugins/included/product-variant/containers/productsContainer.js
@@ -0,0 +1,161 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Meteor } from "meteor/meteor";
+import { Session } from "meteor/session";
+import { Reaction } from "/client/api";
+import { ITEMS_INCREMENT } from "/client/config/defaults";
+import { ReactionProduct } from "/lib/api";
+import { composeWithTracker } from "/lib/api/compose";
+import { applyProductRevision } from "/lib/api/products";
+import { Products, Tags } from "/lib/collections";
+import ProductsComponent from "../components/products";
+
+/**
+ * loadMoreProducts
+ * @summary whenever #productScrollLimitLoader becomes visible, retrieve more results
+ * this basically runs this:
+ * Session.set('productScrollLimit', Session.get('productScrollLimit') + ITEMS_INCREMENT);
+ * @return {undefined}
+ */
+function loadMoreProducts() {
+ let threshold;
+ const target = document.querySelectorAll("#productScrollLimitLoader");
+ let scrollContainer = document.querySelectorAll("#reactionAppContainer");
+
+ if (scrollContainer.length === 0) {
+ scrollContainer = window;
+ }
+
+ if (target.length) {
+ threshold = scrollContainer[0].scrollTop + scrollContainer[0].offsetHeight - target[0].offsetHeight;
+
+ if (target[0].offsetTop <= threshold) {
+ if (!target[0].getAttribute("visible")) {
+ target[0].setAttribute("productScrollLimit", true);
+ Session.set("productScrollLimit", Session.get("productScrollLimit") + ITEMS_INCREMENT || 24);
+ }
+ } else {
+ if (target[0].getAttribute("visible")) {
+ target[0].setAttribute("visible", false);
+ }
+ }
+ }
+}
+
+class ProductsContainer extends Component {
+ static propTypes = {
+ canLoadMoreProducts: PropTypes.bool,
+ products: PropTypes.array,
+ productsSubscription: PropTypes.object
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ initialLoad: true
+ };
+
+ this.ready = this.ready.bind(this);
+ this.loadMoreProducts = this.loadMoreProducts.bind(this);
+ }
+
+ ready = () => {
+ const isInitialLoad = this.state.initialLoad === true;
+ const isReady = this.props.productsSubscription.ready();
+
+ if (isInitialLoad === false) {
+ return true;
+ }
+
+ if (isReady) {
+ return true;
+ }
+ return false;
+ }
+
+ loadMoreProducts = () => {
+ return this.props.canLoadMoreProducts === true;
+ }
+
+ loadProducts = (event) => {
+ event.preventDefault();
+ this.setState({
+ initialLoad: false
+ });
+ loadMoreProducts();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+function composer(props, onData) {
+ window.prerenderReady = false;
+
+ let canLoadMoreProducts = false;
+
+ const slug = Reaction.Router.getParam("slug");
+ const tag = Tags.findOne({ slug: slug }) || Tags.findOne(slug);
+ const scrollLimit = Session.get("productScrollLimit");
+ let tags = {}; // this could be shop default implementation needed
+
+ if (tag) {
+ tags = { tags: [tag._id] };
+ }
+
+ // if we get an invalid slug, don't return all products
+ if (!tag && slug) {
+ return;
+ }
+
+ const queryParams = Object.assign({}, tags, Reaction.Router.current().queryParams);
+ const productsSubscription = Meteor.subscribe("Products", scrollLimit, queryParams);
+
+ if (productsSubscription.ready()) {
+ window.prerenderReady = true;
+ }
+
+ const currentTag = ReactionProduct.getTag();
+ const productCursor = Products.find({
+ ancestors: [],
+ type: { $in: ["simple"] }
+ }, {
+ sort: {
+ [`positions.${currentTag}.position`]: 1,
+ [`positions.${currentTag}.createdAt`]: 1,
+ createdAt: 1
+ }
+ });
+
+ const products = productCursor.map((product) => {
+ return applyProductRevision(product);
+ });
+
+ const sortedProducts = ReactionProduct.sortProducts(products, currentTag);
+
+
+ canLoadMoreProducts = productCursor.count() >= Session.get("productScrollLimit");
+ const stateProducts = sortedProducts;
+
+ Session.set("productGrid/products", sortedProducts);
+
+ const isActionViewOpen = Reaction.isActionViewOpen();
+ if (isActionViewOpen === false) {
+ Session.set("productGrid/selectedProducts", []);
+ }
+
+ onData(null, {
+ productsSubscription,
+ products: stateProducts,
+ canLoadMoreProducts
+ });
+}
+export default composeWithTracker(composer)(ProductsContainer);
diff --git a/imports/plugins/included/product-variant/client/containers/variantFormContainer.js b/imports/plugins/included/product-variant/containers/variantFormContainer.js
similarity index 91%
rename from imports/plugins/included/product-variant/client/containers/variantFormContainer.js
rename to imports/plugins/included/product-variant/containers/variantFormContainer.js
index 0e95ffe4559..c8273f13251 100644
--- a/imports/plugins/included/product-variant/client/containers/variantFormContainer.js
+++ b/imports/plugins/included/product-variant/containers/variantFormContainer.js
@@ -1,4 +1,6 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { Session } from "meteor/session";
import { composeWithTracker } from "/lib/api/compose";
@@ -140,19 +142,19 @@ class VariantFormContainer extends Component {
return;
}
Meteor.call("products/cloneVariant", productId, variant._id,
- function (error, result) {
- if (error) {
- Alerts.alert({
- text: i18next.t("productDetailEdit.cloneVariantFail", { title }),
- confirmButtonText: i18next.t("app.close", { defaultValue: "Close" })
- });
- } else if (result) {
- const variantId = result[0];
-
- ReactionProduct.setCurrentVariant(variantId);
- Session.set("variant-form-" + variantId, true);
- }
- });
+ function (error, result) {
+ if (error) {
+ Alerts.alert({
+ text: i18next.t("productDetailEdit.cloneVariantFail", { title }),
+ confirmButtonText: i18next.t("app.close", { defaultValue: "Close" })
+ });
+ } else if (result) {
+ const variantId = result[0];
+
+ ReactionProduct.setCurrentVariant(variantId);
+ Session.set("variant-form-" + variantId, true);
+ }
+ });
}
handleVariantFieldSave = (variantId, fieldName, value) => {
diff --git a/imports/plugins/included/product-variant/server/methods/populateTaxCodes.js b/imports/plugins/included/product-variant/server/methods/populateTaxCodes.js
index 28f82c0ceec..0fd5931ed59 100644
--- a/imports/plugins/included/product-variant/server/methods/populateTaxCodes.js
+++ b/imports/plugins/included/product-variant/server/methods/populateTaxCodes.js
@@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
+import { check } from "meteor/check";
import { TaxCodes } from "/imports/plugins/core/taxes/lib/collections";
const taxCodes = {};
diff --git a/imports/plugins/included/search-mongo/client/settings/search.js b/imports/plugins/included/search-mongo/client/settings/search.js
index 09ff43d1c06..70a5e698ff9 100644
--- a/imports/plugins/included/search-mongo/client/settings/search.js
+++ b/imports/plugins/included/search-mongo/client/settings/search.js
@@ -1,3 +1,4 @@
+import { AutoForm } from "meteor/aldeed:autoform";
import { Template } from "meteor/templating";
import { Reaction, i18next } from "/client/api";
import { Packages } from "/lib/collections";
diff --git a/imports/plugins/included/search-mongo/server/jobs/buildSearchCollections.js b/imports/plugins/included/search-mongo/server/jobs/buildSearchCollections.js
index 1e5a275ed54..be87288b1de 100644
--- a/imports/plugins/included/search-mongo/server/jobs/buildSearchCollections.js
+++ b/imports/plugins/included/search-mongo/server/jobs/buildSearchCollections.js
@@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
+import { Job } from "meteor/vsivsi:job-collection";
import { Jobs, ProductSearch, Orders, OrderSearch, AccountSearch } from "/lib/collections";
import { Hooks, Logger } from "/server/api";
import { buildProductSearch, buildOrderSearch, buildAccountSearch,
diff --git a/imports/plugins/included/search-mongo/server/methods/searchcollections.js b/imports/plugins/included/search-mongo/server/methods/searchcollections.js
index b7d51393617..c97acbe67f4 100644
--- a/imports/plugins/included/search-mongo/server/methods/searchcollections.js
+++ b/imports/plugins/included/search-mongo/server/methods/searchcollections.js
@@ -1,6 +1,6 @@
/* eslint camelcase: 0 */
-import _ from "lodash";
import moment from "moment";
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";
import { Reaction, Logger } from "/server/api";
diff --git a/imports/plugins/included/search-mongo/server/publications/searchresults.app-test.js b/imports/plugins/included/search-mongo/server/publications/searchresults.app-test.js
index 6e2db55d454..c38e852998c 100644
--- a/imports/plugins/included/search-mongo/server/publications/searchresults.app-test.js
+++ b/imports/plugins/included/search-mongo/server/publications/searchresults.app-test.js
@@ -1,4 +1,5 @@
import faker from "faker";
+import { Factory } from "meteor/dburles:factory";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { Reaction } from "/server/api";
diff --git a/imports/plugins/included/search-mongo/server/publications/searchresults.js b/imports/plugins/included/search-mongo/server/publications/searchresults.js
index b4073e3b973..1d15265d6c7 100644
--- a/imports/plugins/included/search-mongo/server/publications/searchresults.js
+++ b/imports/plugins/included/search-mongo/server/publications/searchresults.js
@@ -1,5 +1,6 @@
import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { Roles } from "meteor/alanning:roles";
import { check, Match } from "meteor/check";
import { Reaction, Logger } from "/server/api";
import { ProductSearch, OrderSearch, AccountSearch } from "/lib/collections";
diff --git a/imports/plugins/included/shipping-rates/client/templates/settings/rates.js b/imports/plugins/included/shipping-rates/client/templates/settings/rates.js
index 675c2e29335..c58db0a4dca 100644
--- a/imports/plugins/included/shipping-rates/client/templates/settings/rates.js
+++ b/imports/plugins/included/shipping-rates/client/templates/settings/rates.js
@@ -1,10 +1,11 @@
+import { $ } from "meteor/jquery";
+import { Meteor } from "meteor/meteor";
import { Template } from "meteor/templating";
import { ReactiveDict } from "meteor/reactive-dict";
import { AutoForm } from "meteor/aldeed:autoform";
import { Shipping } from "/lib/collections";
import { i18next } from "/client/api";
-import MeteorGriddle from "/imports/plugins/core/ui-grid/client/griddle";
-import { IconButton, Loading } from "/imports/plugins/core/ui/client/components";
+import { IconButton, Loading, SortableTable } from "/imports/plugins/core/ui/client/components";
Template.shippingRatesSettings.onCreated(function () {
this.autorun(() => {
@@ -79,8 +80,8 @@ Template.shippingRatesSettings.helpers({
const customColumnMetadata = [];
filteredFields.forEach(function (field) {
const columnMeta = {
- columnName: field,
- displayName: i18next.t(`admin.shippingGrid.${field}`)
+ accessor: field,
+ Header: i18next.t(`admin.shippingGrid.${field}`)
};
customColumnMetadata.push(columnMeta);
});
@@ -99,13 +100,12 @@ Template.shippingRatesSettings.helpers({
// return shipping Grid
return {
- component: MeteorGriddle,
+ component: SortableTable,
publication: "Shipping",
transform: transform,
collection: Shipping,
matchingResultsCount: "shipping-count",
showFilter: true,
- useGriddleStyles: false,
rowMetadata: customRowMetaData,
filteredFields: filteredFields,
columns: filteredFields,
diff --git a/imports/plugins/included/shipping-rates/server/methods/rates.js b/imports/plugins/included/shipping-rates/server/methods/rates.js
index 701d32e8a69..9b4e18bea31 100644
--- a/imports/plugins/included/shipping-rates/server/methods/rates.js
+++ b/imports/plugins/included/shipping-rates/server/methods/rates.js
@@ -1,5 +1,6 @@
import { Meteor } from "meteor/meteor";
-import { check } from "meteor/check";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
import { Shipping } from "/lib/collections";
import { ShippingMethod } from "/lib/collections/schemas";
import { Reaction } from "/server/api";
diff --git a/imports/plugins/included/shippo/client/settings/carriers.js b/imports/plugins/included/shippo/client/settings/carriers.js
index e562a4178ca..da81bc68e7a 100644
--- a/imports/plugins/included/shippo/client/settings/carriers.js
+++ b/imports/plugins/included/shippo/client/settings/carriers.js
@@ -1,10 +1,12 @@
+import { $ } from "meteor/jquery";
import { Template } from "meteor/templating";
import { ReactiveDict } from "meteor/reactive-dict";
import { AutoForm } from "meteor/aldeed:autoform";
import { Shipping } from "/lib/collections";
import { i18next } from "/client/api";
-import MeteorGriddle from "/imports/plugins/core/ui-grid/client/griddle";
-import { Loading } from "/imports/plugins/core/ui/client/components";
+import { Loading, SortableTable } from "/imports/plugins/core/ui/client/components";
+import ShippoTableColumn from "./shippoTableColumn";
+import React from "react";
import "./carriers.html";
@@ -49,9 +51,25 @@ Template.shippoCarriers.helpers({
// add i18n handling to headers
const customColumnMetadata = [];
filteredFields.forEach(function (field) {
+ let colWidth = undefined;
+ let colStyle = undefined;
+ let colClassName = undefined;
+
+ if (field === "enabled") {
+ colWidth = undefined;
+ colStyle = { textAlign: "center" };
+ colClassName = "shippo-carrier-status";
+ }
+
const columnMeta = {
- columnName: field,
- displayName: i18next.t(`admin.shippingGrid.${field}`)
+ accessor: field,
+ Header: i18next.t(`admin.shippingGrid.${field}`),
+ Cell: row => ( // eslint-disable-line
+
+ ),
+ className: colClassName,
+ width: colWidth,
+ style: colStyle
};
customColumnMetadata.push(columnMeta);
});
@@ -71,12 +89,11 @@ Template.shippoCarriers.helpers({
// return shipping Grid
return {
- component: MeteorGriddle,
+ component: SortableTable,
publication: "Shipping",
transform: transform,
collection: Shipping,
showFilter: true,
- useGriddleStyles: false,
rowMetadata: customRowMetaData,
filteredFields: filteredFields,
columns: filteredFields,
@@ -112,13 +129,12 @@ Template.shippoCarriers.events({
});
},
"click .cancel, .shipping-carriers-grid-row .active": function () {
- instance = Template.instance();
+ const instance = Template.instance();
// remove active rows from grid
instance.state.set({
isEditing: false,
editingId: null
});
- // ugly hack
$(".shipping-carriers-grid-row").removeClass("active");
},
"click .shipping-carriers-grid-row": function (event) {
diff --git a/imports/plugins/included/shippo/client/settings/shippo.js b/imports/plugins/included/shippo/client/settings/shippo.js
index a77e62f28ae..a57b5289e3a 100644
--- a/imports/plugins/included/shippo/client/settings/shippo.js
+++ b/imports/plugins/included/shippo/client/settings/shippo.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { AutoForm } from "meteor/aldeed:autoform";
import { Template } from "meteor/templating";
import { Reaction, i18next } from "/client/api";
import { Packages } from "/lib/collections";
@@ -29,7 +31,7 @@ AutoForm.hooks({
onSuccess(formType, result) {
Alerts.removeSeen();
const successMsg = (result.type === "delete") ? i18next.t("admin.settings.saveSuccess") :
- i18next.t("shippo.connectedAndSaved");
+ i18next.t("shippo.connectedAndSaved");
return Alerts.toast(successMsg, "success", {
autoHide: true
diff --git a/imports/plugins/included/shippo/client/settings/shippoTableColumn.js b/imports/plugins/included/shippo/client/settings/shippoTableColumn.js
new file mode 100644
index 00000000000..b85748a62f3
--- /dev/null
+++ b/imports/plugins/included/shippo/client/settings/shippoTableColumn.js
@@ -0,0 +1,36 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Icon } from "/imports/plugins/core/ui/client/components";
+
+class EmailTableColumn extends Component {
+ static propTypes = {
+ data: PropTypes.object,
+ row: PropTypes.object
+ }
+
+ render() {
+ const { row } = this.props;
+
+ const renderColumn = row.column.id;
+
+ if (renderColumn === "enabled") {
+ if (row.value === true) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ }
+ return (
+
{row.value}
+ );
+ }
+}
+
+export default EmailTableColumn;
diff --git a/imports/plugins/included/shippo/register.js b/imports/plugins/included/shippo/register.js
index 63ddc68569f..ea72f614669 100644
--- a/imports/plugins/included/shippo/register.js
+++ b/imports/plugins/included/shippo/register.js
@@ -32,18 +32,18 @@ Reaction.registerPackage({
container: "connection",
template: "shippoSettings"
}
-// WIP:
-// TODO: Review custom shipping in checkout, are layout handling this requirement
-// For now we use Flat Rate's checkout template( which inherits its methods from coreCheckoutShipping
-// to show all shipping methods in the same panel.
-// .If we are gonna proceed with different panel per provider, we need to enable the 'provides:"Shipping Method"',
-// alter coreCheckoutShipping checkout.js and inherit from there (or write specific logic) for a shippo's
-// checkout template.
-//
-// provides: "shippingMethod",
-// name: "shipping/methods/shippo",
-// template: "shippoCheckoutShipping"
-// Not needed at the time cause the coreCheckoutShipping is enough(inherited from Flatrate)
-// }
+ // WIP:
+ // TODO: Review custom shipping in checkout, are layout handling this requirement
+ // For now we use Flat Rate's checkout template( which inherits its methods from coreCheckoutShipping
+ // to show all shipping methods in the same panel.
+ // .If we are gonna proceed with different panel per provider, we need to enable the 'provides:"Shipping Method"',
+ // alter coreCheckoutShipping checkout.js and inherit from there (or write specific logic) for a shippo's
+ // checkout template.
+ //
+ // provides: "shippingMethod",
+ // name: "shipping/methods/shippo",
+ // template: "shippoCheckoutShipping"
+ // Not needed at the time cause the coreCheckoutShipping is enough(inherited from Flatrate)
+ // }
]
});
diff --git a/imports/plugins/included/shippo/server/jobs/shippo.js b/imports/plugins/included/shippo/server/jobs/shippo.js
index 74ba64b6d68..1f6bea9ea0c 100644
--- a/imports/plugins/included/shippo/server/jobs/shippo.js
+++ b/imports/plugins/included/shippo/server/jobs/shippo.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { Job } from "meteor/vsivsi:job-collection";
import { Hooks, Logger, Reaction } from "/server/api";
import { Jobs, Packages } from "/lib/collections";
diff --git a/imports/plugins/included/shippo/server/methods/shippo.js b/imports/plugins/included/shippo/server/methods/shippo.js
index d41d54d19f2..5fdf14966b5 100644
--- a/imports/plugins/included/shippo/server/methods/shippo.js
+++ b/imports/plugins/included/shippo/server/methods/shippo.js
@@ -1,6 +1,8 @@
/* eslint camelcase: 0 */
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
-import { check } from "meteor/check";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Reaction, Hooks } from "/server/api";
import { Packages, Accounts, Shops, Shipping, Cart, Orders } from "/lib/collections";
import { ShippoPackageConfig } from "../../lib/collections/schemas";
@@ -198,7 +200,7 @@ export const methods = {
// Make sure user has proper rights to this package
const { shopId } = Packages.findOne({ _id },
- { field: { shopId: 1 } });
+ { field: { shopId: 1 } });
if (shopId && Roles.userIsInRole(this.userId, shippingRoles, shopId)) {
// If user wants to delete existing key
if (modifier.hasOwnProperty("$unset")) {
diff --git a/imports/plugins/included/shippo/server/methods/shippoapi.js b/imports/plugins/included/shippo/server/methods/shippoapi.js
index 821db80314f..975aea8c514 100644
--- a/imports/plugins/included/shippo/server/methods/shippoapi.js
+++ b/imports/plugins/included/shippo/server/methods/shippoapi.js
@@ -1,6 +1,7 @@
/* eslint camelcase: 0 */
import Shippo from "shippo";
import { Meteor } from "meteor/meteor";
+import { ValidatedMethod } from "meteor/mdg:validated-method";
import { SimpleSchema } from "meteor/aldeed:simple-schema";
import { Logger } from "/server/api";
import { purchaseAddressSchema, parcelSchema } from "../lib/shippoApiSchema";
diff --git a/imports/plugins/included/sms/client/actions/settings.js b/imports/plugins/included/sms/client/actions/settings.js
index 80340ab0598..0258cb147c8 100644
--- a/imports/plugins/included/sms/client/actions/settings.js
+++ b/imports/plugins/included/sms/client/actions/settings.js
@@ -1,4 +1,5 @@
import Alert from "sweetalert2";
+import { Meteor } from "meteor/meteor";
import { i18next } from "/client/api";
export default {
@@ -33,7 +34,7 @@ export default {
Meteor.call("sms/saveSettings", settings, (err) => {
if (err) {
return Alert(i18next.t("app.error"),
- "Your API credentials could not be saved",
+ "Your API credentials could not be saved",
"error");
}
return Alert({
diff --git a/imports/plugins/included/sms/client/components/smsSettings.js b/imports/plugins/included/sms/client/components/smsSettings.js
index 880254d5556..3858a482395 100644
--- a/imports/plugins/included/sms/client/components/smsSettings.js
+++ b/imports/plugins/included/sms/client/components/smsSettings.js
@@ -1,4 +1,7 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Meteor } from "meteor/meteor";
+import { Reaction } from "/client/api";
import { Button, Card, CardHeader, CardBody, CardGroup, TextField, Select } from "/imports/plugins/core/ui/client/components";
class SmsSettings extends Component {
@@ -109,7 +112,7 @@ class SmsSettings extends Component {
type="submit" disabled={isSaving}
>
{isSaving ?
-
+
:
Save}
diff --git a/imports/plugins/included/sms/server/methods/sms.js b/imports/plugins/included/sms/server/methods/sms.js
index d477bc9169f..9125cfb8d1f 100644
--- a/imports/plugins/included/sms/server/methods/sms.js
+++ b/imports/plugins/included/sms/server/methods/sms.js
@@ -42,7 +42,7 @@ Meteor.methods({
check(userId, String);
check(shopId, String);
- const user = Accounts.findOne();
+ const user = Accounts.findOne({ _id: userId });
const addressBook = user.profile.addressBook;
let phone = false;
// check for addressBook phone
@@ -53,15 +53,14 @@ Meteor.methods({
}
if (phone) {
- const smsSettings = Sms.findOne();
+ const smsSettings = Sms.findOne({ shopId });
if (smsSettings) {
const { apiKey, apiToken, smsPhone, smsProvider } = smsSettings;
if (smsProvider === "twilio") {
Logger.debug("choose twilio");
const client = new Twilio(apiKey, apiToken);
-
- client.sendMessage({
+ client.messages.create({
to: phone,
from: smsPhone,
body: message
diff --git a/imports/plugins/included/social/client/components/facebook.js b/imports/plugins/included/social/client/components/facebook.js
index edeeac73747..3c053f2546f 100644
--- a/imports/plugins/included/social/client/components/facebook.js
+++ b/imports/plugins/included/social/client/components/facebook.js
@@ -1,6 +1,9 @@
-import React, { Component, PropTypes } from "react";
+/* global FB, data */
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import Helmet from "react-helmet";
import classnames from "classnames";
+import { $ } from "meteor/jquery";
import { Translation } from "/imports/plugins/core/ui/client/components";
export function getOpenGraphMeta(props) {
@@ -18,6 +21,7 @@ export function getOpenGraphMeta(props) {
if (props.media) {
+ let media;
if (!/^http(s?):\/\/+/.test(data.media)) {
media = location.origin + data.media;
}
diff --git a/imports/plugins/included/social/client/components/googleplus.js b/imports/plugins/included/social/client/components/googleplus.js
index d2477847d9d..c314f4ffaf7 100644
--- a/imports/plugins/included/social/client/components/googleplus.js
+++ b/imports/plugins/included/social/client/components/googleplus.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import Helmet from "react-helmet";
import classnames from "classnames";
import { Translation } from "/imports/plugins/core/ui/client/components";
diff --git a/imports/plugins/included/social/client/components/pinterest.js b/imports/plugins/included/social/client/components/pinterest.js
index 71635899a67..e6265165932 100644
--- a/imports/plugins/included/social/client/components/pinterest.js
+++ b/imports/plugins/included/social/client/components/pinterest.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import classnames from "classnames";
import { Translation } from "/imports/plugins/core/ui/client/components";
diff --git a/imports/plugins/included/social/client/components/settings.js b/imports/plugins/included/social/client/components/settings.js
index a31e9776571..ca66d1d49ac 100644
--- a/imports/plugins/included/social/client/components/settings.js
+++ b/imports/plugins/included/social/client/components/settings.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import {
CardGroup,
SettingsCard,
diff --git a/imports/plugins/included/social/client/components/socialButtons.js b/imports/plugins/included/social/client/components/socialButtons.js
index d43bdd19513..4a603276e3b 100644
--- a/imports/plugins/included/social/client/components/socialButtons.js
+++ b/imports/plugins/included/social/client/components/socialButtons.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import { Facebook, Twitter, GooglePlus, Pinterest } from "./";
export function getProviderComponentByName(providerName) {
@@ -18,7 +19,6 @@ export function getProviderComponentByName(providerName) {
class SocialButtons extends Component {
-
buttonSettngs(provider) {
return this.props.settings.apps[provider];
}
diff --git a/imports/plugins/included/social/client/components/twitter.js b/imports/plugins/included/social/client/components/twitter.js
index 23c0250fbc5..2bb6f95aa3a 100644
--- a/imports/plugins/included/social/client/components/twitter.js
+++ b/imports/plugins/included/social/client/components/twitter.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import Helmet from "react-helmet";
import classnames from "classnames";
import { Translation } from "/imports/plugins/core/ui/client/components";
diff --git a/imports/plugins/included/social/client/containers/socialSettingsContainer.js b/imports/plugins/included/social/client/containers/socialSettingsContainer.js
index de2439ac5cb..99a2878d136 100644
--- a/imports/plugins/included/social/client/containers/socialSettingsContainer.js
+++ b/imports/plugins/included/social/client/containers/socialSettingsContainer.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
import { isEqual } from "lodash";
import { Meteor } from "meteor/meteor";
import { composeWithTracker } from "/lib/api/compose";
diff --git a/imports/plugins/included/social/client/templates/dashboard/social.js b/imports/plugins/included/social/client/templates/dashboard/social.js
index 223e67ce081..f7098b97f20 100644
--- a/imports/plugins/included/social/client/templates/dashboard/social.js
+++ b/imports/plugins/included/social/client/templates/dashboard/social.js
@@ -1,3 +1,4 @@
+import { Template } from "meteor/templating";
import SocialSettingsContainer from "../../containers/socialSettingsContainer";
Template.socialSettings.helpers({
diff --git a/imports/plugins/included/social/client/templates/social.js b/imports/plugins/included/social/client/templates/social.js
index 61b5271b3a6..2174cc7c338 100644
--- a/imports/plugins/included/social/client/templates/social.js
+++ b/imports/plugins/included/social/client/templates/social.js
@@ -1,6 +1,7 @@
+import { merge } from "lodash";
+import { Template } from "meteor/templating";
import { Reaction } from "/client/api";
import { Packages } from "/lib/collections";
-import { merge } from "lodash";
Template.reactionSocial.onCreated(function () {
const self = this;
diff --git a/imports/plugins/included/social/register.js b/imports/plugins/included/social/register.js
index dd1733bf161..16e45577f60 100644
--- a/imports/plugins/included/social/register.js
+++ b/imports/plugins/included/social/register.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
import { Reaction } from "/server/api";
const DefaultSocialApp = {
diff --git a/imports/plugins/included/social/server/methods.js b/imports/plugins/included/social/server/methods.js
index f00ca374692..b7509f78bad 100644
--- a/imports/plugins/included/social/server/methods.js
+++ b/imports/plugins/included/social/server/methods.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";
import { Packages } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/imports/plugins/included/taxes-avalara/client/accounts/exemption.js b/imports/plugins/included/taxes-avalara/client/accounts/exemption.js
index ee40359fcb8..f42d246c2ef 100644
--- a/imports/plugins/included/taxes-avalara/client/accounts/exemption.js
+++ b/imports/plugins/included/taxes-avalara/client/accounts/exemption.js
@@ -1,6 +1,8 @@
import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { Template } from "meteor/templating";
+import { $ } from "meteor/jquery";
+import { AutoForm } from "meteor/aldeed:autoform";
import { Reaction, i18next } from "/client/api";
import { Packages, Accounts } from "/lib/collections";
import { Accounts as AccountsSchema } from "/lib/collections/schemas/accounts";
diff --git a/imports/plugins/included/taxes-avalara/client/settings/avagriddle.js b/imports/plugins/included/taxes-avalara/client/settings/avagriddle.js
index 82fc37e00d1..d96bdb3a5af 100644
--- a/imports/plugins/included/taxes-avalara/client/settings/avagriddle.js
+++ b/imports/plugins/included/taxes-avalara/client/settings/avagriddle.js
@@ -1,17 +1,20 @@
/* eslint react/prop-types:0, react/jsx-sort-props:0, react/forbid-prop-types: 0, "react/prefer-es6-class": [1, "never"] */
import _ from "lodash";
import React from "react";
+import createReactClass from "create-react-class";
+import PropTypes from "prop-types";
import moment from "moment";
import Griddle from "griddle-react";
+import { Meteor } from "meteor/meteor";
import { Counts } from "meteor/tmeasday:publish-counts";
import { ReactMeteorData } from "meteor/react-meteor-data";
-const LogGriddle = React.createClass({
+const LogGriddle = createReactClass({
propTypes: {
- collection: React.PropTypes.object,
- matchingResultsCount: React.PropTypes.string,
- publication: React.PropTypes.string,
- subscriptionParams: React.PropTypes.object
+ collection: PropTypes.object,
+ matchingResultsCount: PropTypes.string,
+ publication: PropTypes.string,
+ subscriptionParams: PropTypes.object
},
mixins: [ReactMeteorData],
diff --git a/imports/plugins/included/taxes-avalara/client/settings/avalara.js b/imports/plugins/included/taxes-avalara/client/settings/avalara.js
index 1325e5b072c..cc473cd30c4 100644
--- a/imports/plugins/included/taxes-avalara/client/settings/avalara.js
+++ b/imports/plugins/included/taxes-avalara/client/settings/avalara.js
@@ -1,4 +1,5 @@
import _ from "lodash";
+import { $ } from "meteor/jquery";
import { Template } from "meteor/templating";
import { ReactiveDict } from "meteor/reactive-dict";
import { Meteor } from "meteor/meteor";
@@ -8,8 +9,7 @@ import { Reaction, i18next } from "/client/api";
import { Packages, Logs } from "/lib/collections";
import { Logs as LogSchema } from "/lib/collections/schemas/logs";
import { AvalaraPackageConfig } from "../../lib/collections/schemas";
-import LogGriddle from "./avagriddle";
-import { Loading } from "/imports/plugins/core/ui/client/components";
+import { Loading, SortableTable } from "/imports/plugins/core/ui/client/components";
function getPackageData() {
@@ -65,7 +65,7 @@ Template.avalaraSettings.helpers({
},
logGrid() {
- const fields = ["date", "docType"];
+ const filteredFields = [ "data.request.data.date", "data.request.data.type"];
const noDataMessage = i18next.t("logGrid.noLogsFound");
const instance = Template.instance();
@@ -89,28 +89,29 @@ Template.avalaraSettings.helpers({
// add i18n handling to headers
const customColumnMetadata = [];
- fields.forEach(function (field) {
+ filteredFields.forEach(function (field) {
const columnMeta = {
- columnName: field,
- displayName: i18next.t(`logGrid.columns.${field}`)
+ accessor: field,
+ Header: i18next.t(`logGrid.columns.${field}`)
};
customColumnMetadata.push(columnMeta);
});
// return template Grid
return {
- component: LogGriddle,
+ component: SortableTable,
publication: "Logs",
collection: Logs,
matchingResultsCount: "logs-count",
- useGriddleStyles: false,
+ showFilter: true,
rowMetadata: customRowMetaData,
- columns: fields,
+ filteredFields: filteredFields,
+ columns: filteredFields,
noDataMessage: noDataMessage,
onRowClick: editRow,
columnMetadata: customColumnMetadata,
externalLoadingComponent: Loading,
- subscriptionParams: { logType: "avalara" }
+ query: { logType: "avalara" }
};
},
diff --git a/imports/plugins/included/taxes-avalara/server/hooks/hooks.js b/imports/plugins/included/taxes-avalara/server/hooks/hooks.js
index b081e7eb69a..e82e0bcee91 100644
--- a/imports/plugins/included/taxes-avalara/server/hooks/hooks.js
+++ b/imports/plugins/included/taxes-avalara/server/hooks/hooks.js
@@ -25,7 +25,7 @@ MethodHooks.after("taxes/calculate", (options) => {
const pkg = taxCalc.getPackageData();
Logger.debug("Avalara triggered on taxes/calculate for cartId:", cartId);
- // console.log(pkg, "package in hook");
+
if (pkg && pkg.settings.avalara.enabled && pkg.settings.avalara.performTaxCalculation) {
taxCalc.estimateCart(cartToCalc, function (result) {
// we don't use totalTax, that just tells us we have a valid tax calculation
diff --git a/imports/plugins/included/taxes-avalara/server/i18n/en.json b/imports/plugins/included/taxes-avalara/server/i18n/en.json
index a067b584ee4..8bc6e4bea5f 100644
--- a/imports/plugins/included/taxes-avalara/server/i18n/en.json
+++ b/imports/plugins/included/taxes-avalara/server/i18n/en.json
@@ -26,6 +26,14 @@
"logGrid": {
"noLogsFound": "No logs found",
"columns": {
+ "data": {
+ "request": {
+ "data": {
+ "date": "Date",
+ "type": "Document Type"
+ }
+ }
+ },
"date": "Date",
"docType": "Document Type"
}
diff --git a/imports/plugins/included/taxes-avalara/server/jobs/cleanup.js b/imports/plugins/included/taxes-avalara/server/jobs/cleanup.js
index 86d7ccb5fd2..2bbb804b7e3 100644
--- a/imports/plugins/included/taxes-avalara/server/jobs/cleanup.js
+++ b/imports/plugins/included/taxes-avalara/server/jobs/cleanup.js
@@ -1,5 +1,6 @@
import moment from "moment";
import { Meteor } from "meteor/meteor";
+import { Job } from "meteor/vsivsi:job-collection";
import { Jobs, Logs } from "/lib/collections";
import { Hooks, Logger } from "/server/api";
import taxCalc from "../methods/taxCalc";
diff --git a/imports/plugins/included/taxes-avalara/server/methods/avalogger.js b/imports/plugins/included/taxes-avalara/server/methods/avalogger.js
index 982f831a5d3..fbdd7b3d848 100644
--- a/imports/plugins/included/taxes-avalara/server/methods/avalogger.js
+++ b/imports/plugins/included/taxes-avalara/server/methods/avalogger.js
@@ -1,11 +1,11 @@
import bunyan from "bunyan";
+import { Meteor } from "meteor/meteor";
import { Logs } from "/lib/collections";
import { Reaction } from "/server/api";
const level = "INFO";
class BunyanMongo {
-
levelToName = {
10: "trace",
20: "debug",
diff --git a/imports/plugins/included/taxes-taxcloud/server/jobs/taxcodes.js b/imports/plugins/included/taxes-taxcloud/server/jobs/taxcodes.js
index 700da632eb7..090d53f5504 100644
--- a/imports/plugins/included/taxes-taxcloud/server/jobs/taxcodes.js
+++ b/imports/plugins/included/taxes-taxcloud/server/jobs/taxcodes.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { Job } from "meteor/vsivsi:job-collection";
import { Jobs, Packages } from "/lib/collections";
import { Hooks, Logger, Reaction } from "/server/api";
diff --git a/imports/plugins/included/ui-search/client/index.js b/imports/plugins/included/ui-search/client/index.js
index 7c9d096a82e..76b8dcf86c4 100644
--- a/imports/plugins/included/ui-search/client/index.js
+++ b/imports/plugins/included/ui-search/client/index.js
@@ -1,25 +1,3 @@
// Search Modal
import "./templates/searchModal/searchModal.html";
import "./templates/searchModal/searchModal.js";
-import "./templates/searchModal/searchInput.html";
-import "./templates/searchModal/searchTypeToggle.html";
-import "./templates/searchModal/searchResults.html";
-
-// Product Search
-import "./templates/productSearch/productResults.html";
-import "./templates/productSearch/productResults.js";
-import "./templates/productSearch/productSearchTags.html";
-import "./templates/productSearch/productItem.html";
-import "./templates/productSearch/productItem.js";
-import "./templates/productSearch/content.html";
-import "./templates/productSearch/content.js";
-import "./templates/productSearch/notice.html";
-import "./templates/productSearch/notice.js";
-
-// Order Search
-import "./templates/orderSearch/orderResults.html";
-import "./templates/orderSearch/orderResults.js";
-
-// Account Search
-import "./templates/accountSearch/accountResults.html";
-import "./templates/accountSearch/accountResults.js";
diff --git a/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.html b/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.html
deleted file mode 100644
index 95c8c28d5b7..00000000000
--- a/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
- {{> React accountTable}}
-
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.js b/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.js
deleted file mode 100644
index 2dfd9d23b72..00000000000
--- a/imports/plugins/included/ui-search/client/templates/accountSearch/accountResults.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import React from "react";
-import { DataType } from "react-taco-table";
-import { Template } from "meteor/templating";
-import { Reaction, i18next } from "/client/api";
-import { SortableTable } from "/imports/plugins/core/ui/client/components";
-
-function userPermissions(userId) {
- if (Reaction.hasPermission("reaction-accounts")) {
- const shopId = Reaction.getShopId();
- const user = Meteor.users.findOne(userId);
- const member = {};
-
- member.userId = user._id;
-
- if (user.emails && user.emails.length) {
- // this is some kind of denormalization. It is helpful to have both
- // of this string and array. Array goes to avatar, string goes to
- // template
- member.emails = user.emails;
- member.email = user.emails[0].address;
- }
- // member.user = user;
- member.username = user.username;
- member.isAdmin = Roles.userIsInRole(user._id, "admin", shopId);
- member.roles = user.roles;
- member.services = user.services;
-
- if (Roles.userIsInRole(member.userId, "owner", shopId)) {
- member.role = "owner";
- } else if (Roles.userIsInRole(member.userId, "admin", shopId)) {
- member.role = "admin";
- } else if (Roles.userIsInRole(member.userId, "dashboard", shopId)) {
- member.role = "dashboard";
- } else if (Roles.userIsInRole(member.userId, "guest", shopId)) {
- member.role = "guest";
- }
-
- return member;
- }
-}
-
-
-Template.searchModal.onCreated(function () {
- this.autorun(() => {
- this.subscribe("ShopMembers");
- });
-});
-
-/**
- * accountSearch helpers
- */
-Template.searchModal.helpers({
- accountSearchResults() {
- const instance = Template.instance();
- const results = instance.state.get("accountSearchResults");
- return results;
- },
- accountTable() {
- const instance = Template.instance();
- const results = instance.state.get("accountSearchResults");
-
- const columns = [
- {
- id: "_id",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.accountId", { defaultValue: "Account ID" }),
- value: rowData => {
- return rowData._id;
- }
- },
- {
- id: "shopId",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.shopId", { defaultValue: "Shop ID" }),
- value: rowData => {
- return rowData.shopId;
- }
- },
- {
- id: "firstName",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.firstName", { defaultValue: "First Name" }),
- value: rowData => {
- if (rowData.profile) {
- return rowData.profile.firstName;
- }
- return undefined;
- }
- },
- {
- id: "lastName",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.lastName", { defaultValue: "Last Name" }),
- value: rowData => {
- if (rowData.profile) {
- return rowData.profile.lastName;
- }
- return undefined;
- }
- },
- {
- id: "phone",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.phone", { defaultValue: "Phone" }),
- value: rowData => {
- if (rowData.profile) {
- return rowData.profile.phone;
- }
- return undefined;
- }
- },
- {
- id: "email",
- type: DataType.String,
- header: i18next.t("search.accountSearchResults.emails", { defaultValue: "Email" }),
- value: rowData => {
- return rowData.emails[0];
- }
- },
- {
- id: "manageAccount",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingStatus", { defaultValue: "Shipping Status" }),
- value: rowData => {
- return rowData.emails[0];
- },
- tdClassName: "account-manage",
- renderer(cellData, { rowData }) {
- return
Manage;
- }
- }
- ];
-
- return {
- component: SortableTable,
- data: results,
- columns: columns
- };
- }
-});
-
-
-/**
- * orderResults events
- */
-Template.searchModal.events({
- "click [data-event-action=manageAccount]": function (event) {
- const instance = Template.instance();
- const view = instance.view;
-
- const userId = $(event.target).data("event-data");
-
- Reaction.showActionView({
- label: "Permissions",
- i18nKeyLabel: "admin.settings.permissionsSettingsLabel",
- data: userPermissions(userId),
- template: "memberSettings"
- });
-
- Reaction.Router.go("dashboard/accounts", {}, {});
-
- $(".js-search-modal").delay(400).fadeOut(400, () => {
- Blaze.remove(view);
- });
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.html b/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.html
deleted file mode 100644
index 89dab3115f3..00000000000
--- a/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
- {{> React orderTable}}
-
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.js b/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.js
deleted file mode 100644
index 3b9fbdbe280..00000000000
--- a/imports/plugins/included/ui-search/client/templates/orderSearch/orderResults.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import _ from "lodash";
-import React from "react";
-import { DataType } from "react-taco-table";
-import { Template } from "meteor/templating";
-import { Reaction, i18next } from "/client/api";
-import { SortableTable } from "/imports/plugins/core/ui/client/components";
-
-
-/**
- * orderResults helpers
- */
-Template.searchModal.helpers({
- orderSearchResults() {
- const instance = Template.instance();
- const results = instance.state.get("orderSearchResults");
- return results;
- },
- orderTable() {
- const instance = Template.instance();
- const results = instance.state.get("orderSearchResults");
- // const route = rowData.url;
- const columns = [
- {
- id: "_id",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.orderId", { defaultValue: "Order ID" }),
- renderer(cellData) { // eslint-disable-line react/no-multi-comp
- return
{cellData};
- }
- },
- {
- id: "shippingName",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingName", { defaultValue: "Name" }),
- value: rowData => {
- return rowData.shippingName;
- }
- },
- {
- id: "userEmail",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.userEmails", { defaultValue: "Email" }),
- value: rowData => {
- return rowData.userEmails[0];
- }
- },
- {
- id: "shippingAddress",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingAddress", { defaultValue: "Address" }),
- value: rowData => {
- return rowData.shippingAddress.address;
- }
- },
- {
- id: "shippingCity",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingCity", { defaultValue: "City" }),
- value: rowData => {
- return rowData.shippingAddress.city;
- }
- },
- {
- id: "shippingRegion",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingRegion", { defaultValue: "Region" }),
- value: rowData => {
- return rowData.shippingAddress.region;
- }
- },
- {
- id: "shippingCountry",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingCountry", { defaultValue: "Country" }),
- value: rowData => {
- return rowData.shippingAddress.country;
- }
- },
- {
- id: "shippingPhone",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingPhone", { defaultValue: "Phone" }),
- value: rowData => {
- return rowData.shippingPhone;
- }
- },
- {
- id: "shippingStatus",
- type: DataType.String,
- header: i18next.t("search.orderSearchResults.shippingStatus", { defaultValue: "Shipping Status" }),
- value: rowData => {
- return rowData.shippingStatus;
- },
- tdClassName: "shipping-status",
- renderer(cellData, { rowData }) { // eslint-disable-line react/no-multi-comp
- const rowClassName = _.lowerCase(rowData.shippingStatus);
- return
{cellData};
- }
- },
- {
- id: "orderDate",
- type: DataType.Date,
- header: i18next.t("search.orderSearchResults.orderDate", { defaultValue: "Date" }),
- value: rowData => {
- return rowData.orderDate;
- }
- },
- {
- id: "orderTotal",
- type: DataType.Number,
- header: i18next.t("search.orderSearchResults.orderTotal", { defaultValue: "Total" }),
- value: rowData => {
- return rowData.orderTotal;
- }
- }
- ];
-
- return {
- component: SortableTable,
- data: results,
- columns: columns
- };
- }
-});
-
-
-/**
- * orderResults events
- */
-Template.searchModal.events({
- "click [data-event-action=goToOrder]": function (event) {
- const instance = Template.instance();
- const view = instance.view;
- const isActionViewOpen = Reaction.isActionViewOpen();
- const orderId = $(event.target).data("event-data");
-
- // toggle detail views
- if (isActionViewOpen === false) {
- Reaction.showActionView({
- label: "Order Details",
- i18nKeyLabel: "orderWorkflow.orderDetails",
- data: instance.data.order,
- props: {
- size: "large"
- },
- template: "coreOrderWorkflow"
- });
- }
-
- Reaction.Router.go("dashboard/orders", {}, {
- _id: orderId
- });
-
- $(".js-search-modal").delay(400).fadeOut(400, () => {
- Blaze.remove(view);
- });
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/content.html b/imports/plugins/included/ui-search/client/templates/productSearch/content.html
deleted file mode 100644
index 3b091da1504..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/content.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/content.js b/imports/plugins/included/ui-search/client/templates/productSearch/content.js
deleted file mode 100644
index aadd7348f7e..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/content.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * gridContent helpers
- */
-
-Template.searchGridContent.inheritsHelpersFrom("gridContent");
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/controls.html b/imports/plugins/included/ui-search/client/templates/productSearch/controls.html
deleted file mode 100644
index 9d12577b521..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/controls.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
- {{#if hasPermission "createProduct"}}
-
-
-
-
- {{> React VisibilityButton}}
-
-
-
- {{> React EditButton}}
-
-
-
- {{/if}}
-
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/controls.js b/imports/plugins/included/ui-search/client/templates/productSearch/controls.js
deleted file mode 100644
index 8632f676091..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/controls.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Session } from "meteor/session";
-import { Template } from "meteor/templating";
-import { ReactiveDict } from "meteor/reactive-dict";
-import { IconButton } from "/imports/plugins/core/ui/client/components";
-
-Template.searchGridControls.onCreated(function () {
- this.state = new ReactiveDict();
-
- this.autorun(() => {
- const selectedProducts = Session.get("productGrid/selectedProducts");
- const isSelected = _.isArray(selectedProducts) ? selectedProducts.indexOf(this.data.product._id) >= 0 : false;
-
- this.state.set("isSelected", isSelected);
- });
-});
-
-Template.searchGridControls.onRendered(function () {
- return this.$("[data-toggle='tooltip']").tooltip({
- position: "top"
- });
-});
-
-Template.searchGridControls.helpers({
- EditButton() {
- const instance = Template.instance();
- const isSelected = instance.state.equals("isSelected", true);
-
- return {
- component: IconButton,
- icon: "fa fa-pencil",
- onIcon: "fa fa-check",
- status: isSelected ? "active" : "default",
- toggle: true,
- toggleOn: isSelected,
- onClick() {
- if (instance.data.onEditButtonClick) {
- instance.data.onEditButtonClick();
- }
- }
- };
- },
-
- VisibilityButton() {
- const instance = Template.instance();
-
- return {
- component: IconButton,
- icon: "fa fa-eye-slash",
- onIcon: "fa fa-eye",
- toggle: true,
- toggleOn: instance.data.product.isVisible,
- onClick() {
- if (instance.data.onPublishButtonClick) {
- instance.data.onPublishButtonClick();
- }
- }
- };
- },
-
- checked: function () {
- return Template.instance().state.equals("isSelected", true);
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/notice.html b/imports/plugins/included/ui-search/client/templates/productSearch/notice.html
deleted file mode 100644
index 679bc9e2083..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/notice.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
- {{#if isSoldOut}}
- {{#if isBackorder}}
- Backorder
- {{else}}
- Sold Out!
- {{/if}}
- {{else}}
- {{#if isLowQuantity}}
- Limited Supply
- {{/if}}
- {{/if}}
-
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/notice.js b/imports/plugins/included/ui-search/client/templates/productSearch/notice.js
deleted file mode 100644
index a824c72972d..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/notice.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * searchGridNotice helpers
- */
-Template.searchGridNotice.helpers({
- isLowQuantity: function () {
- return this.product.isLowQuantity;
- },
- isSoldOut: function () {
- return this.product.isSoldOut;
- },
- isBackorder: function () {
- return this.product.isBackorder;
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/productItem.html b/imports/plugins/included/ui-search/client/templates/productSearch/productItem.html
deleted file mode 100644
index 36730fbcfee..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/productItem.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- {{> searchGridNotice}}
-
- {{> inlineAlerts placement="productGridItem" id=product._id}}
-
-
-
-
-
- {{#with media}}
-
- {{else}}
-
- {{/with}}
-
-
-
-
- {{> searchGridContent product=product}}
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/productItem.js b/imports/plugins/included/ui-search/client/templates/productSearch/productItem.js
deleted file mode 100644
index 435cf8f24f6..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/productItem.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import _ from "lodash";
-import { Meteor } from "meteor/meteor";
-import { Session } from "meteor/session";
-import { Template } from "meteor/templating";
-import { Tracker } from "meteor/tracker";
-import { $ } from "meteor/jquery";
-import Logger from "/client/modules/logger";
-import { ReactionProduct } from "/lib/api";
-import { Media, Products } from "/lib/collections";
-import { Reaction } from "/client/api";
-
-/**
- * productGridItems helpers
- */
-
-Template.productItem.helpers({
- controlProps() {
- const instance = Template.instance();
-
- return {
- product: instance.data,
- onEditButtonClick() {
- const data = instance.data.product;
- const $checkbox = instance.$(`input[type=checkbox][value=${data._id}]`);
-
- Session.set("productGrid/selectedProducts", []);
- $checkbox.prop("checked", true).trigger("change");
- },
- onPublishButtonClick() {
- ReactionProduct.publishProduct(instance.data);
- }
- };
- },
- media: function () {
- const media = Media.findOne({
- "metadata.productId": this.product._id,
- "metadata.priority": 0,
- "metadata.toGrid": 1
- }, { sort: { uploadedAt: 1 } });
-
- return media instanceof FS.File ? media : false;
- },
- additionalMedia: function () {
- const mediaArray = Media.find({
- "metadata.productId": this._id,
- "metadata.priority": {
- $gt: 0
- },
- "metadata.toGrid": 1
- }, { limit: 3 });
-
- if (mediaArray.count() > 1) {
- return mediaArray;
- }
-
- return false;
- },
- weightClass: function () {
- const tag = ReactionProduct.getTag();
- const positions = this.positions && this.positions[tag] || {};
- const weight = positions.weight || 0;
- switch (weight) {
- case 1:
- return "product-medium";
- case 2:
- return "product-large";
- default:
- return "product-small";
- }
- },
- isSelected: function () {
- return _.includes(Session.get("productGrid/selectedProducts"), this._id) ? "active" : "";
- },
- isMediumWeight: function () {
- const tag = ReactionProduct.getTag();
- const positions = this.positions && this.positions[tag] || {};
- const weight = positions.weight || 0;
-
- return weight === 1;
- },
- isLargeWeight: function () {
- const tag = ReactionProduct.getTag();
- const positions = this.positions && this.positions[tag] || {};
- const weight = positions.weight || 0;
-
- return weight === 3;
- },
- shouldShowAdditionalImages: function () {
- if (this.isMediumWeight && this.mediaArray) {
- return true;
- }
- return false;
- },
- // this is needed to get `pinned` from the item template
- positions() {
- const tag = ReactionProduct.getTag();
- return this.positions && this.positions[tag] || {};
- }
-});
-
-/**
- * productGridItems events
- */
-
-Template.productItem.events({
- "click [data-event-action=productClick]": function (event) {
- event.preventDefault();
- const instance = Template.instance();
- const view = instance.view;
- const product = Products.findOne(event.currentTarget.dataset.eventValue);
-
- let handle = product.handle;
- if (product.__published) {
- handle = product.__published.handle;
- }
-
- Reaction.Router.go("product", {
- handle: handle
- });
-
- $(".js-search-modal").delay(400).fadeOut(400, () => {
- $("body").css("overflow-y", "inherit");
- Blaze.remove(view);
- });
- },
- "click [data-event-action=selectSingleProduct]": function (event, template) {
- event.preventDefault();
- const { data } = Template.instance();
-
- const $checkbox = template.$(`input[type=checkbox][value=${data._id}]`);
-
- Session.set("productGrid/selectedProducts", []);
- $checkbox.prop("checked", true).trigger("change");
- },
- "click .publish-product"(event, instance) {
- ReactionProduct.publishProduct(instance.data);
- },
- "click .delete-product": function (event) {
- event.preventDefault();
- ReactionProduct.archiveProduct(this);
- },
- "click .update-product-weight": function (event) {
- event.preventDefault();
-
- const tag = ReactionProduct.getTag();
- const positions = this.positions && this.positions[tag] || {};
- let weight = positions.weight || 0;
-
- if (weight < 2) {
- weight++;
- } else {
- weight = 0;
- }
-
- const position = {
- weight: weight,
- updatedAt: new Date()
- };
- Meteor.call("products/updateProductPosition", this._id, position, tag, error => {
- if (error) {
- Logger.warn(error);
- throw new Meteor.Error(403, error);
- }
- });
- return Tracker.flush();
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/productResults.html b/imports/plugins/included/ui-search/client/templates/productSearch/productResults.html
deleted file mode 100644
index ed19eb00550..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/productResults.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/productResults.js b/imports/plugins/included/ui-search/client/templates/productSearch/productResults.js
deleted file mode 100644
index 22c3d99d0d2..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/productResults.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import _ from "lodash";
-import { Session } from "meteor/session";
-import { Template } from "meteor/templating";
-import { Reaction } from "/client/api";
-import Logger from "/client/modules/logger";
-import { ReactionProduct } from "/lib/api";
-import Sortable from "sortablejs";
-
-/**
- * productGrid helpers
- */
-
-Template.productResults.onCreated(function () {
- Session.set("productGrid/selectedProducts", []);
-});
-
-Template.productResults.onRendered(function () {
- const instance = this;
-
- if (Reaction.hasPermission("createProduct")) {
- const productSort = $(".product-grid-list")[0];
-
- this.sortable = Sortable.create(productSort, {
- group: "products",
- handle: ".product-grid-item",
- onUpdate() {
- const tag = ReactionProduct.getTag();
-
- instance.$(".product-grid-item")
- .toArray()
- .map((element, index) => {
- const productId = element.getAttribute("id");
- const position = {
- position: index,
- updatedAt: new Date()
- };
-
- Meteor.call("products/updateProductPosition", productId, position, tag,
- error => {
- if (error) {
- Logger.warn(error);
- throw new Meteor.Error(403, error);
- }
- });
- });
-
- Tracker.flush();
- }
- });
- }
-});
-
-Template.productResults.events({
- "click [data-event-action=loadMoreProducts]": (event) => {
- event.preventDefault();
- loadMoreProducts();
- },
- "change input[name=selectProduct]": (event) => {
- let selectedProducts = Session.get("productGrid/selectedProducts");
-
- if (event.target.checked) {
- selectedProducts.push(event.target.value);
- } else {
- selectedProducts = _.without(selectedProducts, event.target.value);
- }
-
- Session.set("productGrid/selectedProducts", _.uniq(selectedProducts));
-
- const productCursor = Template.currentData().products;
-
- if (productCursor) {
- const products = productCursor.fetch();
-
- const filteredProducts = _.filter(products, (product) => {
- return _.includes(selectedProducts, product._id);
- });
-
- Reaction.showActionView({
- label: "Product Settings",
- i18nKeyLabel: "productDetailEdit.productSettings",
- template: "productSettings",
- type: "product",
- data: {
- products: filteredProducts
- }
- });
- }
- }
-});
-
-Template.productResults.helpers({
- loadMoreProducts() {
- return Template.instance().state.equals("canLoadMoreProducts", true);
- },
- products() {
- return Template.currentData().products;
- }
-});
diff --git a/imports/plugins/included/ui-search/client/templates/productSearch/productSearchTags.html b/imports/plugins/included/ui-search/client/templates/productSearch/productSearchTags.html
deleted file mode 100644
index 17651214ebb..00000000000
--- a/imports/plugins/included/ui-search/client/templates/productSearch/productSearchTags.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- {{#if tagSearchResults}}
-
- {{/if}}
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/searchModal/searchInput.html b/imports/plugins/included/ui-search/client/templates/searchModal/searchInput.html
deleted file mode 100644
index 0915f191579..00000000000
--- a/imports/plugins/included/ui-search/client/templates/searchModal/searchInput.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
- Clear
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.html b/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.html
index 836b9dbf98f..43cd369e164 100644
--- a/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.html
+++ b/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.html
@@ -1,11 +1,5 @@
-
-
{{> React IconButtonComponent}}
-
- {{> searchResults productSearchResults=productSearchResults orderSearchResults=orderSearchResults accountSearchResults=accountSearchResults orderTable=orderTable accountTable=accountTable }}
+
+ {{> React searchModal }}
diff --git a/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.js b/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.js
index 1f054f6ad5c..ab217030885 100644
--- a/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.js
+++ b/imports/plugins/included/ui-search/client/templates/searchModal/searchModal.js
@@ -1,209 +1,28 @@
-import _ from "lodash";
+import { $ } from "meteor/jquery";
import { Template } from "meteor/templating";
-import { ProductSearch, Tags, OrderSearch, AccountSearch } from "/lib/collections";
-import { IconButton } from "/imports/plugins/core/ui/client/components";
-
-/*
- * searchModal extra functions
- */
-function tagToggle(arr, val) {
- if (arr.length === _.pull(arr, val).length) {
- arr.push(val);
- }
- return arr;
-}
-
-/*
- * searchModal onCreated
- */
-Template.searchModal.onCreated(function () {
- this.state = new ReactiveDict();
- this.state.setDefault({
- initialLoad: true,
- slug: "",
- canLoadMoreProducts: false,
- searchQuery: "",
- productSearchResults: [],
- tagSearchResults: []
- });
-
-
- // Allow modal to be closed by clicking ESC
- // Must be done in Template.searchModal.onCreated and not in Template.searchModal.events
- $(document).on("keyup", (event) => {
- if (event.keyCode === 27) {
- const view = this.view;
- $(".js-search-modal").fadeOut(400, () => {
- $("body").css("overflow", "visible");
- Blaze.remove(view);
- });
- }
- });
-
-
- this.autorun(() => {
- const searchCollection = this.state.get("searchCollection") || "products";
- const searchQuery = this.state.get("searchQuery");
- const facets = this.state.get("facets") || [];
- const sub = this.subscribe("SearchResults", searchCollection, searchQuery, facets);
-
- if (sub.ready()) {
- /*
- * Product Search
- */
- if (searchCollection === "products") {
- const productResults = ProductSearch.find().fetch();
- const productResultsCount = productResults.length;
- this.state.set("productSearchResults", productResults);
- this.state.set("productSearchCount", productResultsCount);
-
- const hashtags = [];
- for (const product of productResults) {
- if (product.hashtags) {
- for (const hashtag of product.hashtags) {
- if (!_.includes(hashtags, hashtag)) {
- hashtags.push(hashtag);
- }
- }
- }
- }
- const tagResults = Tags.find({
- _id: { $in: hashtags }
- }).fetch();
- this.state.set("tagSearchResults", tagResults);
-
- // TODO: Do we need this?
- this.state.set("accountSearchResults", "");
- this.state.set("orderSearchResults", "");
- }
-
- /*
- * Account Search
- */
- if (searchCollection === "accounts") {
- const accountResults = AccountSearch.find().fetch();
- const accountResultsCount = accountResults.length;
- this.state.set("accountSearchResults", accountResults);
- this.state.set("accountSearchCount", accountResultsCount);
-
- // TODO: Do we need this?
- this.state.set("orderSearchResults", "");
- this.state.set("productSearchResults", "");
- this.state.set("tagSearchResults", "");
- }
-
- /*
- * Order Search
- */
- if (searchCollection === "orders") {
- const orderResults = OrderSearch.find().fetch();
- const orderResultsCount = orderResults.length;
- this.state.set("orderSearchResults", orderResults);
- this.state.set("orderSearchCount", orderResultsCount);
-
-
- // TODO: Do we need this?
- this.state.set("accountSearchResults", "");
- this.state.set("productSearchResults", "");
- this.state.set("tagSearchResults", "");
- }
- }
- });
-});
-
+import SearchModalContainer from "../../../lib/containers/searchModalContainer";
/*
* searchModal helpers
*/
Template.searchModal.helpers({
- IconButtonComponent() {
- const instance = Template.instance();
- const view = instance.view;
-
+ searchModal() {
return {
- component: IconButton,
- icon: "fa fa-times",
- onClick() {
- $(".js-search-modal").fadeOut(400, () => {
- $("body").css("overflow", "visible");
- Blaze.remove(view);
- });
- }
+ component: SearchModalContainer
};
- },
- productSearchResults() {
- const instance = Template.instance();
- const results = instance.state.get("productSearchResults");
- return results;
- },
- tagSearchResults() {
- const instance = Template.instance();
- const results = instance.state.get("tagSearchResults");
- return results;
- },
- showSearchResults() {
- return false;
}
});
-
/*
* searchModal events
*/
Template.searchModal.events({
- // on type, reload Reaction.SaerchResults
- "keyup input": (event, templateInstance) => {
+ "click [data-event-action=searchCollection]": function (event) {
event.preventDefault();
- const searchQuery = templateInstance.find("#search-input").value;
- templateInstance.state.set("searchQuery", searchQuery);
- $(".search-modal-header:not(.active-search)").addClass(".active-search");
- if (!$(".search-modal-header").hasClass("active-search")) {
- $(".search-modal-header").addClass("active-search");
- }
- },
- "click [data-event-action=filter]": function (event, templateInstance) {
- event.preventDefault();
- const instance = Template.instance();
- const facets = instance.state.get("facets") || [];
- const newFacet = $(event.target).data("event-value");
-
- tagToggle(facets, newFacet);
-
- $(event.target).toggleClass("active-tag btn-active");
-
- templateInstance.state.set("facets", facets);
- },
- "click [data-event-action=productClick]": function () {
- const instance = Template.instance();
- const view = instance.view;
- $(".js-search-modal").delay(400).fadeOut(400, () => {
- Blaze.remove(view);
- });
- },
- "click [data-event-action=clearSearch]": function (event, templateInstance) {
- $("#search-input").val("");
- $("#search-input").focus();
- const searchQuery = templateInstance.find("#search-input").value;
- templateInstance.state.set("searchQuery", searchQuery);
- },
- "click [data-event-action=searchCollection]": function (event, templateInstance) {
- event.preventDefault();
- const searchCollection = $(event.target).data("event-value");
$(".search-type-option").not(event.target).removeClass("search-type-active");
$(event.target).addClass("search-type-active");
$("#search-input").focus();
-
- templateInstance.state.set("searchCollection", searchCollection);
}
});
-
-
-/*
- * searchModal onDestroyed
- */
-Template.searchModal.onDestroyed(() => {
- // Kill Allow modal to be closed by clicking ESC, which was initiated in Template.searchModal.onCreated
- $(document).off("keyup");
-});
diff --git a/imports/plugins/included/ui-search/client/templates/searchModal/searchResults.html b/imports/plugins/included/ui-search/client/templates/searchModal/searchResults.html
deleted file mode 100644
index 15515e2ee12..00000000000
--- a/imports/plugins/included/ui-search/client/templates/searchModal/searchResults.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
- {{#if productSearchResults}}
- {{> productResults productSearchResults=productSearchResults}}
- {{/if}}
-
- {{#if orderSearchResults}}
- {{> orderResults orderSearchResults=orderSearchResults orderTable=orderTable}}
- {{/if}}
-
- {{#if accountSearchResults}}
- {{> accountResults accountSearchResults=accountSearchResults accountTable=accountTable}}
- {{/if}}
-
-
-
-
diff --git a/imports/plugins/included/ui-search/client/templates/searchModal/searchTypeToggle.html b/imports/plugins/included/ui-search/client/templates/searchModal/searchTypeToggle.html
deleted file mode 100644
index 40be9864a72..00000000000
--- a/imports/plugins/included/ui-search/client/templates/searchModal/searchTypeToggle.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
- {{#if hasPermission "admin"}}
-
-
Products
- {{#if hasPermission "accounts"}}
-
Accounts
- {{/if}}
- {{#if hasPermission "orders"}}
-
Orders
- {{/if}}
-
- {{/if}}
-
diff --git a/imports/plugins/included/ui-search/lib/components/searchModal.js b/imports/plugins/included/ui-search/lib/components/searchModal.js
new file mode 100644
index 00000000000..48c207e6dec
--- /dev/null
+++ b/imports/plugins/included/ui-search/lib/components/searchModal.js
@@ -0,0 +1,131 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Reaction } from "/client/api";
+import { TextField, Button, IconButton, SortableTableLegacy } from "@reactioncommerce/reaction-ui";
+import ProductGridContainer from "/imports/plugins/included/product-variant/containers/productGridContainer";
+import { accountsTable, ordersTable } from "../helpers";
+
+class SearchModal extends Component {
+ static propTypes = {
+ accounts: PropTypes.array,
+ handleAccountClick: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleClick: PropTypes.func,
+ handleOrderClick: PropTypes.func,
+ handleTagClick: PropTypes.func,
+ handleToggle: PropTypes.func,
+ orders: PropTypes.array,
+ products: PropTypes.array,
+ siteName: PropTypes.string,
+ tags: PropTypes.array,
+ unmountMe: PropTypes.func,
+ value: PropTypes.string
+ }
+
+ renderSearchInput() {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ renderSearchTypeToggle() {
+ if (Reaction.hasPermission("admin")) {
+ return (
+
+
this.props.handleToggle("products")}
+ >
+ Products
+
+ {Reaction.hasPermission("accounts") &&
+
this.props.handleToggle("accounts")}
+ >
+ Accounts
+
+ }
+ {Reaction.hasPermission("orders") &&
+
this.props.handleToggle("orders")}
+ >
+ Orders
+
+ }
+
+ );
+ }
+ }
+
+ renderProductSearchTags() {
+ return (
+
+
Suggested tags
+
+ {this.props.tags.map((tag) => (
+ this.props.handleTagClick(tag._id)}>{tag.name}
+ ))}
+
+
+ );
+ }
+
+ render() {
+ return (
+
+
+
+ {this.renderSearchInput()}
+ {this.renderSearchTypeToggle()}
+ {this.props.tags.length > 0 && this.renderProductSearchTags()}
+
+
+ {this.props.products.length > 0 &&
}
+ {this.props.accounts.length > 0 &&
+
+ }
+ {this.props.orders.length > 0 &&
+
+ }
+
+
+ );
+ }
+}
+
+export default SearchModal;
diff --git a/imports/plugins/included/ui-search/lib/containers/searchModalContainer.js b/imports/plugins/included/ui-search/lib/containers/searchModalContainer.js
new file mode 100644
index 00000000000..8fd5239da33
--- /dev/null
+++ b/imports/plugins/included/ui-search/lib/containers/searchModalContainer.js
@@ -0,0 +1,280 @@
+import React, { Component } from "react";
+import { Meteor } from "meteor/meteor";
+import { Tracker } from "meteor/tracker";
+import { Roles } from "meteor/alanning:roles";
+import _ from "lodash";
+import { Reaction } from "/client/api";
+import { composeWithTracker } from "/lib/api/compose";
+import * as Collections from "/lib/collections";
+import SearchModal from "../components/searchModal";
+
+class SearchModalContainer extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ collection: "products",
+ value: localStorage.getItem("searchValue") || "",
+ tagResults: [],
+ productResults: [],
+ orderResults: [],
+ renderChild: true,
+ accountResults: [],
+ facets: []
+ };
+ this.handleClick = this.handleClick.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleChildUnmount = this.handleChildUnmount.bind(this);
+ this.handleAccountClick = this.handleAccountClick.bind(this);
+ this.handleOrderClick = this.handleOrderClick.bind(this);
+ this.handleTagClick = this.handleTagClick.bind(this);
+ this.handleToggle = this.handleToggle.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.dep = new Tracker.Dependency;
+ }
+
+ componentDidMount() {
+ document.addEventListener("keydown", this.handleKeyDown);
+
+ Tracker.autorun(() => {
+ this.dep.depend();
+ const searchCollection = this.state.collection;
+ const searchQuery = this.state.value;
+ const facets = this.state.facets;
+ this.subscription = Meteor.subscribe("SearchResults", searchCollection, searchQuery, facets);
+
+ if (this.subscription.ready()) {
+ /*
+ * Product Search
+ */
+ if (searchCollection === "products") {
+ const productResults = Collections.ProductSearch.find().fetch();
+ this.setState({ productResults });
+
+ const productHashtags = getProductHashtags(productResults);
+ const tagSearchResults = Collections.Tags.find({
+ _id: { $in: productHashtags }
+ }).fetch();
+
+ this.setState({
+ tagResults: tagSearchResults,
+ accountResults: [],
+ orderResults: []
+ });
+ }
+
+ /*
+ * Account Search
+ */
+ if (searchCollection === "accounts") {
+ const accountResults = Collections.AccountSearch.find().fetch();
+ this.setState({
+ accountResults,
+ tagResults: [],
+ orderResults: [],
+ productResults: []
+ });
+ }
+
+ /*
+ * Order Search
+ */
+ if (searchCollection === "orders") {
+ const orderResults = Collections.OrderSearch.find().fetch();
+ this.setState({
+ orderResults,
+ tagResults: [],
+ accountResults: [],
+ productResults: []
+ });
+ }
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("keydown", this.handleKeyDown);
+ this.subscription.stop();
+ }
+
+ handleKeyDown = (event) => {
+ if (event.keyCode === 27) {
+ this.setState({
+ renderChild: false
+ });
+ }
+ }
+
+ handleChange = (event, value) => {
+ this.setState({ value }, () => {
+ this.dep.changed();
+ });
+ localStorage.setItem("searchValue", value);
+ }
+
+ handleClick = () => {
+ this.setState({
+ value: ""
+ }, () => {
+ this.dep.changed();
+ });
+ }
+
+ handleAccountClick = (event) => {
+ const userId = event._id;
+
+ Reaction.showActionView({
+ label: "Permissions",
+ i18nKeyLabel: "admin.settings.permissionsSettingsLabel",
+ data: userPermissions(userId),
+ template: "memberSettings"
+ });
+ Reaction.Router.go("dashboard/accounts", {}, {});
+ this.handleChildUnmount();
+ }
+
+ handleOrderClick = (event) => {
+ const isActionViewOpen = Reaction.isActionViewOpen();
+ const orderId = event._id;
+
+ // toggle detail views
+ if (isActionViewOpen === false) {
+ Reaction.showActionView({
+ label: "Order Details",
+ i18nKeyLabel: "orderWorkflow.orderDetails",
+ data: event,
+ props: {
+ size: "large"
+ },
+ template: "coreOrderWorkflow"
+ });
+ }
+
+ Reaction.Router.go("dashboard/orders", {}, {
+ _id: orderId
+ });
+ this.handleChildUnmount();
+ }
+
+ handleTagClick = (tagId) => {
+ const newFacet = tagId;
+ const element = document.getElementById(tagId);
+ element.classList.toggle("active-tag");
+
+ this.setState({
+ facets: tagToggle(this.state.facets, newFacet)
+ });
+ }
+
+ handleToggle = (collection) => {
+ this.setState({
+ tagResults: [],
+ productResults: [],
+ orderResults: [],
+ accountResults: [],
+ collection
+ }, () => {
+ this.dep.changed();
+ });
+ }
+
+ handleChildUnmount = () => {
+ this.setState({ renderChild: false });
+ }
+
+ render() {
+ return (
+
+ {this.state.renderChild ?
+
+
+
: null
+ }
+
+ );
+ }
+}
+
+function getSiteName() {
+ const shop = Collections.Shops.findOne();
+ return typeof shop === "object" && shop.name ? shop.name : "";
+}
+
+function getProductHashtags(productResults) {
+ const hashtags = [];
+ for (const product of productResults) {
+ if (product.hashtags) {
+ for (const hashtag of product.hashtags) {
+ if (!_.includes(hashtags, hashtag)) {
+ hashtags.push(hashtag);
+ }
+ }
+ }
+ }
+ return hashtags;
+}
+
+function userPermissions(userId) {
+ if (Reaction.hasPermission("reaction-accounts")) {
+ const shopId = Reaction.getShopId();
+ const user = Meteor.users.findOne(userId);
+ const member = {};
+ member.userId = user._id;
+
+ if (user.emails && user.emails.length) {
+ // this is some kind of denormalization. It is helpful to have both
+ // of this string and array. Array goes to avatar, string goes to
+ // template
+ member.emails = user.emails;
+ member.email = user.emails[0].address;
+ }
+ // member.user = user;
+ member.username = user.username;
+ member.isAdmin = Roles.userIsInRole(user._id, "admin", shopId);
+ member.roles = user.roles;
+ member.services = user.services;
+
+ if (Roles.userIsInRole(member.userId, "owner", shopId)) {
+ member.role = "owner";
+ } else if (Roles.userIsInRole(member.userId, "admin", shopId)) {
+ member.role = "admin";
+ } else if (Roles.userIsInRole(member.userId, "dashboard", shopId)) {
+ member.role = "dashboard";
+ } else if (Roles.userIsInRole(member.userId, "guest", shopId)) {
+ member.role = "guest";
+ }
+
+ return member;
+ }
+}
+
+function tagToggle(arr, val) {
+ if (arr.length === _.pull(arr, val).length) {
+ arr.push(val);
+ }
+ return arr;
+}
+
+function composer(props, onData) {
+ Meteor.subscribe("ShopMembers");
+ const siteName = getSiteName();
+
+ onData(null, {
+ siteName
+ });
+}
+
+export default composeWithTracker(composer)(SearchModalContainer);
diff --git a/imports/plugins/included/ui-search/lib/helpers/accountsTable.js b/imports/plugins/included/ui-search/lib/helpers/accountsTable.js
new file mode 100644
index 00000000000..181dd1f7698
--- /dev/null
+++ b/imports/plugins/included/ui-search/lib/helpers/accountsTable.js
@@ -0,0 +1,79 @@
+import React from "react";
+import { DataType } from "react-taco-table";
+import { i18next } from "/client/api";
+
+export default function accountsTable() {
+ const columns = [
+ {
+ id: "_id",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.accountId", { defaultValue: "Account ID" }),
+ value: rowData => {
+ return rowData._id;
+ }
+ },
+ {
+ id: "shopId",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.shopId", { defaultValue: "Shop ID" }),
+ value: rowData => {
+ return rowData.shopId;
+ }
+ },
+ {
+ id: "firstName",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.firstName", { defaultValue: "First Name" }),
+ value: rowData => {
+ if (rowData.profile) {
+ return rowData.profile.firstName;
+ }
+ return undefined;
+ }
+ },
+ {
+ id: "lastName",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.lastName", { defaultValue: "Last Name" }),
+ value: rowData => {
+ if (rowData.profile) {
+ return rowData.profile.lastName;
+ }
+ return undefined;
+ }
+ },
+ {
+ id: "phone",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.phone", { defaultValue: "Phone" }),
+ value: rowData => {
+ if (rowData.profile) {
+ return rowData.profile.phone;
+ }
+ return undefined;
+ }
+ },
+ {
+ id: "email",
+ type: DataType.String,
+ header: i18next.t("search.accountSearchResults.emails", { defaultValue: "Email" }),
+ value: rowData => {
+ return rowData.emails[0];
+ }
+ },
+ {
+ id: "manageAccount",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingStatus", { defaultValue: "Shipping Status" }),
+ value: rowData => {
+ return rowData.emails[0];
+ },
+ tdClassName: "account-manage",
+ renderer(cellData, { rowData }) {
+ return
Manage;
+ }
+ }
+ ];
+
+ return columns;
+}
diff --git a/imports/plugins/included/ui-search/lib/helpers/index.js b/imports/plugins/included/ui-search/lib/helpers/index.js
new file mode 100644
index 00000000000..8420ff02932
--- /dev/null
+++ b/imports/plugins/included/ui-search/lib/helpers/index.js
@@ -0,0 +1,3 @@
+export { default as accountsTable } from "./accountsTable";
+export { default as ordersTable } from "./ordersTable";
+
diff --git a/imports/plugins/included/ui-search/lib/helpers/ordersTable.js b/imports/plugins/included/ui-search/lib/helpers/ordersTable.js
new file mode 100644
index 00000000000..3cb646a7ad6
--- /dev/null
+++ b/imports/plugins/included/ui-search/lib/helpers/ordersTable.js
@@ -0,0 +1,104 @@
+import _ from "lodash";
+import React from "react";
+import { DataType } from "react-taco-table";
+import { i18next } from "/client/api";
+
+export default function ordersTable() {
+ const columns = [
+ {
+ id: "_id",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.orderId", { defaultValue: "Order ID" }),
+ renderer(cellData) { // eslint-disable-line react/no-multi-comp
+ return
{cellData};
+ }
+ },
+ {
+ id: "shippingName",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingName", { defaultValue: "Name" }),
+ value: rowData => {
+ return rowData.shippingName;
+ }
+ },
+ {
+ id: "userEmail",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.userEmails", { defaultValue: "Email" }),
+ value: rowData => {
+ return rowData.userEmails[0];
+ }
+ },
+ {
+ id: "shippingAddress",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingAddress", { defaultValue: "Address" }),
+ value: rowData => {
+ return rowData.shippingAddress.address;
+ }
+ },
+ {
+ id: "shippingCity",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingCity", { defaultValue: "City" }),
+ value: rowData => {
+ return rowData.shippingAddress.city;
+ }
+ },
+ {
+ id: "shippingRegion",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingRegion", { defaultValue: "Region" }),
+ value: rowData => {
+ return rowData.shippingAddress.region;
+ }
+ },
+ {
+ id: "shippingCountry",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingCountry", { defaultValue: "Country" }),
+ value: rowData => {
+ return rowData.shippingAddress.country;
+ }
+ },
+ {
+ id: "shippingPhone",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingPhone", { defaultValue: "Phone" }),
+ value: rowData => {
+ return rowData.shippingPhone;
+ }
+ },
+ {
+ id: "shippingStatus",
+ type: DataType.String,
+ header: i18next.t("search.orderSearchResults.shippingStatus", { defaultValue: "Shipping Status" }),
+ value: rowData => {
+ return rowData.shippingStatus;
+ },
+ tdClassName: "shipping-status",
+ renderer(cellData, { rowData }) { // eslint-disable-line react/no-multi-comp
+ const rowClassName = _.lowerCase(rowData.shippingStatus);
+ return
{cellData};
+ }
+ },
+ {
+ id: "orderDate",
+ type: DataType.Date,
+ header: i18next.t("search.orderSearchResults.orderDate", { defaultValue: "Date" }),
+ value: rowData => {
+ return rowData.orderDate;
+ }
+ },
+ {
+ id: "orderTotal",
+ type: DataType.Number,
+ header: i18next.t("search.orderSearchResults.orderTotal", { defaultValue: "Total" }),
+ value: rowData => {
+ return rowData.orderTotal;
+ }
+ }
+ ];
+
+ return columns;
+}
diff --git a/imports/test-utils/__mocks__/meteor/session.js b/imports/test-utils/__mocks__/meteor/session.js
new file mode 100644
index 00000000000..47d2c78040c
--- /dev/null
+++ b/imports/test-utils/__mocks__/meteor/session.js
@@ -0,0 +1,4 @@
+export const Session = {
+ get() {},
+ set() {}
+};
diff --git a/imports/test-utils/__mocks__/meteor/tmeasday:publish-counts.js b/imports/test-utils/__mocks__/meteor/tmeasday:publish-counts.js
new file mode 100644
index 00000000000..ab8fe2c38c7
--- /dev/null
+++ b/imports/test-utils/__mocks__/meteor/tmeasday:publish-counts.js
@@ -0,0 +1,2 @@
+export const Counts = {
+};
diff --git a/lib/api/account-validation.js b/lib/api/account-validation.js
index 0793f2e92cf..6d1f12a9dee 100644
--- a/lib/api/account-validation.js
+++ b/lib/api/account-validation.js
@@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
const validationMethods = {
/**
diff --git a/lib/api/catalog.js b/lib/api/catalog.js
index 9d8e2cbbbc2..c3a7a2b93d4 100644
--- a/lib/api/catalog.js
+++ b/lib/api/catalog.js
@@ -1,4 +1,6 @@
+import _ from "lodash";
import { Products } from "/lib/collections";
+import { ReactionProduct } from "/lib/api";
import { applyProductRevision } from "/lib/api/products";
export default {
@@ -20,8 +22,7 @@ export default {
productId = product._id;
}
}
- setCurrentProduct(productId);
- setCurrentVariant(variantId);
+ ReactionProduct.setCurrentVariant(variantId);
},
/**
@@ -131,7 +132,7 @@ export default {
const options = this.getVariants(variant._id);
if (options && options.length) {
return options.reduce((sum, option) =>
- sum + option.inventoryQuantity || 0, 0);
+ sum + option.inventoryQuantity || 0, 0);
}
return variant.inventoryQuantity || 0;
},
diff --git a/lib/api/compose.js b/lib/api/compose.js
index b4072335307..cd085a33868 100644
--- a/lib/api/compose.js
+++ b/lib/api/compose.js
@@ -5,6 +5,7 @@
import { compose } from "react-komposer";
import React from "react";
export * from "react-komposer";
+import { Tracker } from "meteor/tracker";
/**
* getTrackerLoader creates a Meteor Tracker to watch dep updates from
diff --git a/lib/api/core.js b/lib/api/core.js
index 58891a4e774..0448642bad5 100644
--- a/lib/api/core.js
+++ b/lib/api/core.js
@@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
+import { Roles } from "meteor/alanning:roles";
import { Accounts, Shops, SellerShops } from "/lib/collections";
// Export Reaction using commonJS style for common libraries to use Reaction easily
diff --git a/lib/api/files.js b/lib/api/files.js
index 87698885a6c..8c423958b22 100644
--- a/lib/api/files.js
+++ b/lib/api/files.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
/*
* Copy store data, copied from Meteor CollectionsFS
* https://github.com/CollectionFS/Meteor-CollectionFS/blob/master/packages/file/fsFile-server.js#L225
diff --git a/lib/api/products.js b/lib/api/products.js
index 1f9c8e8844e..c2dbd5c101e 100644
--- a/lib/api/products.js
+++ b/lib/api/products.js
@@ -1,5 +1,6 @@
import i18next from "i18next";
import orderBy from "lodash/orderBy";
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { ReactiveDict } from "meteor/reactive-dict";
import { Router } from "/imports/plugins/core/router/lib";
diff --git a/lib/api/prop-types.js b/lib/api/prop-types.js
index 7814bbd3af2..454f706a505 100644
--- a/lib/api/prop-types.js
+++ b/lib/api/prop-types.js
@@ -1,4 +1,5 @@
import _ from "lodash";
+import { check } from "meteor/check";
import * as Schemas from "/lib/collections/schemas";
const TagSchema = Schemas.Tag.newContext();
diff --git a/lib/collections/collectionFS.js b/lib/collections/collectionFS.js
index 8fa9798fb70..0e1bf341ad1 100644
--- a/lib/collections/collectionFS.js
+++ b/lib/collections/collectionFS.js
@@ -1,3 +1,4 @@
+
/**
* core collectionsFS configurations
*/
@@ -19,8 +20,8 @@ export const Media = new FS.Collection("Media", {
}), new FS.Store.GridFS("large", {
chunkSize: 1 * 1024 * 1024,
transformWrite: function (fileObj, readStream, writeStream) {
- if (gm.isAvailable) {
- gm(readStream, fileObj.name).resize("1000", "1000").stream()
+ if (this.gm.isAvailable) {
+ this.gm(readStream, fileObj.name).resize("1000", "1000").stream()
.pipe(writeStream);
} else {
readStream.pipe(writeStream);
@@ -29,8 +30,8 @@ export const Media = new FS.Collection("Media", {
}), new FS.Store.GridFS("medium", {
chunkSize: 1 * 1024 * 1024,
transformWrite: function (fileObj, readStream, writeStream) {
- if (gm.isAvailable) {
- gm(readStream, fileObj.name).resize("600", "600").stream().pipe(
+ if (this.gm.isAvailable) {
+ this.gm(readStream, fileObj.name).resize("600", "600").stream().pipe(
writeStream);
} else {
readStream.pipe(writeStream);
@@ -39,8 +40,8 @@ export const Media = new FS.Collection("Media", {
}), new FS.Store.GridFS("small", {
chunkSize: 1 * 1024 * 1024,
transformWrite: function (fileObj, readStream, writeStream) {
- if (gm.isAvailable) {
- gm(readStream).resize("235", "235" + "^").gravity("Center")
+ if (this.gm.isAvailable) {
+ this.gm(readStream).resize("235", "235" + "^").gravity("Center")
.extent("235", "235").stream("PNG").pipe(writeStream);
} else {
readStream.pipe(writeStream);
@@ -49,8 +50,8 @@ export const Media = new FS.Collection("Media", {
}), new FS.Store.GridFS("thumbnail", {
chunkSize: 1 * 1024 * 1024,
transformWrite: function (fileObj, readStream, writeStream) {
- if (gm.isAvailable) {
- gm(readStream).resize("100", "100" + "^").gravity("Center")
+ if (this.gm.isAvailable) {
+ this.gm(readStream).resize("100", "100" + "^").gravity("Center")
.extent("100", "100").stream("PNG").pipe(writeStream);
} else {
readStream.pipe(writeStream);
diff --git a/lib/collections/collections.js b/lib/collections/collections.js
index 328d7358afe..9d00696a813 100644
--- a/lib/collections/collections.js
+++ b/lib/collections/collections.js
@@ -1,4 +1,6 @@
+import _ from "lodash";
import { Mongo } from "meteor/mongo";
+import { Meteor } from "meteor/meteor";
import * as Schemas from "./schemas";
import { cartTransform } from "./transform/cart";
import { orderTransform } from "./transform/order";
diff --git a/lib/collections/schemas/analytics.js b/lib/collections/schemas/analytics.js
index 29e4ade7641..62f4be7a4b2 100644
--- a/lib/collections/schemas/analytics.js
+++ b/lib/collections/schemas/analytics.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { SimpleSchema } from "meteor/aldeed:simple-schema";
import { Roles } from "meteor/alanning:roles";
import { getShopId } from "/lib/api";
diff --git a/lib/collections/transform/order.js b/lib/collections/transform/order.js
index 8930dbc6f90..1b014d96bea 100644
--- a/lib/collections/transform/order.js
+++ b/lib/collections/transform/order.js
@@ -19,11 +19,11 @@ function getSummary(items, prop, prop2) {
if (prop2) {
// S + a * b, where b could be b1 or b2
return sum + item[prop[0]] * (prop2.length === 1 ? item[prop2[0]] :
- item[prop2[0]][prop2[1]]);
+ item[prop2[0]][prop2[1]]);
}
// S + b, where b could be b1 or b2
return sum + (prop.length === 1 ? item[prop[0]] :
- item[prop[0]][prop[1]]);
+ item[prop[0]][prop[1]]);
}, 0);
}
} catch (e) {
diff --git a/package.json b/package.json
index 24159cd0537..85e01f8e87a 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@reactioncommerce/authorize-net": "^1.0.8",
"@reactioncommerce/nodemailer": "^5.0.4",
"accounting-js": "^1.1.1",
+ "amplify-store": "^0.1.0",
"autoprefixer": "^7.1.1",
"autosize": "^3.0.20",
"babel-runtime": "^6.23.0",
@@ -33,6 +34,7 @@
"classnames": "^2.2.5",
"copy-to-clipboard": "^3.0.6",
"country-data": "^0.0.31",
+ "create-react-class": "^15.6.0",
"css-annotation": "^0.6.2",
"deep-diff": "^0.3.4",
"dnd-core": "^2.3.0",
@@ -40,7 +42,6 @@
"fibers": "^1.0.15",
"flatten-obj": "^3.1.0",
"font-awesome": "^4.7.0",
- "griddle-react": "0.7.1",
"handlebars": "^4.0.6",
"history": "^4.6.1",
"i18next": "8.4.2",
@@ -53,6 +54,7 @@
"later": "^1.2.0",
"lodash": "^4.17.4",
"lodash.pick": "^4.4.0",
+ "match-sorter": "^1.8.0",
"meteor-node-stubs": "^0.2.11",
"moment": "^2.18.1",
"moment-timezone": "^0.5.11",
@@ -90,6 +92,7 @@
"react-router-dom": "^4.1.1",
"react-select": "^1.0.0-rc.3",
"react-simple-di": "^1.2.0",
+ "react-table": "^6.3.0",
"react-taco-table": "^0.5.0",
"react-tether": "^0.5.6",
"react-textarea-autosize": "^4.0.5",
@@ -118,8 +121,8 @@
"chai": "^4.0.2",
"enzyme": "^2.8.2",
"enzyme-to-json": "^1.5.1",
- "eslint": "^3.18.0",
- "eslint-plugin-react": "^6.10.3",
+ "eslint": "^4.2.0",
+ "eslint-plugin-react": "^7.1.0",
"jest": "^20.0.4",
"js-yaml": "^3.8.2",
"react-addons-test-utils": "15.4.2"
@@ -138,24 +141,35 @@
},
"jest": {
"moduleNameMapper": {
- "^\/lib(.*)": "
/lib/$1",
- "^\/imports\/plugins(.*)": "/imports/plugins/$1",
- "^\/client\/api(.*)": "/imports/test-utils/__mocks__/client/api$1",
+ "^/lib(.*)": "/lib/$1",
+ "^/imports/plugins(.*)": "/imports/plugins/$1",
+ "^/client/api(.*)": "/imports/test-utils/__mocks__/client/api$1",
"^meteor/aldeed:simple-schema": "/imports/test-utils/__mocks__/meteor/aldeed-simple-schema",
+ "^meteor/tmeasday:publish-counts": "/imports/test-utils/__mocks__/meteor/tmeasday-publish-counts",
"^meteor/(.*)": "/imports/test-utils/__mocks__/meteor/$1"
}
},
"babel": {
"plugins": [
- "lodash", ["module-resolver", {
- "root": ["./"],
- "alias": {
- "underscore": "lodash",
- "@reactioncommerce/reaction-ui": "./imports/plugins/core/ui/client/components",
- "@reactioncommerce/reaction-router": "./imports/plugins/core/router/lib"
+ "lodash",
+ [
+ "module-resolver",
+ {
+ "root": [
+ "./"
+ ],
+ "alias": {
+ "underscore": "lodash",
+ "@reactioncommerce/reaction-ui": "./imports/plugins/core/ui/client/components",
+ "@reactioncommerce/reaction-router": "./imports/plugins/core/router/lib"
+ }
}
- }]
+ ]
],
- "presets": ["es2015", "react", "stage-2"]
+ "presets": [
+ "es2015",
+ "react",
+ "stage-2"
+ ]
}
}
diff --git a/private/data/Shops.json b/private/data/Shops.json
index eff6825f6e8..c6e8b78fc6d 100755
--- a/private/data/Shops.json
+++ b/private/data/Shops.json
@@ -463,6 +463,12 @@
"format": "%s%v",
"symbol": "฿"
},
+ "TND": {
+ "enabled": false,
+ "format": "%v %s",
+ "symbol": "DT",
+ "decimal": ","
+ },
"TWD": {
"enabled": false,
"format": "%s%v",
@@ -3655,6 +3661,12 @@
"format": "%s%v",
"symbol": "฿"
},
+ "TND": {
+ "enabled": false,
+ "format": "%v %s",
+ "symbol": "DT",
+ "decimal": ","
+ },
"TWD": {
"enabled": false,
"format": "%s%v",
diff --git a/server/api/core/accounts/password.js b/server/api/core/accounts/password.js
index 2fb079358bd..0c876ccf6df 100644
--- a/server/api/core/accounts/password.js
+++ b/server/api/core/accounts/password.js
@@ -1,5 +1,7 @@
import _ from "lodash";
import moment from "moment";
+import path from "path";
+import { Random } from "meteor/random";
import { Meteor } from "meteor/meteor";
import { Accounts } from "meteor/accounts-base";
import { SSR } from "meteor/meteorhacks:ssr";
diff --git a/server/api/core/assignRoles.js b/server/api/core/assignRoles.js
index dd2384783ba..d5bde04af11 100644
--- a/server/api/core/assignRoles.js
+++ b/server/api/core/assignRoles.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
import { Roles } from "meteor/alanning:roles";
import { Logger } from "/server/api";
@@ -49,7 +50,7 @@ export function assignOwnerRoles(shopId, pkgName, registry) {
const globalRoles = defaultRoles;
if (registry) {
- // for each registry item define and push roles
+ // for each registry item define and push roles
for (const registryItem of registry) {
// packages don't need to define specific permission routes.,
// the routeName will be used as default roleName for each route.
@@ -63,9 +64,9 @@ export function assignOwnerRoles(shopId, pkgName, registry) {
// define permissions if you need to check custom permission
if (registryItem.permissions) {
for (const permission of registryItem.permissions) {
- // A wrong value in permissions (ie. [String] instead of [Object] in any plugin register.js
- // results in an undefined element in defaultRoles Array
- // an undefined value would make Roles.getUsersInRole(defaultRoles) return ALL users
+ // A wrong value in permissions (ie. [String] instead of [Object] in any plugin register.js
+ // results in an undefined element in defaultRoles Array
+ // an undefined value would make Roles.getUsersInRole(defaultRoles) return ALL users
if (permission && typeof permission.permission === "string" && permission.permission.length) {
defaultRoles.push(permission.permission);
}
diff --git a/server/api/core/core.js b/server/api/core/core.js
index 0b9c85b8a1c..fbcd838e127 100644
--- a/server/api/core/core.js
+++ b/server/api/core/core.js
@@ -1,9 +1,13 @@
import url from "url";
import packageJson from "/package.json";
import { merge, uniqWith } from "lodash";
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
-import { EJSON } from "meteor/ejson";
import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
+import { Accounts } from "meteor/accounts-base";
+import { Roles } from "meteor/alanning:roles";
+import { EJSON } from "meteor/ejson";
import { Jobs, Packages, Shops, Groups } from "/lib/collections";
import { Hooks, Logger } from "/server/api";
import ProcessJobs from "/server/jobs";
@@ -99,7 +103,7 @@ export default {
* @param {String} checkGroup group - default to shopId
* @return {Boolean} Boolean - true if has permission
*/
- hasPermission(checkPermissions, userId = Meteor.userId(), checkGroup = this.getShopId()) { // TODO: getSellerShop Conversion - pass in shop here
+ hasPermission(checkPermissions, userId = Meteor.userId(), checkGroup = this.getShopId()) {
// check(checkPermissions, Match.OneOf(String, Array)); check(userId, String); check(checkGroup,
// Match.Optional(String));
@@ -131,8 +135,6 @@ export default {
}
// global roles check
- // TODO: Review this commented out code
- /*
const sellerShopPermissions = Roles.getGroupsForUser(userId, "admin");
// we're looking for seller permissions.
@@ -146,8 +148,7 @@ export default {
}
}
}
- }*/
-
+ }
// no specific permissions found returning false
return false;
},
@@ -164,6 +165,10 @@ export default {
return this.hasPermission(["owner", "admin", "dashboard"]);
},
+ getSellerShopId() {
+ return Roles.getGroupsForUser(this.userId, "admin");
+ },
+
configureMailUrl() {
// maintained for legacy support
Logger.warn("Reaction.configureMailUrl() is deprecated. Please use Reaction.Email.getMailUrl() instead");
@@ -181,6 +186,7 @@ export default {
return cursor;
},
+ // TODO: Get actual current shop instead of first - not sure what this will break..
getCurrentShop() {
const currentShopCursor = this.getCurrentShopCursor();
// also, we could check in such a way: `currentShopCursor instanceof Object` but not instanceof something.Cursor
@@ -192,7 +198,6 @@ export default {
getShopId(userId) {
check(userId, Match.Maybe(String));
-
const activeUserId = Meteor.call("reaction/getUserId");
if (activeUserId || userId) {
const activeShopId = this.getUserPreferences({
@@ -216,7 +221,6 @@ export default {
_id: 1
}
}).fetch()[0];
-
return shop && shop._id;
},
@@ -266,39 +270,27 @@ export default {
return shop && shop.currency || "USD";
},
+ // TODO: Marketplace - should each shop set their own default language or
+ // should the Marketplace set a language that's picked up by all shops?
getShopLanguage() {
- const shop = Shops.findOne({
+ const { language } = Shops.findOne({
_id: this.getShopId()
}, {
fields: {
language: 1
} }
);
- if (shop) {
- return shop.language;
- }
- throw new Meteor.Error("Shop not found");
+ return language;
},
getPackageSettings(name) {
- const shopId = this.getShopId();
- const query = {
- name
- };
-
- if (shopId) {
- query.shopId = shopId;
- }
-
- return Packages.findOne(query);
+ return Packages.findOne({ name: name, shopId: this.getShopId() }) || null;
},
- // {packageName, preference, defaultValue}
+ // options: {packageName, preference, defaultValue}
getUserPreferences(options) {
- const userId = options.userId;
- const packageName = options.packageName;
- const preference = options.preference;
- const defaultValue = options.defaultValue;
+ const { userId, packageName, preference, defaultValue } = options;
+
if (!userId) {
return undefined;
}
@@ -311,60 +303,52 @@ export default {
return profile.preferences[packageName][preference];
}
}
-
return defaultValue || undefined;
},
- /** // TODO: Check to see if this is still being used
- * Add default roles for new visitors
- * @param {String|Array} roles - A string or array of roles and routes
- * @returns {undefined} - does not specifically return anything
- */
- addDefaultRolesToVisitors(roles) {
- Logger.info(`Adding defaultRoles & defaultVisitorRole permissions for ${roles}`);
-
- const shop = Shops.findOne(this.getShopId());
-
- if (Match.test(roles, [String])) {
- Shops.update(shop._id, {
- $addToSet: { defaultVisitorRole: { $each: roles } }
- });
- Shops.update(shop._id, {
- $addToSet: { defaultRoles: { $each: roles } }
- });
- } else if (Match.test(roles, String)) {
- Shops.update(shop._id, {
- $addToSet: { defaultVisitorRole: roles }
- });
- Shops.update(shop._id, {
- $addToSet: { defaultRoles: roles }
- });
- } else {
- throw new Meteor.Error(`Failed to add default roles ${roles}`);
- }
- },
-
/**
- * Add default roles for new sellers
- * @param {String|Array} roles A string or array of roles and routes
- * @returns {undefined} - does not specifically return anything
+ * insertPackagesForShop
+ * insert Reaction packages into Packages collection registry for a new shop
+ * Assigns owner roles for new packages
+ * Imports layouts from packages
+ * @param {String} shopId - the shopId you need to create packages for
+ * @return {String} returns insert result
*/
- addDefaultRolesToSellers(roles) {
- Logger.info(`Adding defaultSellerRoles permissions for ${roles}`);
+ insertPackagesForShop(shopId) {
+ const layouts = [];
+ if (!shopId) {
+ return [];
+ }
+ const packages = this.Packages;
+ // for each shop, we're loading packages in a unique registry
+ // Object.keys(pkgConfigs).forEach((pkgName) => {
+ for (const packageName in packages) {
+ // Guard to prvent unexpected `for in` behavior
+ if ({}.hasOwnProperty.call(packages, packageName)) {
+ const config = packages[packageName];
+ this.assignOwnerRoles(shopId, packageName, config.registry);
- const shop = Shops.findOne(this.getShopId());
+ const pkg = Object.assign({}, config, {
+ shopId: shopId
+ });
- if (Match.test(roles, [String])) {
- Shops.update(shop._id, {
- $addToSet: { defaultSellerRole: { $each: roles } }
- });
- } else if (Match.test(roles, String)) {
- Shops.update(shop._id, {
- $addToSet: { defaultSellerRole: roles }
- });
- } else {
- throw new Meteor.Error(`Failed to add default seller roles ${roles}`);
+ // populate array of layouts that don't already exist (?!)
+ if (pkg.layout) {
+ // filter out layout templates
+ for (const template of pkg.layout) {
+ if (template && template.layout) {
+ layouts.push(template);
+ }
+ }
+ }
+ Packages.insert(pkg);
+ Logger.debug(`Initializing ${shopId} ${packageName}`);
+ }
}
+
+ // helper for removing layout duplicates
+ const uniqLayouts = uniqWith(layouts, _.isEqual);
+ Shops.update({ _id: shopId }, { $set: { layout: uniqLayouts } });
},
getAppVersion() {
@@ -454,7 +438,7 @@ export default {
// set the default shop email to the default admin email
Shops.update(shopId, {
$addToSet: {
- domains: domain
+ domains: this.getDomain()
}
});
}
@@ -634,52 +618,6 @@ export default {
});
});
},
-
- /**
- * insertPackagesForShop
- * insert Reaction packages into Packages collection registry for a new shop
- * Assigns owner roles for new packages
- * Imports layouts from packages
- * @param {String} shopId - the shopId you need to create packages for
- * @return {String} returns insert result
- */
- insertPackagesForShop(shopId) {
- const layouts = [];
- if (!shopId) {
- return [];
- }
- const packages = this.Packages;
- // for each shop, we're loading packages in a unique registry
- // Object.keys(pkgConfigs).forEach((pkgName) => {
- for (const packageName in packages) {
- // Guard to prvent unexpected `for in` behavior
- if ({}.hasOwnProperty.call(packages, packageName)) {
- const config = packages[packageName];
- this.assignOwnerRoles(shopId, packageName, config.registry);
-
- const pkg = Object.assign({}, config, {
- shopId: shopId
- });
-
- // populate array of layouts that don't already exist (?!)
- if (pkg.layout) {
- // filter out layout templates
- for (const template of pkg.layout) {
- if (template && template.layout) {
- layouts.push(template);
- }
- }
- }
- Packages.insert(pkg);
- Logger.debug(`Initializing ${shopId} ${packageName}`);
- }
- }
-
- // helper for removing layout duplicates
- const uniqLayouts = uniqWith(layouts, _.isEqual);
- Shops.update({ _id: shopId }, { $set: { layout: uniqLayouts } });
- },
-
setAppVersion() {
const version = packageJson.version;
Logger.info(`Reaction Version: ${version}`);
diff --git a/server/api/core/import.js b/server/api/core/import.js
index 6e7787c33f1..2d424f2f8ed 100644
--- a/server/api/core/import.js
+++ b/server/api/core/import.js
@@ -1,5 +1,8 @@
import { Mongo } from "meteor/mongo";
import { EJSON } from "meteor/ejson";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
+import { MongoInternals } from "meteor/mongo";
import * as Collections from "/lib/collections";
import Hooks from "../hooks";
import { Logger } from "../logger";
@@ -67,7 +70,7 @@ Import.identify = function (document) {
const probabilities = {};
- for (key of Object.keys(document)) {
+ for (const key of Object.keys(document)) {
if (this._indications[key]) {
const collection = this._name(this._indications[key].collection);
probabilities[collection] = probabilities[collection] || 1.0 * this._indications[
@@ -76,13 +79,13 @@ Import.identify = function (document) {
}
let total = 1.0;
- for (key of Object.keys(probabilities)) {
+ for (const key of Object.keys(probabilities)) {
total *= probabilities[key];
}
let max = 0.0;
let name;
- for (key of Object.keys(probabilities)) {
+ for (const key of Object.keys(probabilities)) {
const probability = total / probabilities[key];
if (probability > max) {
max = probability;
@@ -215,7 +218,6 @@ Import.buffer = function (collection) {
* @summary Store a product in the import buffer.
* @param {Object} key A key to look up the product
* @param {Object} product The product data to be updated
- * @param {Object} parent A key to identify the parent product
* @returns {Object}
* Importing a variant currently consists of the following steps:
*
@@ -224,6 +226,12 @@ Import.buffer = function (collection) {
* * Update the variant.
*/
Import.product = function (key, product) {
+ // If product has an _id, we use it to look up the product before
+ // updating the product so as to avoid trying to change the _id
+ // which is immutable.
+ if (product._id && !key._id) {
+ key._id = product._id;
+ }
return this.object(Collections.Products, key, product);
};
@@ -251,7 +259,6 @@ Import.package = function (pkg, shopId) {
/**
* @summary Store a template in the import buffer.
* @param {Object} templateInfo The template data to be updated
- * @param {String} shopId The package data to be updated
* @returns {undefined}
*/
Import.template = function (templateInfo) {
@@ -353,7 +360,9 @@ Import.object = function (collection, key, object) {
const updateObject = object;
// enforce strings instead of Mongo.ObjectId
- if (!collection.findOne(key) && !object._id) key._id = Random.id();
+ if (!collection.findOne(key) && !object._id) {
+ key._id = Random.id();
+ }
// hooks for additional import manipulation.
const importObject = Hooks.Events.run(`onImport${this._name(collection)}`, object);
diff --git a/server/api/core/loadSettings.js b/server/api/core/loadSettings.js
index 3d96bdf14b0..1dac2b35c88 100644
--- a/server/api/core/loadSettings.js
+++ b/server/api/core/loadSettings.js
@@ -1,3 +1,6 @@
+import _ from "lodash";
+import { check } from "meteor/check";
+import { ServiceConfiguration } from "meteor/service-configuration";
import { Packages } from "/lib/collections";
import { Logger } from "/server/api";
import { EJSON } from "meteor/ejson";
@@ -30,9 +33,10 @@ export function loadSettings(json) {
if (!_.isArray(validatedJson[0])) {
Logger.warn(
"Load Settings is not an array. Failed to load settings.");
- return;
+ return false;
}
+ let result;
// loop settings and upsert packages.
for (const pkg of validatedJson) {
for (const item of pkg) {
@@ -80,4 +84,5 @@ export function loadSettings(json) {
Logger.debug(`loaded local package data: ${item.name}`);
}
}
+ return result;
}
diff --git a/server/api/core/ui.js b/server/api/core/ui.js
index b270551cfa1..717b73801c3 100644
--- a/server/api/core/ui.js
+++ b/server/api/core/ui.js
@@ -1,7 +1,10 @@
+/* global baseStyles */
import postcss from "postcss";
import postcssJS from "postcss-js";
import autoprefixer from "autoprefixer";
import cssAnnotation from "css-annotation";
+import { check, Match } from "meteor/check";
+import { Meteor } from "meteor/meteor";
import { Shops, Themes } from "/lib/collections";
import { Reaction } from "./core";
diff --git a/server/api/geocoder.js b/server/api/geocoder.js
index bab001cdd80..43a74089692 100644
--- a/server/api/geocoder.js
+++ b/server/api/geocoder.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
import { HTTP } from "meteor/http";
import { Meteor } from "meteor/meteor";
import { Packages } from "/lib/collections";
diff --git a/server/api/hooks.js b/server/api/hooks.js
index 323b0c11b4e..5faa32c6385 100644
--- a/server/api/hooks.js
+++ b/server/api/hooks.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
/**
* Callback hooks to alter the behavior of common operations or trigger other things.
diff --git a/server/api/logger/main.js b/server/api/logger/main.js
index f7436e3b2d1..1c814317ae8 100644
--- a/server/api/logger/main.js
+++ b/server/api/logger/main.js
@@ -4,6 +4,7 @@ import bunyanFormat from "bunyan-format";
import { Meteor } from "meteor/meteor";
import Bunyan2Loggly from "./loggly";
+
// configure bunyan logging module for reaction server
// See: https://github.com/trentm/node-bunyan#levels
const levels = ["FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
diff --git a/server/imports/fixtures/accounts.js b/server/imports/fixtures/accounts.js
index 1a310ad0236..375f07ef2bb 100755
--- a/server/imports/fixtures/accounts.js
+++ b/server/imports/fixtures/accounts.js
@@ -1,5 +1,6 @@
import faker from "faker";
import _ from "lodash";
+import { Meteor } from "meteor/meteor";
import { Factory } from "meteor/dburles:factory";
import { Accounts } from "/lib/collections";
import { getShop } from "./shops";
diff --git a/server/imports/fixtures/cart.js b/server/imports/fixtures/cart.js
index 20ef99c1ea9..1a8341822ed 100755
--- a/server/imports/fixtures/cart.js
+++ b/server/imports/fixtures/cart.js
@@ -1,5 +1,7 @@
import faker from "faker";
+import _ from "lodash";
import { Factory } from "meteor/dburles:factory";
+import { Random } from "meteor/random";
import { Cart, Products } from "/lib/collections";
import "./shops";
import { getShop } from "./shops";
diff --git a/server/imports/fixtures/fixtures.app-test.js b/server/imports/fixtures/fixtures.app-test.js
index 393df91f66b..5eaa165e033 100644
--- a/server/imports/fixtures/fixtures.app-test.js
+++ b/server/imports/fixtures/fixtures.app-test.js
@@ -1,5 +1,6 @@
import { Meteor } from "meteor/meteor";
-
+import { Factory } from "meteor/dburles:factory";
+import { check, Match } from "meteor/check";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
diff --git a/server/imports/fixtures/orders.js b/server/imports/fixtures/orders.js
index 7d6026edeee..f8996262fa8 100755
--- a/server/imports/fixtures/orders.js
+++ b/server/imports/fixtures/orders.js
@@ -1,4 +1,7 @@
import faker from "faker";
+import _ from "lodash";
+import { Random } from "meteor/random";
+import { Factory } from "meteor/dburles:factory";
import { Orders, Products } from "/lib/collections";
import { getShop } from "./shops";
import { getUser } from "./users";
diff --git a/server/imports/fixtures/products.js b/server/imports/fixtures/products.js
index af26fa58190..b5fb759d77a 100755
--- a/server/imports/fixtures/products.js
+++ b/server/imports/fixtures/products.js
@@ -1,4 +1,6 @@
import faker from "faker";
+import _ from "lodash";
+import { Factory } from "meteor/dburles:factory";
import { Products, Tags } from "/lib/collections";
import { getShop } from "./shops";
diff --git a/server/imports/fixtures/shops.js b/server/imports/fixtures/shops.js
index decd932f608..47e1dafc51b 100755
--- a/server/imports/fixtures/shops.js
+++ b/server/imports/fixtures/shops.js
@@ -1,4 +1,6 @@
import faker from "faker";
+import _ from "lodash";
+import { Factory } from "meteor/dburles:factory";
import { Shops } from "/lib/collections";
export function getShop() {
diff --git a/server/imports/fixtures/users.js b/server/imports/fixtures/users.js
index 3119b872cd2..dad6be3ec43 100755
--- a/server/imports/fixtures/users.js
+++ b/server/imports/fixtures/users.js
@@ -1,4 +1,8 @@
import faker from "faker";
+import _ from "lodash";
+import { Meteor } from "meteor/meteor";
+import { Random } from "meteor/random";
+import { Factory } from "meteor/dburles:factory";
import "./shops";
import { getShop } from "./shops";
import moment from "moment";
diff --git a/server/jobs/email.js b/server/jobs/email.js
index 4bbcaf39afb..800363a366f 100644
--- a/server/jobs/email.js
+++ b/server/jobs/email.js
@@ -1,4 +1,5 @@
import nodemailer from "@reactioncommerce/nodemailer";
+import { Meteor } from "meteor/meteor";
import { Emails, Jobs } from "/lib/collections";
import { Reaction, Logger } from "/server/api";
diff --git a/server/main.js b/server/main.js
index e6af789af4d..89f65ee6f7d 100644
--- a/server/main.js
+++ b/server/main.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import "./methods";
import Startup from "./startup";
import Security from "./security";
diff --git a/server/methods/accounts/accounts.app-test.js b/server/methods/accounts/accounts.app-test.js
index dae1fa3ebda..554f6083bd8 100644
--- a/server/methods/accounts/accounts.app-test.js
+++ b/server/methods/accounts/accounts.app-test.js
@@ -1,11 +1,14 @@
/* eslint dot-notation: 0 */
import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { Factory } from "meteor/dburles:factory";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
import { Accounts as MeteorAccount } from "meteor/accounts-base";
-import { Accounts, Packages, Orders, Products, Shops, Cart } from "/lib/collections";
-import { Reaction } from "/server/api";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
+import { Accounts, Packages, Orders, Products, Shops, Cart } from "/lib/collections";
+import { Reaction } from "/server/api";
import { getShop, getAddress } from "/server/imports/fixtures/shops";
import Fixtures from "/server/imports/fixtures";
@@ -99,7 +102,7 @@ describe("Account Meteor method ", function () {
account = Accounts.findOne(account._id);
expect(account.profile.addressBook.length).to.equal(2);
const newAddress = account.profile.addressBook[
- account.profile.addressBook.length - 1];
+ account.profile.addressBook.length - 1];
delete newAddress._id;
expect(_.isEqual(address, newAddress)).to.be.true;
return done();
@@ -160,41 +163,41 @@ describe("Account Meteor method ", function () {
it("should disabled isShipping/BillingDefault properties inside sibling" +
" address if we enable their while adding",
- function (done) {
- const account = Factory.create("account");
- sandbox.stub(Meteor, "userId", function () {
- return account.userId;
- });
- sandbox.stub(Reaction, "getShopId", function () {
- return shopId;
- });
- const sessionId = Random.id(); // Required for creating a cart
- spyOnMethod("setShipmentAddress", account.userId);
- spyOnMethod("setPaymentAddress", account.userId);
-
- Meteor.call("cart/createCart", account.userId, sessionId);
- // cart was created without any default addresses, we need to add one
- const address = Object.assign({}, getAddress(), {
- isShippingDefault: true,
- isBillingDefault: true
- });
- Meteor.call("accounts/addressBookAdd", address);
-
- // Now we need to override cart with new address
- const newAddress = Object.assign({}, getAddress(), {
- _id: Random.id(),
- isShippingDefault: true,
- isBillingDefault: true
- });
- Meteor.call("accounts/addressBookAdd", newAddress);
-
- // now we need to get address ids from cart and compare their
- const cart = Cart.findOne({ userId: account.userId });
- expect(cart.shipping[0].address._id).to.equal(newAddress._id);
- expect(cart.billing[0].address._id).to.equal(newAddress._id);
-
- return done();
+ function (done) {
+ const account = Factory.create("account");
+ sandbox.stub(Meteor, "userId", function () {
+ return account.userId;
+ });
+ sandbox.stub(Reaction, "getShopId", function () {
+ return shopId;
+ });
+ const sessionId = Random.id(); // Required for creating a cart
+ spyOnMethod("setShipmentAddress", account.userId);
+ spyOnMethod("setPaymentAddress", account.userId);
+
+ Meteor.call("cart/createCart", account.userId, sessionId);
+ // cart was created without any default addresses, we need to add one
+ const address = Object.assign({}, getAddress(), {
+ isShippingDefault: true,
+ isBillingDefault: true
+ });
+ Meteor.call("accounts/addressBookAdd", address);
+
+ // Now we need to override cart with new address
+ const newAddress = Object.assign({}, getAddress(), {
+ _id: Random.id(),
+ isShippingDefault: true,
+ isBillingDefault: true
});
+ Meteor.call("accounts/addressBookAdd", newAddress);
+
+ // now we need to get address ids from cart and compare their
+ const cart = Cart.findOne({ userId: account.userId });
+ expect(cart.shipping[0].address._id).to.equal(newAddress._id);
+ expect(cart.billing[0].address._id).to.equal(newAddress._id);
+
+ return done();
+ });
});
describe("addressBookUpdate", function () {
@@ -348,41 +351,41 @@ describe("Account Meteor method ", function () {
it("should disable isShipping/BillingDefault properties inside sibling" +
" address if we enable them while editing",
- function (done) {
- let account = Factory.create("account");
- spyOnMethod("setShipmentAddress", account.userId);
- spyOnMethod("setPaymentAddress", account.userId);
- sandbox.stub(Meteor, "userId", function () {
- return account.userId;
- });
- sandbox.stub(Reaction, "getShopId", () => shopId);
- Meteor.call("cart/createCart", account.userId, sessionId);
- // cart was created without any default addresses, we need to add one
- let address = Object.assign({}, account.profile.addressBook[0], {
- isShippingDefault: true,
- isBillingDefault: true
- });
- Meteor.call("accounts/addressBookUpdate", address);
-
- // we add new address with disabled defaults
- address = Object.assign({}, getAddress(), {
- _id: Random.id(),
- isShippingDefault: false,
- isBillingDefault: false
- });
- Meteor.call("accounts/addressBookAdd", address);
- // now we can test edit
- Object.assign(address, {
- isShippingDefault: true,
- isBillingDefault: true
- });
- Meteor.call("accounts/addressBookUpdate", address);
- account = Accounts.findOne(account._id);
-
- expect(account.profile.addressBook[0].isBillingDefault).to.be.false;
- expect(account.profile.addressBook[0].isShippingDefault).to.be.false;
- return done();
- }
+ function (done) {
+ let account = Factory.create("account");
+ spyOnMethod("setShipmentAddress", account.userId);
+ spyOnMethod("setPaymentAddress", account.userId);
+ sandbox.stub(Meteor, "userId", function () {
+ return account.userId;
+ });
+ sandbox.stub(Reaction, "getShopId", () => shopId);
+ Meteor.call("cart/createCart", account.userId, sessionId);
+ // cart was created without any default addresses, we need to add one
+ let address = Object.assign({}, account.profile.addressBook[0], {
+ isShippingDefault: true,
+ isBillingDefault: true
+ });
+ Meteor.call("accounts/addressBookUpdate", address);
+
+ // we add new address with disabled defaults
+ address = Object.assign({}, getAddress(), {
+ _id: Random.id(),
+ isShippingDefault: false,
+ isBillingDefault: false
+ });
+ Meteor.call("accounts/addressBookAdd", address);
+ // now we can test edit
+ Object.assign(address, {
+ isShippingDefault: true,
+ isBillingDefault: true
+ });
+ Meteor.call("accounts/addressBookUpdate", address);
+ account = Accounts.findOne(account._id);
+
+ expect(account.profile.addressBook[0].isBillingDefault).to.be.false;
+ expect(account.profile.addressBook[0].isShippingDefault).to.be.false;
+ return done();
+ }
);
it("should update cart default addresses via `type` argument", function () {
@@ -464,7 +467,7 @@ describe("Account Meteor method ", function () {
});
const accountUpdateSpy = sandbox.spy(Accounts, "update");
expect(() => Meteor.call("accounts/addressBookRemove",
- address2._id, account2.userId)).to.throw;
+ address2._id, account2.userId)).to.throw;
expect(accountUpdateSpy).to.not.have.been.called;
});
@@ -493,8 +496,8 @@ describe("Account Meteor method ", function () {
const createUserSpy = sandbox.spy(MeteorAccount, "createUser");
// create user
expect(() => Meteor.call("accounts/inviteShopMember", shopId,
- fakeUser.emails[0].address,
- fakeUser.profile.addressBook[0].fullName)).to.throw(Meteor.Error, /Access denied/);
+ fakeUser.emails[0].address,
+ fakeUser.profile.addressBook[0].fullName)).to.throw(Meteor.Error, /Access denied/);
// expect that createUser shouldnt have run
expect(createUserSpy).to.not.have.been.called;
// expect(createUserSpy).to.not.have.been.called.with({
diff --git a/server/methods/accounts/accounts.js b/server/methods/accounts/accounts.js
index 51a66cdfd96..1867b1d1aa7 100644
--- a/server/methods/accounts/accounts.js
+++ b/server/methods/accounts/accounts.js
@@ -2,8 +2,11 @@ import _ from "lodash";
import moment from "moment";
import path from "path";
import { Meteor } from "meteor/meteor";
+import { Random } from "meteor/random";
import { Accounts as MeteorAccounts } from "meteor/accounts-base";
import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
+import { SSR } from "meteor/meteorhacks:ssr";
import { Accounts, Cart, Media, Shops, Packages } from "/lib/collections";
import * as Schemas from "/lib/collections/schemas";
import { Logger, Reaction } from "/server/api";
@@ -758,6 +761,22 @@ export function setUserPermissions(userId, permissions, group) {
}
}
+/**
+ * accounts/createFallbackLoginToken
+ * @returns {String} returns a new loginToken for current user,
+ * that can be used for special login scenarios - e.g. store the
+ * newly created token as cookie on the browser, if the client
+ * does not offer local storage.
+ */
+export function createFallbackLoginToken() {
+ if (this.userId) {
+ const stampedLoginToken = MeteorAccounts._generateStampedLoginToken();
+ const loginToken = stampedLoginToken.token;
+ MeteorAccounts._insertLoginToken(this.userId, stampedLoginToken);
+ return loginToken;
+ }
+}
+
/**
* Reaction Account Methods
*/
@@ -772,5 +791,6 @@ Meteor.methods({
"accounts/sendWelcomeEmail": sendWelcomeEmail,
"accounts/addUserPermissions": addUserPermissions,
"accounts/removeUserPermissions": removeUserPermissions,
- "accounts/setUserPermissions": setUserPermissions
+ "accounts/setUserPermissions": setUserPermissions,
+ "accounts/createFallbackLoginToken": createFallbackLoginToken
});
diff --git a/server/methods/accounts/serviceConfiguration.js b/server/methods/accounts/serviceConfiguration.js
index 17053920b8d..07eac003480 100644
--- a/server/methods/accounts/serviceConfiguration.js
+++ b/server/methods/accounts/serviceConfiguration.js
@@ -1,4 +1,7 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { check } from "meteor/check";
+import { ServiceConfiguration } from "meteor/service-configuration";
import { Reaction } from "/server/api";
diff --git a/server/methods/catalog.app-test.js b/server/methods/catalog.app-test.js
index 4f55fa56e4a..b7bba564989 100644
--- a/server/methods/catalog.app-test.js
+++ b/server/methods/catalog.app-test.js
@@ -2,6 +2,7 @@
/* eslint no-loop-func: 0 */
import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Factory } from "meteor/dburles:factory";
import { Reaction } from "/server/api";
import { Products, Revisions, Tags } from "/lib/collections";
diff --git a/server/methods/catalog.js b/server/methods/catalog.js
index 13e9f974e36..3216c2d76b1 100644
--- a/server/methods/catalog.js
+++ b/server/methods/catalog.js
@@ -1,6 +1,7 @@
import _ from "lodash";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
import { EJSON } from "meteor/ejson";
-import { check } from "meteor/check";
import { Meteor } from "meteor/meteor";
import { copyFile, ReactionProduct } from "/lib/api";
import { ProductRevision as Catalog } from "/imports/plugins/core/revisions/server/hooks";
@@ -600,14 +601,14 @@ Meteor.methods({
uniqueShopIds.forEach((shopId) => {
if (!Reaction.hasPermission("createProduct", this.userId, shopId)) {
throw new Meteor.Error(403,
- "Access Denied");
+ "Access Denied");
}
});
} else {
// Single product was passed in - ensure that user has permission to clone
if (!Reaction.hasPermission("createProduct", this.userId, productOrArray.shopId)) {
throw new Meteor.Error(403,
- "Access Denied");
+ "Access Denied");
}
}
@@ -1302,7 +1303,7 @@ Meteor.methods({
if (typeof variant.title === "string" && !variant.title.length) {
variantValidator = false;
}
- if (typeof optionTitle === "string" && !optionTitle.length) {
+ if (typeof variant.optionTitle === "string" && !variant.optionTitle.length) {
variantValidator = false;
}
});
diff --git a/server/methods/core/cart-create.app-test.js b/server/methods/core/cart-create.app-test.js
index f7a4219e931..e3ef591e7f3 100644
--- a/server/methods/core/cart-create.app-test.js
+++ b/server/methods/core/cart-create.app-test.js
@@ -1,6 +1,7 @@
/* eslint dot-notation: 0 */
-
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
import { Factory } from "meteor/dburles:factory";
import { Reaction } from "/server/api";
import { Cart, Products, Accounts } from "/lib/collections";
diff --git a/server/methods/core/cart-merge.app-test.js b/server/methods/core/cart-merge.app-test.js
index 181e7a64493..525847a70c8 100644
--- a/server/methods/core/cart-merge.app-test.js
+++ b/server/methods/core/cart-merge.app-test.js
@@ -1,5 +1,8 @@
/* eslint dot-notation: 0 */
import { Meteor } from "meteor/meteor";
+import { Factory } from "meteor/dburles:factory";
+import { Random } from "meteor/random";
+import { check, Match } from "meteor/check";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { getShop } from "/server/imports/fixtures/shops";
diff --git a/server/methods/core/cart-remove.app-test.js b/server/methods/core/cart-remove.app-test.js
index 2789080c496..862fd8b7b19 100644
--- a/server/methods/core/cart-remove.app-test.js
+++ b/server/methods/core/cart-remove.app-test.js
@@ -1,5 +1,8 @@
/* eslint dot-notation: 0 */
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Random } from "meteor/random";
+import { Factory } from "meteor/dburles:factory";
import { assert, expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { getShop } from "/server/imports/fixtures/shops";
diff --git a/server/methods/core/cart.js b/server/methods/core/cart.js
index 496f768782f..c91fbed82c6 100644
--- a/server/methods/core/cart.js
+++ b/server/methods/core/cart.js
@@ -1,6 +1,8 @@
import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
+import { Random } from "meteor/random";
import * as Collections from "/lib/collections";
import { Logger, Reaction } from "/server/api";
@@ -21,9 +23,9 @@ function quantityProcessing(product, variant, itemQty = 1) {
if (MIN > MAX) {
Logger.debug(`productId: ${product._id}, variantId ${variant._id
- }: inventoryQuantity lower then minimum order`);
+ }: inventoryQuantity lower then minimum order`);
throw new Meteor.Error(`productId: ${product._id}, variantId ${variant._id
- }: inventoryQuantity lower then minimum order`);
+ }: inventoryQuantity lower then minimum order`);
}
// TODO: think about #152 implementation here
@@ -142,7 +144,7 @@ Meteor.methods({
Logger.debug(
`merge cart: begin merge processing of session ${
- sessionId} into: ${currentCart._id}`
+ sessionId} into: ${currentCart._id}`
);
// loop through session carts and merge into user cart
sessionCarts.forEach(sessionCart => {
@@ -198,7 +200,7 @@ Meteor.methods({
Meteor.users.remove(sessionCart.userId);
Logger.debug(
`merge cart: delete cart ${
- sessionCart._id} and user: ${sessionCart.userId}`
+ sessionCart._id} and user: ${sessionCart.userId}`
);
}
Logger.debug(
diff --git a/server/methods/core/methods.app-test.js b/server/methods/core/methods.app-test.js
index d6ee3974e8f..46485d5d2d2 100644
--- a/server/methods/core/methods.app-test.js
+++ b/server/methods/core/methods.app-test.js
@@ -1,4 +1,6 @@
import { Meteor } from "meteor/meteor";
+import { Roles } from "meteor/alanning:roles";
+import { Factory } from "meteor/dburles:factory";
import { Shops, Tags } from "/lib/collections";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
diff --git a/server/methods/core/orders.app-test.js b/server/methods/core/orders.app-test.js
index d89fbcf45aa..02da477e25f 100644
--- a/server/methods/core/orders.app-test.js
+++ b/server/methods/core/orders.app-test.js
@@ -1,5 +1,6 @@
import accounting from "accounting-js";
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Factory } from "meteor/dburles:factory";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
@@ -97,7 +98,7 @@ describe("orders test", function () {
});
it("should return the product to stock ", function () {
- // Mock user permissions
+ // Mock user permissions
sandbox.stub(Reaction, "hasPermission", () => true);
const returnToStock = true;
const previousProduct = Products.findOne({ _id: order.items[0].variants._id });
diff --git a/server/methods/core/orders.js b/server/methods/core/orders.js
index a1ec90bb258..5b8ab41f286 100644
--- a/server/methods/core/orders.js
+++ b/server/methods/core/orders.js
@@ -4,7 +4,8 @@ import moment from "moment";
import accounting from "accounting-js";
import Future from "fibers/future";
import { Meteor } from "meteor/meteor";
-import { check } from "meteor/check";
+import { check, Match } from "meteor/check";
+import { SSR } from "meteor/meteorhacks:ssr";
import { getSlug } from "/lib/api";
import { Media, Orders, Products, Shops, Packages } from "/lib/collections";
import * as Schemas from "/lib/collections/schemas";
diff --git a/server/methods/core/registry.js b/server/methods/core/registry.js
index 44ad9cb6297..269a3249bf5 100644
--- a/server/methods/core/registry.js
+++ b/server/methods/core/registry.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
import { check } from "meteor/check";
import { Packages } from "/lib/collections";
diff --git a/server/methods/core/shop.js b/server/methods/core/shop.js
index 557382f4367..be7d63469f1 100644
--- a/server/methods/core/shop.js
+++ b/server/methods/core/shop.js
@@ -1,4 +1,7 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { Roles } from "meteor/alanning:roles";
+import { Random } from "meteor/random";
import { check, Match } from "meteor/check";
import { HTTP } from "meteor/http";
import { Job } from "meteor/vsivsi:job-collection";
@@ -253,7 +256,7 @@ Meteor.methods({
// get a rate request, using base currency
const rateUrl =
`https://openexchangerates.org/api/latest.json?base=${
- baseCurrency}&app_id=${openexchangeratesAppId}`;
+ baseCurrency}&app_id=${openexchangeratesAppId}`;
let rateResults;
// We can get an error if we try to change the base currency with a simple
@@ -567,7 +570,6 @@ Meteor.methods({
/**
* shop/hideHeaderTag
* @param {String} tagId - method to remove tag navigation tags
- * @param {String} currentTagId - currentTagId
* @return {String} returns remove result
*/
"shop/hideHeaderTag": function (tagId) {
diff --git a/server/methods/core/shops.app-test.js b/server/methods/core/shops.app-test.js
index f94c27b388d..b2b47cf4920 100644
--- a/server/methods/core/shops.app-test.js
+++ b/server/methods/core/shops.app-test.js
@@ -1,9 +1,8 @@
/* eslint dot-notation: 0 */
-import { Factory } from "meteor/dburles:factory";
-
+import { Meteor } from "meteor/meteor";
import { expect } from "meteor/practicalmeteor:chai";
+import { Factory } from "meteor/dburles:factory";
import { sinon, stubs, spies } from "meteor/practicalmeteor:sinon";
-
import Fixtures from "/server/imports/fixtures";
import { Reaction } from "/server/api";
import { Shops } from "/lib/collections";
diff --git a/server/methods/core/workflows/orders.js b/server/methods/core/workflows/orders.js
index 0be791540a6..40fd025885c 100644
--- a/server/methods/core/workflows/orders.js
+++ b/server/methods/core/workflows/orders.js
@@ -1,4 +1,6 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Reaction } from "/server/api";
diff --git a/server/methods/i18n.js b/server/methods/i18n.js
index b46a5c2b414..18469772764 100644
--- a/server/methods/i18n.js
+++ b/server/methods/i18n.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Translations } from "/lib/collections";
import { Reaction } from "/server/api";
import { loadCoreTranslations } from "/server/startup/i18n";
diff --git a/server/methods/media.js b/server/methods/media.js
index 06d3790b8ca..ab804d6a821 100644
--- a/server/methods/media.js
+++ b/server/methods/media.js
@@ -1,3 +1,6 @@
+import { Meteor } from "meteor/meteor";
+import { SimpleSchema } from "meteor/aldeed:simple-schema";
+import { ValidatedMethod } from "meteor/mdg:validated-method";
import { Media } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/methods/translations.app-test.js b/server/methods/translations.app-test.js
index 16f251038c4..80dafdcf83e 100644
--- a/server/methods/translations.app-test.js
+++ b/server/methods/translations.app-test.js
@@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
+import { Factory } from "meteor/dburles:factory";
import { Roles } from "meteor/alanning:roles";
import { Translations } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/publications/accounts/serviceConfiguration.js b/server/publications/accounts/serviceConfiguration.js
index a0cd7177136..4a9388541a2 100644
--- a/server/publications/accounts/serviceConfiguration.js
+++ b/server/publications/accounts/serviceConfiguration.js
@@ -1,3 +1,7 @@
+import { ServiceConfiguration } from "meteor/service-configuration";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Reaction } from "/server/api";
/**
@@ -12,7 +16,7 @@ Meteor.publish("ServiceConfiguration", function (checkUserId) {
}
// Admins and account managers can manage the login methods for the shop
if (Roles.userIsInRole(this.userId, ["owner", "admin", "dashboard/accounts"],
- Reaction.getShopId())) {
+ Reaction.getShopId())) {
return ServiceConfiguration.configurations.find({}, {
fields: {
secret: 1
diff --git a/server/publications/collections/accounts.js b/server/publications/collections/accounts.js
index 5e7695f1bc4..42f41a25a3c 100644
--- a/server/publications/collections/accounts.js
+++ b/server/publications/collections/accounts.js
@@ -1,4 +1,7 @@
import _ from "lodash";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import * as Collections from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/publications/collections/cart-publications.app-test.js b/server/publications/collections/cart-publications.app-test.js
index 21ca5a1a0b4..ed7a014ba43 100644
--- a/server/publications/collections/cart-publications.app-test.js
+++ b/server/publications/collections/cart-publications.app-test.js
@@ -1,5 +1,7 @@
/* eslint dot-notation: 0 */
import { Meteor } from "meteor/meteor";
+import { Random } from "meteor/random";
+import { Factory } from "meteor/dburles:factory";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { getShop } from "/server/imports/fixtures/shops";
diff --git a/server/publications/collections/cart.js b/server/publications/collections/cart.js
index c2dd6715381..6365638efa4 100644
--- a/server/publications/collections/cart.js
+++ b/server/publications/collections/cart.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Cart, Media } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/publications/collections/media.js b/server/publications/collections/media.js
index 3789d6feead..50424f62068 100644
--- a/server/publications/collections/media.js
+++ b/server/publications/collections/media.js
@@ -1,100 +1,80 @@
-import { Reaction } from "/lib/api";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Media, Revisions } from "/lib/collections";
+import { Reaction } from "/server/api";
import { RevisionApi } from "/imports/plugins/core/revisions/lib/api/revisions";
-//
-// define search filters as a schema so we can validate
-// params supplied to the Media publication
-//
-const filters = new SimpleSchema({
- shops: {
- type: [String],
- optional: true
- },
- products: {
- type: [String],
- optional: true
- }
-});
-
-
/**
* CollectionFS - Image/Video Publication
- * @params {Object} mediaFilters - filters of Media (Products/Shops)
+ * @params {Array} shops - array of current shop object
*/
-Meteor.publish("Media", function (mediaFilters) {
- check(mediaFilters, Match.OneOf(undefined, filters));
-
- const shopId = Reaction.getSellerShopId(this.userId);
+Meteor.publish("Media", function (shops) {
+ check(shops, Match.Optional(Array));
+ let selector;
+ const shopId = Reaction.getShopId();
if (!shopId) {
return this.ready();
}
-
- // Default selector gets only published brandAsset of shop (main image of shop)
- const selector = {
- "metadata.type": "brandAsset",
- "metadata.workflow": [null, "published"],
- "metadata.shopId": shopId
- };
-
- if (mediaFilters) {
- if (mediaFilters.products) {
- const products = mediaFilters.products;
- delete selector["metadata.type"];
- delete selector["metadata.shopId"];
- selector["metadata.productId"] = {
- $in: products
- };
- } else if (mediaFilters.shops) {
- const shops = mediaFilters.shops;
- selector["metadata.shopId"] = {
+ if (shopId) {
+ selector = {
+ "metadata.shopId": shopId
+ };
+ }
+ if (shops) {
+ selector = {
+ "metadata.shopId": {
$in: shops
- };
- }
+ }
+ };
}
- // product editors can see published & unpublished images
- if (Reaction.hasPermission(["createProduct"], this.userId, shopId)) {
- selector["metadata.workflow"] = { $nin: ["archived"] };
+ // Product editors can see both published and unpublished images
+ if (!Reaction.hasPermission(["createProduct"], this.userId)) {
+ selector["metadata.workflow"] = {
+ $in: [null, "published"]
+ };
+ } else {
+ // but no one gets to see archived images
+ selector["metadata.workflow"] = {
+ $nin: ["archived"]
+ };
+ }
- if (RevisionApi.isRevisionControlEnabled()) {
- const revisionHandle = Revisions.find({
- "documentType": "image",
- "workflow.status": { $nin: ["revision/published"] }
- }).observe({
- added: (revision) => {
+ if (RevisionApi.isRevisionControlEnabled()) {
+ const revisionHandle = Revisions.find({
+ "documentType": "image",
+ "workflow.status": { $nin: [ "revision/published"] }
+ }).observe({
+ added: (revision) => {
+ const media = Media.findOne(revision.documentId);
+ if (media) {
+ this.added("Media", media._id, media);
+ this.added("Revisions", revision._id, revision);
+ }
+ },
+ changed: (revision) => {
+ const media = Media.findOne(revision.documentId);
+ this.changed("Media", media._id, media);
+ this.changed("Revisions", revision._id, revision);
+ },
+ removed: (revision) => {
+ if (revision) {
const media = Media.findOne(revision.documentId);
if (media) {
- this.added("Media", media._id, media);
- this.added("Revisions", revision._id, revision);
- }
- },
- changed: (revision) => {
- const media = Media.findOne(revision.documentId);
- this.changed("Media", media._id, media);
- this.changed("Revisions", revision._id, revision);
- },
- removed: (revision) => {
- if (revision) {
+ this.removed("Media", media._id, media);
this.removed("Revisions", revision._id, revision);
- const media = Media.findOne(revision.documentId);
- if (media) {
- this.removed("Media", media._id, media);
- }
}
}
- });
+ }
+ });
- this.onStop(() => {
- revisionHandle.stop();
- });
- }
+ this.onStop(() => {
+ revisionHandle.stop();
+ });
}
- return Media.find(selector, {
- sort: {
- "metadata.priority": 1
- }
+ return Media.find({
+ "metadata.type": "brandAsset"
});
});
diff --git a/server/publications/collections/members-publications.app-test.js b/server/publications/collections/members-publications.app-test.js
index b543c1e7fec..ebb2bbc1ed3 100644
--- a/server/publications/collections/members-publications.app-test.js
+++ b/server/publications/collections/members-publications.app-test.js
@@ -1,5 +1,7 @@
/* eslint dot-notation: 0 */
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { Factory } from "meteor/dburles:factory";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { Roles } from "meteor/alanning:roles";
diff --git a/server/publications/collections/orders-publications.app-test.js b/server/publications/collections/orders-publications.app-test.js
index 70bb33b512c..b75e52b2ef6 100644
--- a/server/publications/collections/orders-publications.app-test.js
+++ b/server/publications/collections/orders-publications.app-test.js
@@ -1,6 +1,7 @@
/* eslint dot-notation: 0 */
import { Meteor } from "meteor/meteor";
-
+import { check, Match } from "meteor/check";
+import { Factory } from "meteor/dburles:factory";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { Roles } from "meteor/alanning:roles";
diff --git a/server/publications/collections/orders.js b/server/publications/collections/orders.js
index 8c8e3268685..83bc3a2ae0c 100644
--- a/server/publications/collections/orders.js
+++ b/server/publications/collections/orders.js
@@ -1,3 +1,6 @@
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Counts } from "meteor/tmeasday:publish-counts";
import { Orders } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/publications/collections/packages.js b/server/publications/collections/packages.js
index 88b4948098b..7a2f7de8b45 100644
--- a/server/publications/collections/packages.js
+++ b/server/publications/collections/packages.js
@@ -1,7 +1,10 @@
+import _ from "lodash";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Packages } from "/lib/collections";
import { Reaction } from "/server/api";
import { translateRegistry } from "/lib/api";
-import { Roles } from "meteor/alanning:roles";
/**
* Packages contains user specific configuration
diff --git a/server/publications/collections/product-publications.app-test.js b/server/publications/collections/product-publications.app-test.js
index e456a5106a7..601c15ea988 100644
--- a/server/publications/collections/product-publications.app-test.js
+++ b/server/publications/collections/product-publications.app-test.js
@@ -1,4 +1,5 @@
/* eslint dot-notation: 0 */
+import { Random } from "meteor/random";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import { Roles } from "meteor/alanning:roles";
@@ -312,7 +313,12 @@ describe("Publication", function () {
collector.collect("Product", "my", (collections) => {
const products = collections.Products;
- expect(products.length).to.equal(0);
+ if (products) {
+ expect(products.length).to.equal(0);
+ } else {
+ expect(products).to.be.undefined;
+ }
+
if (!isDone) {
isDone = true;
diff --git a/server/publications/collections/product.js b/server/publications/collections/product.js
index 33423b39347..cd9953f1367 100644
--- a/server/publications/collections/product.js
+++ b/server/publications/collections/product.js
@@ -1,6 +1,8 @@
-import { Reaction } from "/lib/api";
-import { Logger } from "/server/api";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Media, Products, Revisions } from "/lib/collections";
+import { Logger, Reaction } from "/server/api";
import { RevisionApi } from "/imports/plugins/core/revisions/lib/api/revisions";
export function findProductMedia(publicationInstance, productIds) {
@@ -23,15 +25,14 @@ export function findProductMedia(publicationInstance, productIds) {
selector["metadata.shopId"] = shopId;
}
- // By default only images that are not "archived"
- // and are either "published" or null are published
+ // No one needs to see archived images on products
selector["metadata.workflow"] = {
- $nin: ["archived"],
- $in: [null, "published"]
+ $nin: ["archived"]
};
- if (Reaction.hasPermission(["createProduct"], publicationInstance.userId)) {
- delete selector["metadata.workflow"].$in;
+ // Product editors can see both published and unpublished images
+ if (!Reaction.hasPermission(["createProduct"], publicationInstance.userId)) {
+ selector["metadata.workflow"].$in = [null, "published"];
}
return Media.find(selector, {
@@ -39,29 +40,6 @@ export function findProductMedia(publicationInstance, productIds) {
"metadata.priority": 1
}
});
- // Removed SellerShopId related media publication for now.
- // const isUserOwnerOrModerator = Reaction.hasPermission(["owner", "moderator"], publicationInstance.userId);
- //
- // if (isUserOwnerOrModerator) {
- // selector["metadata.workflow"] = { $nin: ["archived"] };
- // } else {
- // // get seller-shop id if user is a seller;
- // const sellerShopId = Reaction.getSellerShopId(publicationInstance.userId, true);
- // // sellers can see unpublished images only of their shop
- // if (sellerShopId) {
- // selector.$or = [{
- // "metadata.workflow": { $in: [null, "published"] }
- // }, {
- // "metadata.shopId": sellerShopId
- // }];
- // }
- // }
- //
- // return Media.find(selector, {
- // sort: {
- // "metadata.priority": 1
- // }
- // });
}
@@ -72,103 +50,75 @@ export function findProductMedia(publicationInstance, productIds) {
*/
Meteor.publish("Product", function (productId) {
check(productId, Match.OptionalOrNull(String));
-
if (!productId) {
Logger.debug("ignoring null request on Product subscription");
return this.ready();
}
-
- // verify that parent shop is ready
+ let _id;
const shop = Reaction.getCurrentShop();
+ // verify that shop is ready
if (typeof shop !== "object") {
return this.ready();
}
+ let selector = {};
+ selector.isVisible = true;
+ selector.isDeleted = { $in: [null, false] };
- let id;
- let productShopId;
-
- // Default selector. Any changes come via specific roles
- const selector = {
- isDeleted: { $in: [null, false] },
- isVisible: true
- };
-
- // Permits admins to view both visible and invisible products.
- if (Roles.userIsInRole(this.userId, ["owner", "admin", "createProduct"], shop._id)) {
- selector.isVisible = { $in: [true, false] };
+ if (Roles.userIsInRole(this.userId, ["owner", "admin", "createProduct"],
+ shop._id)) {
+ selector.isVisible = {
+ $in: [true, false]
+ };
}
-
// TODO review for REGEX / DOS vulnerabilities.
if (productId.match(/^[23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz]{17}$/)) {
- // selector._id = productId;
+ selector._id = productId;
// TODO try/catch here because we can have product handle passed by such regex
- id = productId;
-
- let product = Products.findOne({ _id: id });
-
- if (!product) {
- // if we didn't find the product by id, see if it was a 17 character handle
- product = Products.findOne({
- handle: {
- $regex: id,
- $options: "i"
- }
- });
- }
-
- if (product) {
- // Take productShopId in order to check if user can edit this product or view its revisions
- productShopId = product.shopId;
-
- // Ensure that the id is the productId and not the handle
- id = product._id;
- } else {
- // No Product found, return empty cursor;
- return this.ready();
- }
+ _id = productId;
} else {
- // if productId doesn't match the id regex,
- // see if there's a product with a matching handle
- const product = Products.findOne({
- handle: {
- $regex: productId,
- $options: "i"
- }
- });
-
- if (product) {
- id = product._id;
- productShopId = product.shopId;
+ selector.handle = {
+ $regex: productId,
+ $options: "i"
+ };
+ const products = Products.find(selector).fetch();
+ if (products.length > 0) {
+ _id = products[0]._id;
} else {
return this.ready();
}
}
- // Begin selector for product
- // We don't need handle anymore(we got product's id in the previous step)
- // Try to find a product with the _is as an Random.id()
- // Try to find a product variant with _id using the ancestors array
- selector.$or = [{
- _id: id
- }, {
- ancestors: {
- $in: [id]
- }
- }];
+ // Selector for hih?
+ selector = {
+ isVisible: true,
+ isDeleted: { $in: [null, false] },
+ $or: [
+ { handle: _id },
+ { _id: _id },
+ {
+ ancestors: {
+ $in: [_id]
+ }
+ }
+ ]
+ };
+
+ // Authorized content curators for the shop get special publication of the product
+ // all all relevant revisions all is one package
+ if (Roles.userIsInRole(this.userId, ["owner", "admin", "createProduct"], shop._id)) {
+ selector.isVisible = {
+ $in: [true, false, undefined]
+ };
- // Authorized content curators of the shop get special publication of the product
- // all relevant revisions all is one package
- if (Reaction.hasPermission("createProduct", this.userId, productShopId)) {
- delete selector.isVisible;
if (RevisionApi.isRevisionControlEnabled()) {
const productCursor = Products.find(selector);
const productIds = productCursor.map(p => p._id);
const handle = productCursor.observeChanges({
- added: (docId, fields) => {
+ added: (id, fields) => {
const revisions = Revisions.find({
- "documentId": docId,
+ "documentId": id,
"workflow.status": {
$nin: [
"revision/published"
@@ -177,11 +127,11 @@ Meteor.publish("Product", function (productId) {
}).fetch();
fields.__revisions = revisions;
- this.added("Products", docId, fields);
+ this.added("Products", id, fields);
},
- changed: (docId, fields) => {
+ changed: (id, fields) => {
const revisions = Revisions.find({
- "documentId": docId,
+ "documentId": id,
"workflow.status": {
$nin: [
"revision/published"
@@ -190,10 +140,10 @@ Meteor.publish("Product", function (productId) {
}).fetch();
fields.__revisions = revisions;
- this.changed("Products", docId, fields);
+ this.changed("Products", id, fields);
},
- removed: (docId) => {
- this.removed("Products", docId);
+ removed: (id) => {
+ this.removed("Products", id);
}
});
@@ -238,13 +188,8 @@ Meteor.publish("Product", function (productId) {
product = Products.findOne(revision.parentDocument);
}
if (product) {
- // Empty product's __revisions only if this revision is of the actual product
- // and not of a relative document( like an image) - in that case the revision has
- // a parentDocument field.
- if (!revision.parentDocument) {
- product.__revisions = [];
- this.changed("Products", product._id, product);
- }
+ product.__revisions = [];
+ this.changed("Products", product._id, product);
this.removed("Revisions", revision._id, revision);
}
}
diff --git a/server/publications/collections/products.js b/server/publications/collections/products.js
index b273130ba8b..12d7a639b13 100644
--- a/server/publications/collections/products.js
+++ b/server/publications/collections/products.js
@@ -1,4 +1,8 @@
import _ from "lodash";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
+import { SimpleSchema } from "meteor/aldeed:simple-schema";
import { Products, Revisions } from "/lib/collections";
import { Reaction, Logger } from "/server/api";
import { RevisionApi } from "/imports/plugins/core/revisions/lib/api/revisions";
@@ -60,10 +64,6 @@ const filters = new SimpleSchema({
"weight.max": {
type: String,
optional: true
- },
- "marketplace": {
- type: Boolean,
- optional: true
}
});
@@ -93,20 +93,8 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
}
if (shop) {
- // check if user has create-product access to parent Shop
- const hasCreateProductAccessToOwnerShop = Reaction.hasPermission(["create-shop"], this.userId, shop._id);
- // Shop Id of user has a Seller-Shop
- const sellerShopId = !hasCreateProductAccessToOwnerShop && Reaction.getSellerShopId(this.userId, true);
- // if a seller views a page with all his products he gets a special publication (with product revisions etc)
- let sellerViewsHisShop = false;
- if (sellerShopId && productFilters && Array.isArray(productFilters.shops) &&
- productFilters.shops.length === 1 && productFilters.shops[0] === sellerShopId) {
- sellerViewsHisShop = true;
- }
-
const selector = {};
- // Marketplace version of userInRole owner/admin/createProduct for shop
- if (hasCreateProductAccessToOwnerShop) {
+ if (Roles.userIsInRole(this.userId, ["owner", "admin", "createProduct"], shop._id)) {
_.extend(selector, {
isDeleted: { $in: [null, false] },
ancestors: { $exists: true },
@@ -121,20 +109,13 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
}
if (productFilters) {
- // Seller can see the variant products of his shop
- if (sellerViewsHisShop) {
- _.extend(selector, {
- shopId: sellerShopId,
- ancestors: { $exists: true }
- });
- } else if (productFilters.shops) {
+ // handle multiple shops
+ if (productFilters.shops) {
_.extend(selector, {
shopId: {
$in: productFilters.shops
}
});
- } else if (productFilters.marketplace) {
- delete selector.shopId;
}
// filter by tags
@@ -254,9 +235,10 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
}
} // end if productFilters
- // Authorized content curators of the shop get special publication of the product
+ // Authorized content curators fo the shop get special publication of the product
// with all relevant revisions all is one package
- if (hasCreateProductAccessToOwnerShop || sellerViewsHisShop) {
+
+ if (Roles.userIsInRole(this.userId, ["owner", "admin", "createProduct"], shop._id)) {
selector.isVisible = {
$in: [true, false, undefined]
};
@@ -334,8 +316,7 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
$nin: [
"revision/published"
]
- },
- "shopId": newSelector.shopId
+ }
}).observe({
added: (revision) => {
let product;
@@ -368,7 +349,7 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
if (!revision.documentType || revision.documentType === "product") {
product = Products.findOne(revision.documentId);
- } else if (revision.documentType === "image" || revision.documentType === "tag") {
+ } else if (revision.docuentType === "image" || revision.documentType === "tag") {
product = Products.findOne(revision.parentDocument);
}
if (product) {
@@ -379,6 +360,7 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
}
});
+
this.onStop(() => {
handle.stop();
handle2.stop();
@@ -414,34 +396,27 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so
limit: productScrollLimit
}).map(product => product._id);
+ let newSelector = selector;
- // TODO: Review this commented out code from Marketplace
- // TODO: Why is newSelector not used?
-
- // let newSelector = selector;
-
- // seems to not be used :
// Remove hashtag filter from selector (hashtags are not applied to variants, we need to get variants)
- // if (productFilters && productFilters.tags) {
-
- // newSelector = _.omit(selector, ["hashtags"]);
-
- // Re-configure selector to pick either Variants of one of the top-level products, or the top-level products in the filter
- // _.extend(newSelector, {
- // $or: [
- // {
- // ancestors: {
- // $in: productIds
- // }
- // }, {
- // hashtags: {
- // $in: productFilters.tags
- // }
- // }
- // ]
- // });
- // }
-
+ if (productFilters && productFilters.tags) {
+ newSelector = _.omit(selector, ["hashtags"]);
+
+ // Re-configure selector to pick either Variants of one of the top-level products, or the top-level products in the filter
+ _.extend(newSelector, {
+ $or: [
+ {
+ ancestors: {
+ $in: productIds
+ }
+ }, {
+ hashtags: {
+ $in: productFilters.tags
+ }
+ }
+ ]
+ });
+ }
// Returning Complete product tree for top level products to avoid sold out warning.
const productCursor = Products.find({
$or: [
diff --git a/server/publications/collections/revisions.js b/server/publications/collections/revisions.js
index 03b62f64755..011afb94943 100644
--- a/server/publications/collections/revisions.js
+++ b/server/publications/collections/revisions.js
@@ -1,6 +1,8 @@
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
+import { Roles } from "meteor/alanning:roles";
import { Revisions } from "/lib/collections";
import { Reaction } from "/server/api";
-import { check, Match } from "meteor/check";
/**
* accounts
diff --git a/server/publications/collections/sessions.js b/server/publications/collections/sessions.js
index 3122f9ea2e0..2dc94556030 100644
--- a/server/publications/collections/sessions.js
+++ b/server/publications/collections/sessions.js
@@ -1,4 +1,6 @@
import { Mongo } from "meteor/mongo";
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Reaction } from "/server/api";
/**
diff --git a/server/publications/collections/shops.js b/server/publications/collections/shops.js
index e3e2bd49f48..374b03b63e0 100644
--- a/server/publications/collections/shops.js
+++ b/server/publications/collections/shops.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { Reaction } from "/server/api";
/**
diff --git a/server/publications/collections/tags.js b/server/publications/collections/tags.js
index e5cd3009676..69eb69725ec 100644
--- a/server/publications/collections/tags.js
+++ b/server/publications/collections/tags.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { Tags } from "/lib/collections";
import { Reaction } from "/server/api";
diff --git a/server/publications/collections/themes.js b/server/publications/collections/themes.js
index a64257ac612..a90e3d52bbf 100644
--- a/server/publications/collections/themes.js
+++ b/server/publications/collections/themes.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { Themes } from "/lib/collections";
/**
diff --git a/server/publications/collections/translations.js b/server/publications/collections/translations.js
index 7aa4f573287..5d786834bf9 100644
--- a/server/publications/collections/translations.js
+++ b/server/publications/collections/translations.js
@@ -1,3 +1,5 @@
+import { Meteor } from "meteor/meteor";
+import { check, Match } from "meteor/check";
import { Shops, Translations } from "/lib/collections";
import { Reaction } from "/server/api";
@@ -17,7 +19,7 @@ Meteor.publish("Translations", function (languages) {
// set shop default
sessionLanguages.push(shopLanguage);
// lets get all these langauges
- if (typeof languages === "array") {
+ if (Array.isArray(languages)) {
sessionLanguages.concat(languages);
} else {
sessionLanguages.push(languages);
diff --git a/server/publications/email.js b/server/publications/email.js
index 8bee9c50545..b23ef4ee2da 100644
--- a/server/publications/email.js
+++ b/server/publications/email.js
@@ -1,6 +1,8 @@
import { Meteor } from "meteor/meteor";
-import { Jobs } from "/lib/collections";
+import { check, Match } from "meteor/check";
+import { Counts } from "meteor/tmeasday:publish-counts";
import { Roles } from "meteor/alanning:roles";
+import { Jobs } from "/lib/collections";
/**
* Email Job Logs
diff --git a/server/security/collections.js b/server/security/collections.js
index 6aabd3c656b..83d8ffd2c58 100644
--- a/server/security/collections.js
+++ b/server/security/collections.js
@@ -1,3 +1,6 @@
+import _ from "lodash";
+import { Security } from "meteor/ongoworks:security";
+import { Roles } from "meteor/alanning:roles";
import * as Collections from "/lib/collections";
import { Reaction, Hooks } from "/server/api";
@@ -37,6 +40,18 @@ export default function () {
* Define some additional rule chain methods
*/
+ Security.defineMethod("ifHasRoleForActiveShop", {
+ fetch: [],
+ transform: null,
+ allow(type, arg, userId) {
+ if (!arg) throw new Error("ifHasRole security rule method requires an argument");
+ if (arg.role) {
+ return Roles.userIsInRole(userId, arg.role, Reaction.getShopId());
+ }
+ return Roles.userIsInRole(userId, arg);
+ }
+ });
+
// use this rule for collections other than Shops
// matches this.shopId
Security.defineMethod("ifShopIdMatches", {
@@ -102,8 +117,7 @@ export default function () {
Packages,
Templates,
Jobs
- ]).ifHasRole({
- group: Reaction.getShopId(),
+ ]).ifHasRoleForActiveShop({
role: "admin"
}).ifShopIdMatches().exceptProps(["shopId"]).allowInClientCode();
@@ -111,8 +125,7 @@ export default function () {
* Permissive security for users with the "admin" role for FS.Collections
*/
- Security.permit(["insert", "update", "remove"]).collections([Media]).ifHasRole({
- group: Reaction.getShopId(),
+ Security.permit(["insert", "update", "remove"]).collections([Media]).ifHasRoleForActiveShop({
role: ["admin", "owner", "createProduct"]
}).ifFileBelongsToShop().allowInClientCode();
@@ -121,8 +134,7 @@ export default function () {
* remove their shop but may not insert one.
*/
- Shops.permit(["update", "remove"]).ifHasRole({
- group: Reaction.getShopId(),
+ Shops.permit(["update", "remove"]).ifHasRoleForActiveShop({
role: ["admin", "owner"]
}).ifShopIdMatchesThisId().allowInClientCode();
@@ -131,8 +143,7 @@ export default function () {
* remove products, but createProduct allows just for just a product editor
*/
- Products.permit(["insert", "update", "remove"]).ifHasRole({
- group: Reaction.getShopId(),
+ Products.permit(["insert", "update", "remove"]).ifHasRoleForActiveShop({
role: ["createProduct"]
}).ifShopIdMatches().allowInClientCode();
@@ -140,8 +151,7 @@ export default function () {
* Users with the "owner" role may remove orders for their shop
*/
- Orders.permit("remove").ifHasRole({
- group: Reaction.getShopId(),
+ Orders.permit("remove").ifHasRoleForActiveShop({
role: ["admin", "owner"]
}).ifShopIdMatches().exceptProps(["shopId"]).allowInClientCode();
@@ -152,16 +162,14 @@ export default function () {
* XXX should verify session match, but doesn't seem possible? Might have to move all cart updates to server methods, too?
*/
- Cart.permit(["insert", "update", "remove"]).ifHasRole({
- group: Reaction.getShopId(),
+ Cart.permit(["insert", "update", "remove"]).ifHasRoleForActiveShop({
role: ["anonymous", "guest"]
}).ifShopIdMatches().ifUserIdMatches().ifSessionIdMatches().allowInClientCode();
/*
* Users may update their own account
*/
- Collections.Accounts.permit(["insert", "update"]).ifHasRole({
- group: Reaction.getShopId(),
+ Collections.Accounts.permit(["insert", "update"]).ifHasRoleForActiveShop({
role: ["anonymous", "guest"]
}).ifUserIdMatches().allowInClientCode();
diff --git a/server/security/policy.js b/server/security/policy.js
index d459a721bff..4d122a29533 100644
--- a/server/security/policy.js
+++ b/server/security/policy.js
@@ -1,4 +1,5 @@
import url from "url";
+import { Meteor } from "meteor/meteor";
import { BrowserPolicy } from "meteor/browser-policy-common";
import { WebApp } from "meteor/webapp";
diff --git a/server/startup/accounts.js b/server/startup/accounts.js
index 9a270cc29b8..af458717d83 100644
--- a/server/startup/accounts.js
+++ b/server/startup/accounts.js
@@ -1,4 +1,6 @@
+import _ from "lodash";
import { Meteor } from "meteor/meteor";
+import { Accounts } from "meteor/accounts-base";
import * as Collections from "/lib/collections";
import { Hooks, Logger, Reaction } from "/server/api";
diff --git a/server/startup/load-data.js b/server/startup/load-data.js
index e1e60eb2246..aedb9faf365 100644
--- a/server/startup/load-data.js
+++ b/server/startup/load-data.js
@@ -1,3 +1,4 @@
+import { Meteor } from "meteor/meteor";
import { Shops } from "/lib/collections";
import { Logger, Reaction } from "/server/api";
import { Fixture } from "/server/api/core/import";
diff --git a/server/startup/packages.js b/server/startup/packages.js
index b279a15a866..7b2393c65c9 100644
--- a/server/startup/packages.js
+++ b/server/startup/packages.js
@@ -14,17 +14,16 @@ export default function () {
*/
// TODO: review this. not sure this does what it was intended to
Products.before.update((userId, product, fieldNames, modifier) => {
+ let updatedAt;
// handling product positions updates
if (_.indexOf(fieldNames, "positions") !== -1) {
if (modifier.$addToSet) {
if (modifier.$addToSet.positions) {
- createdAt = new Date();
updatedAt = new Date();
if (modifier.$addToSet.positions.$each) {
- for (position in modifier.$addToSet.positions.$each) {
+ for (const position in modifier.$addToSet.positions.$each) {
if ({}.hasOwnProperty.call(modifier.$addToSet.positions.$each,
- position)) {
- createdAt = new Date();
+ position)) {
updatedAt = new Date();
}
}
diff --git a/server/startup/registry.js b/server/startup/registry.js
index 3d82a067b3d..3e1751fb464 100644
--- a/server/startup/registry.js
+++ b/server/startup/registry.js
@@ -5,7 +5,7 @@ import initRegistry from "./registry/index";
export default function () {
initRegistry();
- // initialize shop registry when a new shop is added
+ // initialize shop registry when a new shop is added
Shops.find().observe({
added(doc) {
Reaction.setShopName(doc);