diff --git a/.env.example b/.env.example index 6b6a0f168c..ca172c1f47 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ MONGO_URL=mongodb://mongo:27017/reaction PORT=4080 PUBLIC_GRAPHQL_API_URL_HTTP=http://localhost:3000/graphql-beta PUBLIC_GRAPHQL_API_URL_WS=ws://localhost:3000/graphql-beta +PUBLIC_FILES_BASE_URL=http://localhost:3000 PUBLIC_I18N_BASE_URL=http://localhost:3000 PUBLIC_STOREFRONT_HOME_URL=http://localhost:4000 ROOT_URL=http://localhost:4080 diff --git a/client/modules/core/domains.js b/client/modules/core/domains.js deleted file mode 100644 index 7308a2ca3e..0000000000 --- a/client/modules/core/domains.js +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable node/no-deprecated-api */ -/* TODO: revisit `url.parse` throughout Reaction */ -import url from "url"; -import { composeUrl } from "/lib/core/url-common"; - -export const DomainsMixin = { - shopDomain() { - // We must use host rather than hostname in order to get the port, too, if present - return document.location.host; - }, - - /** - * absoluteUrl - * @summary a wrapper method for composeUrl (formerly Meteor.absoluteUrl) - * which sets the rootUrl to the current URL (instead of defaulting to - * ROOT_URL) - * @param {String} [pathOrOptions] A path to append to the root URL. Do not - * include a leading "`/`". absoluteUrl can be called with a - * single parameter, where pathOrOptions can be the path - * (String) or the options (Object) - * @param {Object} [optionalOptions] Optional options - * @param {Boolean} optionalOptions.secure Create an HTTPS URL. - * @param {Boolean} optionalOptions.replaceLocalhost Replace localhost with - * 127.0.0.1. Useful for services that don't recognize - * localhost as a domain name. - * @param {String} optionalOptions.rootUrl Override the default ROOT_URL from - * the server environment. - * For example: "`http://foo.example.com`" - * @returns {String} URL for the given path and options - */ - absoluteUrl(pathOrOptions, optionalOptions) { - let path; - let options; - - // path is optional - if (!optionalOptions && typeof pathOrOptions === "object") { - path = undefined; - options = Object.assign({}, pathOrOptions); - } else { - path = pathOrOptions; - options = Object.assign({}, optionalOptions); - } - - const hasRootUrl = "rootUrl" in options; - - // presumably, you could simply access window.location.href here, but when - // we get to server-side rendering, it is better to use a variable that gets - // assigned when we are sure we are rendering in the browser - const domain = this.shopDomain(); - - if (!hasRootUrl && domain) { - // unfortunately, the scheme/protocol is not available here, so let's - // get it from the default Meteor absoluteUrl options, then replace the - // host - const rootUrl = url.parse(composeUrl.defaultOptions.rootUrl); - rootUrl.host = domain; - options.rootUrl = rootUrl.format(); - } - - return composeUrl(path, options); - }, - - /** - * getDomain - * @summary extracts the domain name from the absoluteUrl. - * @param {Object} [options] inherited from absoluteUrl - * @param {Boolean} options.replaceLocalhost Replace localhost with 127.0.0.1. - * Useful for services that don't recognize localhost as a - * domain name. - * @param {String} options.rootUrl Override the default ROOT_URL from the - * server environment. For example: "`http://foo.example.com`" - * @returns {String} Domain/hostname for the given options - */ - getDomain(options) { - const absoluteUrl = this.absoluteUrl(options); - - if (!absoluteUrl) { return null; } - - const { hostname } = url.parse(absoluteUrl); - - return hostname; - } -}; diff --git a/client/modules/core/domains.test.js b/client/modules/core/domains.test.js deleted file mode 100644 index cb93098173..0000000000 --- a/client/modules/core/domains.test.js +++ /dev/null @@ -1,98 +0,0 @@ -/* eslint-disable require-jsdoc */ -jest.mock("/lib/core/url-common"); - -import { composeUrl } from "/lib/core/url-common"; -import { DomainsMixin } from "./domains"; - -describe("DomainsMixin", () => { - let path; - let connectionHost; - let rootUrl; - - beforeEach(() => { - // commonly used test vars - path = randomString(); - connectionHost = `${randomString()}.reactioncommerce.com`; - }); - - describe("#absoluteUrl", () => { - beforeEach(() => { - // mocking $ROOT_URL - const ROOT_URL = `https://${randomString()}.reactioncommerce.com`; - - if (!composeUrl.defaultOptions) { - composeUrl.defaultOptions = {}; - } - composeUrl.defaultOptions.rootUrl = ROOT_URL; - - rootUrl = `https://${document.location.host}/`; - }); - - describe("before the domain is set", () => { - test("wraps composeUrl without parameters", () => { - DomainsMixin.absoluteUrl(); - - expect(composeUrl).toHaveBeenCalledWith(undefined, { rootUrl }); - }); - - test("wraps composeUrl with path only", () => { - DomainsMixin.absoluteUrl(path); - - expect(composeUrl).toHaveBeenCalledWith(path, { rootUrl }); - }); - - test("wraps composeUrl with options only", () => { - const options = { optionA: 1, optionB: 2 }; - - DomainsMixin.absoluteUrl(options); - - expect(composeUrl).toHaveBeenCalledWith(undefined, { ...options, rootUrl }); - }); - - test("wraps composeUrl both a path and options", () => { - const options = { optionA: 1, optionB: 2 }; - - DomainsMixin.absoluteUrl(path, options); - - expect(composeUrl).toHaveBeenCalledWith(path, { ...options, rootUrl }); - }); - }); - - describe("within the domain set", () => { - beforeEach(() => { - jest.spyOn(DomainsMixin, "shopDomain").mockReturnValue(connectionHost); - - DomainsMixin.absoluteUrl(); - }); - - test("uses the current connection's host", () => { - expect(composeUrl) - .toHaveBeenCalledWith(undefined, expect.objectContaining({ - rootUrl: expect.stringContaining(connectionHost) - })); - }); - - test("uses $ROOT_URL's protocol/scheme", () => { - // this would he http:// if $ROOT_URL had not used https:// - expect(composeUrl) - .toHaveBeenCalledWith(undefined, expect.objectContaining({ - rootUrl: expect.stringMatching(/^https:\/\//) - })); - }); - }); - }); - - describe("#getDomain", () => { - test("returns the domain portion of absoluteUrl", () => { - jest.spyOn(DomainsMixin, "absoluteUrl") - .mockReturnValue(`https://${connectionHost}:80/${path}`); - - expect(DomainsMixin.getDomain()) - .toEqual(connectionHost); - }); - }); - - function randomString() { - return Math.random().toString(36); - } -}); diff --git a/client/modules/core/helpers/apps.js b/client/modules/core/helpers/apps.js deleted file mode 100644 index c99ca28575..0000000000 --- a/client/modules/core/helpers/apps.js +++ /dev/null @@ -1,172 +0,0 @@ -import _ from "lodash"; -import { Template } from "meteor/templating"; -import { Roles } from "meteor/alanning:roles"; -import { Reaction } from "/client/api"; -import { Packages, Shops } from "/lib/collections"; - -/** - * @typedef optionHash - * @type {Object} - * @property {String} name - name of a package. - * @property {String} provides -purpose of this package as identified to the registry - * @property {String} container - filter registry entries for matching container. - * @property {String} shopId - filter to only display results matching shopId, not returned - * @property {String} template - filter registry entries for matching template - */ - -/** - * @method Apps - * @param {optionHash} optionHash Option hash - * @returns {Object[]} returns an array of filtered, structure reactionApps - */ -export function Apps(optionHash) { - const { getUserId } = Reaction; - const filter = {}; - const registryFilter = {}; - let key; - const reactionApps = []; - let options = {}; - - // allow for object or option.hash - if (optionHash) { - if (optionHash.hash) { - options = optionHash.hash; - } else { - options = optionHash; - } - } - - // you could provide a shopId in optionHash - if (!options.shopId) { - options.shopId = Reaction.getShopId(); - } - - // Get the shop to determine shopType - const shop = Shops.findOne({ _id: options.shopId }) || {}; - const { shopType } = shop; - - // remove audience permissions for owner (still needed here for older/legacy calls) - if (Reaction.hasOwnerAccess() && options.audience) { - delete options.audience; - } - - // - // build filter to only get matching registry elements - // - for (key in options) { - if ({}.hasOwnProperty.call(options, key)) { - const value = options[key]; - if (value) { - if (!(key === "enabled" || key === "name" || key === "shopId")) { - filter[`registry.${key}`] = Array.isArray(options[key]) ? { $in: value } : value; - registryFilter[key] = value; - } else { - // perhaps not the best way to check but lets admin see all packages - if (!Reaction.hasOwnerAccess()) { - if (key !== "shopId") { - registryFilter[key] = value; - } - } - filter[key] = value; - } - } - } - } - - delete filter["registry.audience"]; // Temporarily remove "audience" key (see comment below) - - // TODO: Review fix for filter on Packages.find(filter) - // The current "filter" setup uses "audience" field which is not present in the registry array in most (if not all) docs - // in the Packages coll. - // For now, the audience checks (after the Package.find call) filters out the registry items based on permissions. But - // part of the filtering should have been handled by the Package.find call, if the "audience" filter works as it should. - Packages.find(filter).forEach((app) => { - const matchingRegistry = _.filter(app.registry, (item) => { - const itemFilter = _.cloneDeep(registryFilter); - - // check audience permissions only if they exist as part of optionHash and are part of the registry item - // ideally all routes should use it, safe for backwards compatibility though - // owner bypasses permissions - if (!Reaction.hasOwnerAccess() && item.permissions && registryFilter.audience) { - let hasAccess; - - for (const permission of registryFilter.audience) { - // This checks that the registry item contains a permissions matches with the user's permission for the shop - const hasPermissionToRegistryItem = item.permissions.indexOf(permission) > -1; - // This checks that the user's permission set have the right value that is on the registry item - const hasRoleAccessForShop = Roles.userIsInRole(getUserId(), permission, Reaction.getShopId()); - - // both checks must pass for access to be granted - if (hasPermissionToRegistryItem && hasRoleAccessForShop) { - hasAccess = true; - break; - } - } - - if (!hasAccess) { - return false; - } - - // safe to clean up now, and isMatch can ignore audience - delete itemFilter.audience; - } - - // Check that shopType matches showForShopType if option is present - if (item.showForShopTypes && - Array.isArray(item.showForShopTypes) && - !item.showForShopTypes.includes(shopType)) { - return false; - } - - // Check that shopType does not match hideForShopType if option is present - if (item.hideForShopTypes && - Array.isArray(item.hideForShopTypes) && - item.hideForShopTypes.includes(shopType)) { - return false; - } - - // Loop through all keys in the itemFilter - // each filter item should match exactly with the property in the registry or - // should be included in the array if that property is an array - return Object.keys(itemFilter).every((property) => { - const filterVal = itemFilter[property]; - const itemVal = item[property]; - - // Check to see if the registry entry is an array. - // Legacy registry entries could exist that use a string even when the schema requires an array. - // If it's not an array, the filter should match exactly - return Array.isArray(itemVal) ? itemVal.includes(filterVal) : itemVal === filterVal; - }); - }); - - for (const registry of matchingRegistry) { - reactionApps.push(registry); - } - }); - - // Sort apps by priority (registry.priority) - return reactionApps.sort((appA, appB) => appA.priority - appB.priority).slice(); -} - -/** - * - * @name reactionApps - * @memberof BlazeTemplateHelpers - * @summary Return an array of filtered, structured `reactionApps` as a Template Helper - * @example {{#each reactionApps provides="settings" name=packageName container=container}} - * @example {{#each reactionApps provides="userAccountDropdown" enabled=true}} - * @param {optionHash} optionHash Option hash - * @returns {Object[]} returns an array of filtered, structure reactionApps - * ```[{ - * enabled: true - * label: "Stripe" - * name: "reaction-stripe" - * packageId: "QqkGsQCDRhg2LSn8J" - * priority: 1 - * provides: "paymentMethod" - * template: "stripePaymentForm" - * etc: "additional properties as defined in Packages.registry" - * ... - * }]``` - */ -Template.registerHelper("reactionApps", (optionHash) => Reaction.Apps(optionHash)); diff --git a/client/modules/core/helpers/permissions.js b/client/modules/core/helpers/permissions.js index 5dca028c29..0b97c417f6 100644 --- a/client/modules/core/helpers/permissions.js +++ b/client/modules/core/helpers/permissions.js @@ -48,11 +48,3 @@ Template.registerHelper("hasAdminAccess", () => Reaction.hasAdminAccess()); * @returns {Boolean} return true if user has dashboard permission */ Template.registerHelper("hasDashboardAccess", () => Reaction.hasDashboardAccess()); - -/** - * @method allowGuestCheckout - * @memberof BlazeTemplateHelpers - * @summary check if guest users are allowed to checkout, uses [alanning:meteor-roles](http://alanning.github.io/meteor-roles/classes/Roles.html) - * @returns {Boolean} return true if shop has guest checkout enabled - */ -Template.registerHelper("allowGuestCheckout", () => Reaction.allowGuestCheckout()); diff --git a/client/modules/core/index.js b/client/modules/core/index.js index acc465cfae..4cea1cb8cd 100644 --- a/client/modules/core/index.js +++ b/client/modules/core/index.js @@ -1,5 +1,4 @@ import Core from "./main"; -import * as Apps from "./helpers/apps"; import * as Globals from "./helpers/globals"; import * as Utils from "./helpers/utils"; import { Subscriptions } from "./subscriptions"; @@ -11,10 +10,8 @@ import { Router } from "/client/modules/router"; import * as Collections from "/lib/collections"; import * as Schemas from "/lib/collections/schemas"; - export const Reaction = Object.assign( Core, - Apps, Globals, Utils, { Subscriptions }, diff --git a/client/modules/core/main.js b/client/modules/core/main.js index e3dc728ca8..f026f3ef4c 100644 --- a/client/modules/core/main.js +++ b/client/modules/core/main.js @@ -6,10 +6,8 @@ import { Tracker } from "meteor/tracker"; import { ReactiveVar } from "meteor/reactive-var"; import { ReactiveDict } from "meteor/reactive-dict"; import { Roles } from "meteor/alanning:roles"; -import Logger from "/client/modules/logger"; -import { Packages, Shops } from "/lib/collections"; +import { Shops } from "/lib/collections"; import { Router } from "/client/modules/router"; -import { DomainsMixin } from "./domains"; import { getUserId } from "./helpers/utils"; /** @@ -33,8 +31,6 @@ let slugify; const latinLangs = ["az", "da", "de", "en", "es", "ff", "fr", "ha", "hr", "hu", "ig", "is", "it", "jv", "ku", "ms", "nl", "no", "om", "pl", "pt", "ro", "sv", "sw", "tl", "tr", "uz", "vi", "yo"]; // eslint-disable-line max-len export default { - ...DomainsMixin, - /** * @summary The active shop * @memberof Core/Client @@ -362,13 +358,8 @@ export default { const activeShopId = this.getUserShopId(); if (activeShopId) return Shops.findOne({ _id: activeShopId }); - // If no chosen shop, look up the shop by domain - let shop = Shops.findOne({ domains: this.getDomain() }); - - // Finally fall back to primary shop - if (!shop) shop = Shops.findOne({ shopType: "primary" }); - - return shop; + // If no chosen shop, fall back to primary shop + return Shops.findOne({ shopType: "primary" }); }, /** @@ -485,19 +476,6 @@ export default { return slug; }, - /** - * @name allowGuestCheckout - * @method - * @memberof Core/Client - * @returns {Boolean} is guest checkout allowed - */ - allowGuestCheckout() { - const pkg = Packages.findOne({ name: "core", shopId: this.getShopId() }) || {}; - const shopSettings = pkg.settings || {}; - // we can disable in admin, let's check. - return !!(shopSettings.public && shopSettings.public.allowGuestCheckout); - }, - /** * (similar to server/api canInviteToGroup) * @name canInviteToGroup @@ -580,15 +558,9 @@ export default { Session.set("admin/actionView", viewStack); } else { - const registryItem = this.getRegistryForCurrentRoute("settings"); - - if (registryItem) { - this.setActionView(registryItem); - } else { - this.setActionView({ - template: "blankControls" - }); - } + this.setActionView({ + template: "blankControls" + }); } }, @@ -608,13 +580,7 @@ export default { actionViewStack.push(viewData); Session.set("admin/actionView", actionViewStack); } else { - const registryItem = this.getRegistryForCurrentRoute("settings"); - - if (registryItem) { - this.pushActionView(registryItem); - } else { - this.pushActionView({ template: "blankControls" }); - } + this.pushActionView({ template: "blankControls" }); } }, @@ -809,40 +775,5 @@ export default { } return null; - }, - - /** - * @name getRegistryForCurrentRoute - * @method - * @memberof Core/Client - * @param {String} provides type of template from registry - * @returns {Object} settings data from this package - */ - getRegistryForCurrentRoute(provides = "dashboard") { - this.Router.watchPathChange(); - const currentRouteName = this.Router.getRouteName(); - const currentRoute = this.Router.current(); - const { template } = currentRoute.route.options; - // find registry entries for routeName - const reactionApp = Packages.findOne({ - "registry.name": currentRouteName, - "registry.provides": provides, - "enabled": true - }, { - enabled: 1, - registry: 1, - route: 1, - name: 1, - label: 1, - settings: 1 - }); - - // valid application - if (reactionApp) { - const settingsData = _.find(reactionApp.registry, (item) => item.provides && item.provides.includes(provides) && item.template === template); - return settingsData; - } - Logger.debug("getRegistryForCurrentRoute not found", template, provides); - return {}; } }; diff --git a/client/modules/router/helpers.js b/client/modules/router/helpers.js index a9228454b4..a79bc447a5 100644 --- a/client/modules/router/helpers.js +++ b/client/modules/router/helpers.js @@ -1,5 +1,4 @@ import { Template } from "meteor/templating"; -import { Reaction } from "/client/api"; import Router from "./main"; /** @@ -10,14 +9,6 @@ import Router from "./main"; */ Template.registerHelper("pathFor", Router.pathFor); -/** - * @method urlFor - * @memberof BlazeTemplateHelpers - * @summary template helper to return absolute + path - * @returns {String} username - */ -Template.registerHelper("urlFor", (path, params) => Reaction.absoluteUrl(Router.pathFor(path, params).substr(1))); - /** * @method active * @memberof BlazeTemplateHelpers diff --git a/imports/client/ui/hooks/useCurrentShop.js b/imports/client/ui/hooks/useCurrentShop.js new file mode 100644 index 0000000000..fdfae75076 --- /dev/null +++ b/imports/client/ui/hooks/useCurrentShop.js @@ -0,0 +1,58 @@ +import { useLazyQuery } from "@apollo/react-hooks"; +import gql from "graphql-tag"; +import useCurrentShopId from "./useCurrentShopId.js"; + +const getShopQuery = gql` + query getShop($id: ID!) { + shop(id: $id) { + _id + brandAssets { + navbarBrandImageId + } + defaultParcelSize { + height + length + weight + width + } + language + name + shopLogoUrls { + primaryShopLogoUrl + } + storefrontUrls { + storefrontHomeUrl + storefrontLoginUrl + storefrontOrderUrl + storefrontOrdersUrl + storefrontAccountProfileUrl + } + } + } +`; + +/** + * React Hook that gets the globally current shop + * @return {Object} Object with `shop` and `shopId` props + */ +export default function useCurrentShop() { + const [shopId] = useCurrentShopId(); + + const [getShop, { called, data, loading, refetch }] = useLazyQuery(getShopQuery, { + fetchPolicy: "network-only" + }); + + // Wait until we're sure we have a shop ID to call the query + if (shopId && !called) { + getShop({ + variables: { id: shopId } + }); + } + + return { + isLoadingShop: loading, + refetchShop: refetch, + shop: data && data.shop, + shopId + }; +} diff --git a/imports/collections/schemas/index.js b/imports/collections/schemas/index.js index 58d1b3e329..ab654fc975 100644 --- a/imports/collections/schemas/index.js +++ b/imports/collections/schemas/index.js @@ -29,7 +29,6 @@ export * from "./notifications"; export * from "./orders"; export * from "./payments"; export * from "./products"; -export * from "./registry"; export * from "./shipping"; export * from "./shops"; export * from "./groups"; diff --git a/imports/collections/schemas/layouts.js b/imports/collections/schemas/layouts.js index 649b7beeb9..9d8cfe47ab 100644 --- a/imports/collections/schemas/layouts.js +++ b/imports/collections/schemas/layouts.js @@ -5,8 +5,7 @@ import { registerSchema } from "@reactioncommerce/schemas"; * @name LayoutStructure * @memberof Schemas * @type {SimpleSchema} - * @summary Layout are used by the Shops and Packages schemas. - * Layouts are used to in two ways: 1) Define the template layout on the site + * @summary Layouts are used to in two ways: 1) Define the template layout on the site * 2) Define workflow components used in each layout block * @description Read more about Layouts in {@link https://docs.reactioncommerce.com/reaction-docs/master/layout documentation} * @property {String} template optional @@ -59,8 +58,7 @@ registerSchema("LayoutStructure", LayoutStructure); * @name Layout * @memberof Schemas * @type {SimpleSchema} - * @summary Layout are used by the Shops and Packages schemas. - * Read more about Layouts in {@link https://docs.reactioncommerce.com/reaction-docs/master/layout documentation} + * @summary Read more about Layouts in {@link https://docs.reactioncommerce.com/reaction-docs/master/layout documentation} * @property {String} layout optional * @property {String} workflow optional * @property {String} template optional diff --git a/imports/collections/schemas/registry.js b/imports/collections/schemas/registry.js deleted file mode 100644 index f8f86c5605..0000000000 --- a/imports/collections/schemas/registry.js +++ /dev/null @@ -1,335 +0,0 @@ -import SimpleSchema from "simpl-schema"; -import { registerSchema } from "@reactioncommerce/schemas"; -import { Layout } from "./layouts"; - -/** - * @name Permissions - * @memberof Schemas - * @summary The Permissions schema is part of the Registry. The Registry Schema allows package settings to be defined. - * For more, read the in-depth {@link https://blog.reactioncommerce.com/an-intro-to-architecture-the-registry/ Intro to Architecture: The Registry}. - * @type {SimpleSchema} - * @property {String} permission - * @property {String} label - */ -export const Permissions = new SimpleSchema({ - permission: { - type: String - }, - label: { - type: String - } -}); - -registerSchema("Permissions", Permissions); - -/** - * @name Registry - * @memberof Schemas - * @type {SimpleSchema} - * @summary The Registry Schema allows package settings to be defined. For more, read the in-depth {@link https://blog.reactioncommerce.com/an-intro-to-architecture-the-registry/ Intro to Architecture: The Registry}. - * @property {String[]} provides Legacy `provides` apps use a String rather than an array. These are transformed in loadPackages. - * @property {String} route optional - * @property {String} name optional, Registry name must be unique. Namespace your plugin (e.g. `yourorg-plugin-name`) to avoid conflicts. - * @property {String} template optional, Assign to a Blaze template - * @property {String} workflow optional, A layout for a template in the package - * @property {String} layout optional, Force the app to render a specific layout - * @property {String[]} triggersEnter optional, Trigger on Enter - * @property {String[]} triggersExit optional, Trigger on Exit - * @property {Object} options optional, Routing Options - * @property {String} description optional, deprecated - * @property {String} icon optional, A set of CSS classes, often Font Awesome classes, used to define the package in the sidebar. - * @property {String} label optional, Human-readable name for a Registry entry - * @property {String} container optional, Used to group plugins - * @property {Number} priority optional, Plugin load order. Lower values loaded first. - * @property {Boolean} enabled optional, Enable or not - * @property {Permissions[]} permissions optional, Define a new user permission - * @property {String[]} audience optional, Define what permissions are required to view a step in a workflow - * @property {Object} meta optional, Set `dashboardSize` for the `actionView` - * @property {String[]} showForShopTypes optional, Shop Types this plugin should show for - * @property {String[]} hideForShopTypes optional, Shop Types this plugin should not show for - */ -export const Registry = new SimpleSchema({ - // TODO: This should _not_ be optional in the future, but we need to organize our packages better - "provides": { - // Legacy `provides` apps use a String rather than an array. These are transformed in loadPackages - type: Array, - optional: true - }, - "provides.$": { - type: String - }, - "route": { - type: String, - optional: true - }, - // TODO: This should _not_ be optional in the future, but we need to organize our packages better - "name": { - type: String, - label: "Registry Name", - optional: true - }, - "template": { - type: String, - optional: true - }, - "workflow": { - type: String, - optional: true - }, - "layout": { - type: String, - optional: true - }, - "triggersEnter": { - type: Array, - label: "Trigger on Entry", - optional: true - }, - "triggersEnter.$": { - type: String - }, - "triggersExit": { - type: Array, - label: "Trigger on Exit", - optional: true - }, - "triggersExit.$": { - type: String - }, - "options": { - label: "Routing Options", - type: Object, - optional: true, - defaultValue: {} - }, - "description": { - type: String, - optional: true - }, - "icon": { - type: String, - optional: true - }, - "label": { - type: String, - optional: true - }, - "container": { - type: String, - optional: true - }, - "priority": { - type: SimpleSchema.Integer, - optional: true - }, - "enabled": { - type: Boolean, - optional: true - }, - "permissions": { - type: Array, - optional: true - }, - "permissions.$": { - type: Permissions - }, - "audience": { - type: Array, - label: "Audience", - optional: true - }, - "audience.$": { - type: String - }, - "meta": { - label: "Meta", - type: Object, - optional: true, - blackbox: true - }, - "showForShopTypes": { - label: "Shop Types this plugin should show for", - type: Array, - optional: true - }, - "showForShopTypes.$": { - type: String - }, - "hideForShopTypes": { - type: Array, - label: "Shop Types this plugin should not show for", - optional: true - }, - "hideForShopTypes.$": { - type: String - } -}); - -registerSchema("Registry", Registry); - -/** - * @name PackageConfig - * @memberof Schemas - * @type {SimpleSchema} - * @summary The PackageConfig is part of the configuration settings required for packages in the Registry. - * The Registry Schema allows package settings to be defined. For more, read the in-depth {@link https://blog.reactioncommerce.com/an-intro-to-architecture-the-registry/ Intro to Architecture: The Registry}. - * @property {String} shopId The shop that owns this package config - * @property {String} name required - * @property {Boolean} enabled default value: true - * @property {String} icon optional - * @property {Object} settings optional - * @property {Registry[]} registry optional - * @property {Layout[]} layout optional - */ -export const PackageConfig = new SimpleSchema({ - "shopId": { - type: String, - label: "PackageConfig ShopId", - optional: true - }, - "name": String, - "enabled": { - type: Boolean, - defaultValue: true - }, - "version": { - type: String, - label: "Package Version", - optional: true - }, - "icon": { - type: String, - optional: true - }, - "settings": { - type: Object, - optional: true, - blackbox: true, - defaultValue: {} - }, - "registry": { - type: Array, - optional: true - }, - "registry.$": { - type: Registry - }, - "layout": { - type: Array, - optional: true - }, - "layout.$": { - type: Layout - } -}); - -registerSchema("PackageConfig", PackageConfig); - -/** - * @name CorePackageConfig - * @memberof Schemas - * @type {SimpleSchema} - * @summary The Core Package Config is part of the Registry. - * The Registry Schema allows package settings to be defined. For more, read the in-depth {@link https://blog.reactioncommerce.com/an-intro-to-architecture-the-registry/ Intro to Architecture: The Registry}. - * @extends {PackageConfig} - * @property {Object} settings.mail optional, Mail settings - * @property {String} settings.mail.user Mail user - * @property {String} settings.mail.password Mail password - * @property {String} settings.mail.host Mail host - * @property {String} settings.mail.port Mail port - * @property {String} settings.openexchangerates.appId OpenExchangeRates Id - * @property {String} settings.openexchangerates.refreshPeriod default value: `"every 1 hour"` - * @property {String} settings.google.clientId default value: `null` - * @property {String} settings.google.apiKey default value: `null` - * @property {Object} settings.public optional Settings in `public` are published to the client. - * @property {Boolean} settings.public.allowGuestCheckout allows guest checkout - * @property {String} settings.cart.cleanupDurationDays default value: `"older than 3 days"` - */ -export const CorePackageConfig = PackageConfig.clone().extend({ - // Remove blackbox: true from settings obj - "settings": { - type: Object, - optional: true, - blackbox: false, - defaultValue: {} - }, - "settings.mail": { - type: Object, - optional: true, - label: "Mail Settings" - }, - "settings.mail.user": { - type: String, - label: "Username" - }, - "settings.mail.password": { - type: String, - label: "Password" - }, - "settings.mail.host": { - type: String, - label: "Host" - }, - "settings.mail.port": { - type: String, - label: "Port" - }, - "settings.openexchangerates": { - type: Object, - optional: true - }, - "settings.openexchangerates.appId": { - type: String, - label: "Open Exchange Rates App Id", - optional: true - }, - "settings.openexchangerates.refreshPeriod": { - type: String, - label: "Open Exchange Rates refresh period", - defaultValue: "every 1 hour" - }, - "settings.google": { - type: Object, - optional: true - }, - "settings.google.clientId": { - type: String, - label: "Google Client Id" - }, - "settings.google.apiKey": { - type: String, - label: "Google Api Key" - }, - "settings.public": { - type: Object, - optional: true, - defaultValue: {} - }, - "settings.public.allowGuestCheckout": { - type: Boolean, - label: "Allow Guest Checkout", - defaultValue: true - }, - "settings.cart": { - type: Object, - defaultValue: {}, - optional: true - }, - "settings.cart.cleanupDurationDays": { - type: String, - label: "Cleanup Schedule", - defaultValue: "older than 3 days" - }, - "settings.sitemaps": { - type: Object, - defaultValue: {}, - optional: true - }, - "settings.sitemaps.refreshPeriod": { - type: String, - label: "Sitemap refresh period", - defaultValue: "every 24 hours" - } -}); - -registerSchema("CorePackageConfig", CorePackageConfig); diff --git a/imports/collections/schemas/shipping.js b/imports/collections/schemas/shipping.js index 154f82aa7c..a1c50d26e0 100644 --- a/imports/collections/schemas/shipping.js +++ b/imports/collections/schemas/shipping.js @@ -3,7 +3,6 @@ import { registerSchema } from "@reactioncommerce/schemas"; import { schemaIdAutoValue } from "./helpers"; import { Address } from "./address"; import { Invoice } from "./payments"; -import { PackageConfig } from "./registry"; import { Workflow } from "./workflow"; /** @@ -526,25 +525,3 @@ export const Shipping = new SimpleSchema({ }); registerSchema("Shipping", Shipping); - -/** - * @name ShippingPackageConfig - * @memberof Schemas - * @type {SimpleSchema} - * @property {String} settings.name default value: `Flat Rate Service` - */ -export const ShippingPackageConfig = PackageConfig.clone().extend({ - // Remove blackbox: true from settings obj - "settings": { - type: Object, - optional: true, - blackbox: false, - defaultValue: {} - }, - "settings.name": { - type: String, - defaultValue: "Flat Rate Service" - } -}); - -registerSchema("ShippingPackageConfig", ShippingPackageConfig); diff --git a/imports/collections/schemas/social.js b/imports/collections/schemas/social.js index 39a8ffdad2..3f58a126d4 100644 --- a/imports/collections/schemas/social.js +++ b/imports/collections/schemas/social.js @@ -1,6 +1,5 @@ import SimpleSchema from "simpl-schema"; import { registerSchema } from "@reactioncommerce/schemas"; -import { PackageConfig } from "./registry"; /** * @name SocialProvider @@ -25,72 +24,3 @@ export const SocialProvider = new SimpleSchema({ }); registerSchema("SocialProvider", SocialProvider); - -/** - * @name SocialPackageConfig - * @memberof Schemas - * @type {SimpleSchema} - * @extends {PackageConfig} - * @property {Object} settings.public optional - * @property {Object} settings.public.apps optional - * @property {SocialProvider} settings.public.apps.facebook optional, Facebook - * @property {String} settings.public.apps.facebook.appId optional, Facebook App ID - * @property {SocialProvider} settings.public.apps.twitter optional, Twitter - * @property {String} settings.public.apps.twitter.username optional, Twitter username - * @property {SocialProvider} settings.public.apps.pinterest optional, Pinterest - * @property {SocialProvider} settings.public.apps.googleplus optional, Google+ - */ -export const SocialPackageConfig = PackageConfig.clone().extend({ - // Remove blackbox: true from settings obj - "settings": { - type: Object, - optional: true, - blackbox: false, - defaultValue: {} - }, - "settings.public": { - type: Object, - optional: true, - defaultValue: {} - }, - "settings.public.apps": { - type: Object, - label: "Social Settings", - optional: true, - defaultValue: {} - }, - "settings.public.apps.facebook": { - type: SocialProvider, - optional: true, - defaultValue: {} - }, - "settings.public.apps.facebook.appId": { - type: String, - regEx: /\d+/, - label: "App Id", - optional: true - }, - "settings.public.apps.twitter": { - type: SocialProvider, - optional: true, - defaultValue: {} - }, - "settings.public.apps.twitter.username": { - type: String, - label: "Username", - optional: true - }, - "settings.public.apps.pinterest": { - type: SocialProvider, - optional: true, - defaultValue: {} - }, - "settings.public.apps.googleplus": { - type: SocialProvider, - label: "Google+", - optional: true, - defaultValue: {} - } -}); - -registerSchema("SocialPackageConfig", SocialPackageConfig); diff --git a/imports/plugins/core/accounts/client/components/editGroup.js b/imports/plugins/core/accounts/client/components/editGroup.js index 886057db87..5091a04b67 100644 --- a/imports/plugins/core/accounts/client/components/editGroup.js +++ b/imports/plugins/core/accounts/client/components/editGroup.js @@ -9,14 +9,234 @@ import { Reaction, i18next } from "/client/api"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import CardHeader from "@material-ui/core/CardHeader"; -import { groupPermissions } from "../helpers/accountsHelper"; + +const permissionList = [ + { + name: "reaction-dashboard", + label: "Dashboard", + permissions: [ + { + permission: "dashboard", + label: "Dashboard" + }, + { + permission: "shopSettings", + label: "Shop Settings" + } + ] + }, + { + name: "reaction-taxes-rates", + label: "Taxes Rates", + permissions: [] + }, + { + name: "reaction-file-collections", + label: "File Collections", + permissions: [ + { + permission: "media/create", + label: "Create Media" + }, + { + permission: "media/update", + label: "Update Media" + }, + { + permission: "media/delete", + label: "Delete Media" + } + ] + }, + { + name: "reaction-taxes", + label: "Taxes", + permissions: [] + }, + { + name: "reaction-i18n", + label: "I18n", + permissions: [] + }, + { + name: "reaction-product-admin", + label: "Product Admin", + permissions: [ + { + permission: "product/admin", + label: "Product Admin" + }, + { + permission: "product/archive", + label: "Archive Product" + }, + { + permission: "product/clone", + label: "Clone Product" + }, + { + permission: "product/create", + label: "Create Product" + }, + { + permission: "product/publish", + label: "Publish Product" + }, + { + permission: "product/update", + label: "Update Product" + } + ] + }, + { + name: "reaction-email", + label: "Email", + permissions: [] + }, + { + label: "Address", + permissions: [] + }, + { + name: "reaction-orders", + label: "Orders", + permissions: [ + { + permission: "order/fulfillment", + label: "Order Fulfillment" + }, + { + permission: "order/view", + label: "Order View" + } + ] + }, + { + name: "reaction-product-variant", + label: "Product Variant", + permissions: [ + { + permission: "tag", + label: "/tag/:slug?" + }, + { + permission: "createProduct", + label: "Add Product" + } + ] + }, + { + name: "reaction-tags", + label: "Tags", + permissions: [ + { + permission: "tag/admin", + label: "Tag Admin" + }, + { + permission: "tag/edit", + label: "Edit Tag" + } + ] + }, + { + name: "reaction-accounts", + label: "Accounts", + permissions: [ + { + permission: "accounts", + label: "Accounts" + }, + { + permission: "account/verify", + label: "Account Verify" + }, + { + permission: "reaction-accounts/accountsSettings", + label: "Account Settings" + }, + { + permission: "dashboard/accounts", + label: "Accounts" + }, + { + permission: "account/profile", + label: "Profile" + }, + { + permission: "reset-password", + label: "reset-password" + }, + { + permission: "account/enroll", + label: "Account Enroll" + }, + { + permission: "account/invite", + label: "Account Invite" + } + ] + }, + { + name: "discount-codes", + label: "discount Codes", + permissions: [ + { + permission: "discounts/apply", + label: "Apply Discounts" + } + ] + }, + { + name: "reaction-shipping", + label: "Shipping", + permissions: [ + { + permission: "shipping", + label: "Shipping" + } + ] + }, + { + name: "reaction-notification", + label: "Notification", + permissions: [ + { + permission: "notifications", + label: "Notifications" + } + ] + }, + { + name: "reaction-templates", + label: "Templates", + permissions: [] + }, + { + name: "reaction-discounts", + label: "Discounts", + permissions: [] + }, + { + label: "Hydra Oauth", + permissions: [ + { + permission: "account/login", + label: "OAuth Login" + }, + { + permission: "not-found", + label: "not-found" + } + ] + } +]; class EditGroup extends Component { static propTypes = { accounts: PropTypes.array, groups: PropTypes.array, onChangeGroup: PropTypes.func, - packages: PropTypes.array, selectedGroup: PropTypes.object }; @@ -142,10 +362,11 @@ class EditGroup extends Component { {this.state.groups.map((grp, index) => (