Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Marketplace] Owner permission should be transferable #2188 #2609

Merged
merged 53 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
9fac63c
Setup up version for migrating from old permissions style to groups
impactmass Jul 26, 2017
a3e3d11
Update accounts that match on migrate and add down method
impactmass Jul 26, 2017
aa656a5
Add shop manager as part of default groups on start and add to migrat…
impactmass Jul 27, 2017
9b6abdd
Show no dropdown if shop has only one group
impactmass Jul 27, 2017
aceb0e5
If account with no name, use first part of email as name
impactmass Jul 27, 2017
9687910
Switch logger to debug
impactmass Jul 28, 2017
725774d
add sort to display higher permission groups at the top
impactmass Jul 28, 2017
74dd3e9
Setup searching for and creating custom group and setting users to cu…
impactmass Jul 28, 2017
862d0e1
Add back initial setup from previous commits - inviteOwner method, ma…
impactmass Jul 28, 2017
f2fea8c
Merge branch 'marketplace' into seun-invite-owner-2186
impactmass Jul 28, 2017
3e757c0
fix failing test
impactmass Jul 28, 2017
c2f46e8
Merge branch 'seun-invite-owner-2186' of github.com:reactioncommerce/…
impactmass Jul 28, 2017
4415585
Add form to call invite user form marketplace settings dashgs
impactmass Jul 28, 2017
452c2dd
Set invited user as owner of new shop and set preferences
impactmass Jul 28, 2017
548f034
Fix typo, and set default option
impactmass Jul 29, 2017
ca7a1e4
Merge branch 'marketplace' into seun-migration-to-groups
impactmass Jul 29, 2017
2393ab7
Add comments to document flow of migration
impactmass Jul 29, 2017
b38d78d
Merge branch 'seun-migration-to-groups' of github.com:reactioncommerc…
impactmass Jul 29, 2017
1477059
Prevent error on user without roles field in tests
impactmass Jul 29, 2017
d9c5276
Change info logs to debug
impactmass Jul 29, 2017
da2675f
Merge branch 'seun-migration-to-groups' into seun-add-shop-manager-de…
impactmass Jul 29, 2017
d99f6ab
Add comment
impactmass Jul 29, 2017
55166ed
Merge branch 'marketplace' into seun-invite-owner-2186
impactmass Jul 29, 2017
e87c84a
Merge branch 'marketplace' into seun-change-owner-2188
impactmass Jul 29, 2017
c6ba02a
make dropdown more groups aware; and init owner swap
impactmass Jul 30, 2017
cd444cb
wip
impactmass Jul 31, 2017
04b3ce4
Merge branch 'marketplace' into seun-invite-owner-2186
impactmass Jul 31, 2017
1d882b0
Resolve lint import error
impactmass Jul 31, 2017
e8e6b19
Fix error in createShop; and update publication to limit to shop
impactmass Jul 31, 2017
4368720
Merge branch 'marketplace' into seun-invite-owner-2186
impactmass Aug 1, 2017
57a797a
Merge seun-fix-passwordoverlay-comp-2605 into seun-invite-owner-2186
impactmass Aug 1, 2017
58b691e
Updating sortable for pre-passed data
impactmass Aug 1, 2017
1ca913e
wip
impactmass Aug 1, 2017
904fc7a
Convert comp to react; and use registerComponent
impactmass Aug 2, 2017
7b8e36c
Use correct Id on shopcreate
impactmass Aug 2, 2017
3a4a214
Fix Reset password link and overlays broken (#2612)
impactmass Aug 1, 2017
18263c1
Merge branch 'marketplace' into seun-invite-owner-2186
impactmass Aug 2, 2017
be792f5
Merge branch 'seun-invite-owner-2186' into seun-change-owner-2188
impactmass Aug 2, 2017
f3ccb1c
Alert before swap owner; Add i18n and event update
impactmass Aug 2, 2017
15683b0
Add load on long method calls
impactmass Aug 3, 2017
4914b32
Move loader to HOC
impactmass Aug 3, 2017
6b78685
Update to loader
impactmass Aug 3, 2017
2af9c9b
Separating marketplace owner change from shop owner change
impactmass Aug 3, 2017
f4259d0
Remove log error
impactmass Aug 3, 2017
e4c577d
Separate warning for Marketplace change
impactmass Aug 3, 2017
8728d28
Show dropdown options based on owner perm
impactmass Aug 3, 2017
ee6d8de
Update comment
impactmass Aug 3, 2017
9b98390
Set correct shopId when user updates password for the first time
spencern Aug 3, 2017
88557b3
Merge branch 'marketplace' into seun-invite-owner-2186
Aug 4, 2017
2f79a7d
Merge branch 'seun-invite-owner-2186' into seun-change-owner-2188
impactmass Aug 4, 2017
8dd461f
Merge branch 'marketplace' into seun-change-owner-2188
impactmass Aug 4, 2017
00da956
Check if addressBook exist before using
impactmass Aug 4, 2017
2e7ad6c
Make shopIdAutoValueForCart separate from shopIdAutoValue
impactmass Aug 4, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ class AccountsDashboard extends Component {
this.setState({ selectedGroup: group });
};

handleMethodLoad = () => {
this.setState({ loading: true });
};

handleMethodDone = () => {
this.setState({ loading: false });
};

renderGroupDetail = () => {
const { groups, accounts } = this.state;
return (
Expand All @@ -47,11 +55,23 @@ class AccountsDashboard extends Component {

renderGroupsTable(groups) {
if (Array.isArray(groups)) {
return groups.map((group, index) => {
return (
<Components.GroupsTable key={index} group={group} onGroupSelect={this.handleGroupSelect} {...this.props} />
);
});
return (
<div className="group-container">
{this.state.loading && <Components.Loading />}
{groups.map((group, index) => {
return (
<Components.GroupsTable
key={index}
group={group}
onMethodLoad={this.handleMethodLoad}
onMethodDone={this.handleMethodDone}
onGroupSelect={this.handleGroupSelect}
{...this.props}
/>
);
})}
</div>
);
}

return null;
Expand Down
30 changes: 22 additions & 8 deletions imports/plugins/core/accounts/client/components/adminInviteForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ class AdminInviteForm extends Component {
constructor(props) {
super(props);
const { defaultInviteGroup, groups } = props;
const groupsInvitable = groups.filter((grp) => grp.slug !== "owner");

this.state = {
groups,
groups: groupsInvitable,
defaultInviteGroup,
name: "",
email: "",
Expand All @@ -30,7 +31,8 @@ class AdminInviteForm extends Component {

componentWillReceiveProps(nextProps) {
const { groups, defaultInviteGroup } = nextProps;
this.setState({ groups, defaultInviteGroup });
const groupsInvitable = groups.filter((grp) => grp.slug !== "owner");
this.setState({ groups: groupsInvitable, defaultInviteGroup });
}

onChange(event) {
Expand Down Expand Up @@ -99,15 +101,27 @@ class AdminInviteForm extends Component {
if (!buttonGroup._id) {
return null;
}
const buttonElement = (
const buttonElement = (opt) => (
<Components.Button bezelStyle="solid" label={buttonGroup.name && _.startCase(buttonGroup.name)} >
&nbsp;<i className="fa fa-chevron-down" />
&nbsp;
{opt && opt.length && // add icon only if there's a list of options
<i className="fa fa-chevron-down" />
}
</Components.Button>
);

// current selected option and "owner" should not show in list options
const dropOptions = this.state.groups.filter((grp) => grp._id !== buttonGroup._id);
if (!dropOptions.length) { return buttonElement(); } // do not use dropdown if only one option

return (
<Components.DropDownMenu buttonElement={buttonElement} attachment="bottom center" onChange={this.handleGroupSelect}>
{this.state.groups
.filter((grp) => grp._id !== buttonGroup._id)
<Components.DropDownMenu
buttonElement={buttonElement(dropOptions)}
onChange={this.handleGroupSelect}
attachment="bottom right"
targetAttachment="top right"
>
{dropOptions
.map((grp, index) => (
<Components.MenuItem
key={index}
Expand All @@ -120,7 +134,6 @@ class AdminInviteForm extends Component {
);
}


renderForm() {
return (
<div className="panel panel-default admin-invite-form">
Expand Down Expand Up @@ -156,6 +169,7 @@ class AdminInviteForm extends Component {
<div className="form-btns add-admin justify">
<Components.Button
status="primary"
buttonType="submit"
onClick={this.handleSubmit}
bezelStyle="solid"
i18nKeyLabel="accountsUI.info.sendInvitation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class GroupForm extends Component {
<div className="justify">
<Components.Button
status="primary"
buttonType="submit"
onClick={this.handleSubmit}
bezelStyle="solid"
i18nKeyLabel={this.props.i18nKeyLabel}
Expand Down
74 changes: 50 additions & 24 deletions imports/plugins/core/accounts/client/components/groupsTableCell.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Meteor } from "meteor/meteor";
import React from "react";
import _ from "lodash";
import PropTypes from "prop-types";
import { Components, registerComponent } from "@reactioncommerce/reaction-components";
import { Reaction } from "/client/api";
import { getGravatar } from "../helpers/accountsHelper";

const GroupsTableCell = ({ account, columnName, group, groups, handleRemoveUserFromGroup, handleUserGroupChange }) => {
const GroupsTableCell = ({ account, columnName, group, groups, handleRemoveUserFromGroup, handleUserGroupChange, ...props }) => {
const email = _.get(account, "emails[0].address");

if (columnName === "name") {
Expand Down Expand Up @@ -37,40 +39,62 @@ const GroupsTableCell = ({ account, columnName, group, groups, handleRemoveUserF
}

if (columnName === "dropdown") {
const groupName = <p>{_.startCase(groups[0].name)}</p>;
const ownerGroup = groups.find((grp) => grp.slug === "owner") || {};
const hasOwnerAccess = Reaction.hasPermission("owner", Meteor.userId(), Reaction.getShopId());

if (groups.length === 1) {
return (
<p>{_.startCase(groups[0].name)}</p>
);
return groupName;
}

if (group.slug === "owner") {
return groupName;
}
const dropDownButton = (

const { onMethodDone, onMethodLoad } = props;
const dropDownButton = (opt) => ( // eslint-disable-line
<div className="group-dropdown">
<Components.Button label={group.name && _.startCase(group.name)}>
&nbsp;<i className="fa fa-chevron-down" />
<Components.Button bezelStyle="solid" label={group.name && _.startCase(group.name)}>
&nbsp;
{opt && opt.length > 1 && // add icon only if there's more than the current group
<i className="fa fa-chevron-down" />
}
</Components.Button>
</div>
);

// Permission check. Remove owner option, if user is not current owner
const dropOptions = groups.filter(grp => (grp.slug === "owner" && !hasOwnerAccess) ? false : true) || [];
if (dropOptions.length < 2) { return dropDownButton(); } // do not use dropdown if only one option

return (
<Components.DropDownMenu
buttonElement={dropDownButton}
attachment="bottom center"
onChange={handleUserGroupChange(account)}
>
{groups
.filter((grp) => grp._id !== group._id)
.map((grp, index) => (
<Components.MenuItem
key={index}
label={_.startCase(grp.name)}
selectLabel={_.startCase(grp.name)}
value={grp._id}
/>
))}
</Components.DropDownMenu>
<div className="group-dropdown">
<Components.DropDownMenu
className="dropdown-item"
buttonElement={dropDownButton(groups)}
attachment="bottom right"
targetAttachment="top right"
onChange={handleUserGroupChange({ account, ownerGrpId: ownerGroup._id, onMethodDone, onMethodLoad })}
>
{dropOptions
.filter(grp => grp._id !== group._id)
.map((grp, index) => (
<Components.MenuItem
key={index}
label={_.startCase(grp.name)}
selectLabel={_.startCase(grp.name)}
value={grp._id}
/>
))}
</Components.DropDownMenu>
</div>
);
}

if (columnName === "button") {
if (group.slug === "owner") {
return null;
}
return (
<div className="group-table-button">
<Components.Button
Expand All @@ -93,7 +117,9 @@ GroupsTableCell.propTypes = {
group: PropTypes.object, // current group in interation
groups: PropTypes.array, // all available groups
handleRemoveUserFromGroup: PropTypes.func,
handleUserGroupChange: PropTypes.func
handleUserGroupChange: PropTypes.func,
onMethodDone: PropTypes.func,
onMethodLoad: PropTypes.func
};

registerComponent("GroupsTableCell", GroupsTableCell);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import { compose, withProps } from "recompose";
import Alert from "sweetalert2";
import { registerComponent, composeWithTracker } from "@reactioncommerce/reaction-components";
import { Meteor } from "meteor/meteor";
import { Accounts, Groups } from "/lib/collections";
import { Reaction, i18next } from "/client/api";
import AccountsDashboard from "../components/accountsDashboard";

const handlers = {
handleUserGroupChange(account) {
handleUserGroupChange({ account, ownerGrpId, onMethodLoad, onMethodDone }) {
return (event, groupId) => {
if (onMethodLoad) { onMethodLoad(); }

if (groupId === ownerGrpId) {
return alertConfirm()
.then(() => {
return updateMethodCall(groupId);
})
.catch(() => {
if (onMethodDone) { onMethodDone(); }
});
}

return updateMethodCall(groupId);
};

function updateMethodCall(groupId) {
Meteor.call("group/addUser", account._id, groupId, (err) => {
if (err) {
return Alerts.toast(i18next.t("admin.groups.addUserError", { err: err.message }), "error");
Alerts.toast(i18next.t("admin.groups.addUserError", { err: err.message }), "error");
}
return Alerts.toast(i18next.t("admin.groups.addUserSuccess"), "success");
if (!err) {
Alerts.toast(i18next.t("admin.groups.addUserSuccess"), "success");
}
if (onMethodDone) { onMethodDone(); }
});
};
}

function alertConfirm() {
let changeOwnerWarn = "changeShopOwnerWarn";
if (Reaction.getShopId() === Reaction.getPrimaryShopId()) {
changeOwnerWarn = "changeMktOwnerWarn";
}
return Alert({
title: i18next.t("admin.settings.changeOwner"),
text: i18next.t(`admin.settings.${changeOwnerWarn}`),
type: "warning",
showCancelButton: true,
cancelButtonText: i18next.t("admin.settings.cancel"),
confirmButtonText: i18next.t("admin.settings.continue")
});
}
},

handleRemoveUserFromGroup(account, groupId) {
Expand All @@ -30,6 +65,7 @@ const handlers = {
};

const composer = (props, onData) => {
const shopId = Reaction.getShopId();
const adminUserSub = Meteor.subscribe("Accounts", null);
const grpSub = Meteor.subscribe("Groups");

Expand All @@ -39,14 +75,14 @@ const composer = (props, onData) => {
shopId: Reaction.getShopId()
}).fetch();
const adminQuery = {
[`roles.${Reaction.getShopId()}`]: {
[`roles.${shopId}`]: {
$in: ["dashboard"]
}
};

const adminUsers = Meteor.users.find(adminQuery, { fields: { _id: 1 } }).fetch();
const ids = adminUsers.map((user) => user._id);
const accounts = Accounts.find({ _id: { $in: ids }, shopId: Reaction.getShopId() }).fetch();
const accounts = Accounts.find({ _id: { $in: ids } }).fetch();

onData(null, { accounts, groups });
}
Expand Down
5 changes: 5 additions & 0 deletions imports/plugins/core/accounts/server/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"accountsDescription": "Account Settings"
},
"settings": {
"continue": "Continue",
"cancel": "Cancel",
"changeOwner": "Change Owner",
"changeShopOwnerWarn": "You are about to change the Shop owner. The current owner will be replaced",
"changeMktOwnerWarn": "You are about to change the Marketplace owner. The current owner will be replaced",
"accountsSettingsLabel": "Accounts",
"accountsSettingsTitle": "Accounts",
"accountsSettingsDescription": "Account Settings"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@
}
border-left: 1px solid #ccc;
}
.group-container {
position: relative;
.spinner-container {
position: absolute;
top: 50%;
left: 50%;
z-index: 9999;
}
}
}

.accounts-group-table {
Expand Down
6 changes: 3 additions & 3 deletions lib/collections/schemas/cart.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SimpleSchema } from "meteor/aldeed:simple-schema";
import { shopIdAutoValue } from "./helpers";
import { shopIdAutoValueForCart } from "./helpers";
import { Payment } from "./payments";
import { Product, ProductVariant } from "./products";
import { Shipment, ShippingParcel } from "./shipping";
Expand All @@ -19,7 +19,7 @@ export const CartItem = new SimpleSchema({
},
shopId: {
type: String,
autoValue: shopIdAutoValue,
autoValue: shopIdAutoValueForCart,
index: 1,
label: "Cart Item shopId",
optional: true
Expand Down Expand Up @@ -87,7 +87,7 @@ export const Cart = new SimpleSchema({
},
shopId: {
type: String,
autoValue: shopIdAutoValue,
autoValue: shopIdAutoValueForCart,
index: 1,
label: "Cart ShopId"
},
Expand Down
16 changes: 16 additions & 0 deletions lib/collections/schemas/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ import { Reaction } from "/lib/api";
* @return {String} returns current shopId
*/
export function shopIdAutoValue() {
// we should always have a shopId
if (this.isSet && Meteor.isServer) {
return this.value;
} else if (Meteor.isServer && !this.isUpdate || Meteor.isClient && this.isInsert) {
return Reaction.getShopId();
}
return this.unset();
}

/**
* shopIdAutoValueForCart
* @summary copy of shopIdAutoValue with modification for Cart
* @example autoValue: shopIdAutoValue
* @return {String} shopId
*/
export function shopIdAutoValueForCart() {
// we should always have a shopId
if (this.isSet && Meteor.isServer) {
return this.value;
Expand Down
Loading