Skip to content

Commit

Permalink
Merge pull request #297 from outgrow/outgrow-shop-selector
Browse files Browse the repository at this point in the history
Add global shop selector for multi-shop usage
  • Loading branch information
focusaurus authored Sep 22, 2020
2 parents e18f1be + c93ed2d commit 4f3c448
Show file tree
Hide file tree
Showing 40 changed files with 459 additions and 1,282 deletions.
62 changes: 13 additions & 49 deletions imports/client/ui/components/ShopLogoWithData/ShopLogoWithData.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from "react";
import PropTypes from "prop-types";
import { compose } from "recompose";
import classNames from "classnames";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import { Link } from "react-router-dom";
import Typography from "@material-ui/core/Typography";
import withStyles from "@material-ui/core/styles/withStyles";
import { withComponents } from "@reactioncommerce/components-context";
import withPrimaryShopId from "/imports/plugins/core/graphql/lib/hocs/withPrimaryShopId";
Expand Down Expand Up @@ -47,29 +44,15 @@ const styles = (theme) => ({
* @param {Object} props Component props
* @returns {Node} React component
*/
function ShopLogoWithData({ className, classes, shopId, shouldShowShopName, linkTo, size }) {
function ShopLogoWithData({ classes, shopId, size }) {
if (!shopId) {
return (
<Link
className={classNames(classes.root, className)}
to={linkTo}
>
<img
alt="Reaction Commerce"
className={classes.logo}
src={defaultLogo}
width={size}
/>
{shouldShowShopName &&
<Typography
variant="h3"
component="span"
className={classes.logoName}
>
Reaction Commerce
</Typography>
}
</Link>
<img
alt="Reaction Commerce"
className={classes.logo}
src={defaultLogo}
width={size}
/>
);
}

Expand All @@ -84,26 +67,12 @@ function ShopLogoWithData({ className, classes, shopId, shouldShowShopName, link
const customLogoFromUrlInput = shop.shopLogoUrls && shop.shopLogoUrls.primaryShopLogoUrl;

return (
<Link
className={classNames(classes.root, className)}
to={linkTo}
>
<img
alt={shop.name}
className={classes.logo}
src={customLogoFromUrlInput || customLogoFromUpload || defaultLogo}
width={size}
/>
{shouldShowShopName &&
<Typography
variant="h3"
component="span"
className={classes.logoName}
>
{shop.name}
</Typography>
}
</Link>
<img
alt={shop.name}
className={classes.logo}
src={customLogoFromUrlInput || customLogoFromUpload || defaultLogo}
width={size}
/>
);
}

Expand All @@ -116,17 +85,12 @@ function ShopLogoWithData({ className, classes, shopId, shouldShowShopName, link
}

ShopLogoWithData.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
linkTo: PropTypes.string,
shopId: PropTypes.string,
shouldShowShopName: PropTypes.bool,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};

ShopLogoWithData.defaultProps = {
linkTo: "/",
shouldShowShopName: false,
size: 60
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import React from "react";
import PropTypes from "prop-types";
import { compose } from "recompose";
import classNames from "classnames";
import { Link, useLocation } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import InputBase from "@material-ui/core/InputBase";
import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
import Typography from "@material-ui/core/Typography";
import withStyles from "@material-ui/core/styles/withStyles";
import { withComponents } from "@reactioncommerce/components-context";

const defaultLogo = "/resources/reaction-logo-circular.svg";

const styles = (theme) => ({
root: {
display: "flex",
alignItems: "center"
},
logo: {
marginRight: theme.spacing(2)
},
menuItem: {
"& a span": {
color: theme.palette.colors.black90
}
},
selectMenu: {
"& a span": {
color: theme.palette.colors.black15
}
},
dummyShopName: {
color: theme.palette.colors.black15
},
newShopLink: {
"color": theme.palette.colors.darkBlue,
"&:hover": {
color: theme.palette.colors.darkBlue
}
},
plusIcon: {
display: "flex",
justifyContent: "center"
},
selectArrow: {
color: theme.palette.colors.black15
}
});

const ShopSelectorInput = withStyles(() => ({
input: {
"border": "none",
"&:focus": {
border: "none",
background: "transparent"
}
}
}))(InputBase);

/**
* ShopSelectorWithData
* @param {Object} props Component props
* @returns {Node} React component
*/
function ShopSelectorWithData({ className, classes, shouldShowShopName, shopId, linkTo, size, viewer }) {
const location = useLocation();

let adminUIShops = [];

if (viewer?.adminUIShops) {
({ adminUIShops } = viewer);
} else {
return (
<Link
className={classNames(classes.root, className)}
to={linkTo}
>
<img
alt="Reaction Commerce"
className={classes.logo}
src={defaultLogo}
width={size}
/>
{shouldShowShopName &&
<Typography
className={classes.dummyShopName}
component="span"
variant="h3"
>
Reaction Commerce
</Typography>
}
</Link>
);
}

return (
<Select classes={{ selectMenu: classes.selectMenu, icon: classes.selectArrow }} value={shopId} input={<ShopSelectorInput />}>
{adminUIShops.map((shop) => {
const customLogoFromUpload = shop.brandAssets && shop.brandAssets.navbarBrandImage && shop.brandAssets.navbarBrandImage.large;
const customLogoFromUrlInput = shop.shopLogoUrls && shop.shopLogoUrls.primaryShopLogoUrl;
// Replace only the first segment of the URL with the shop ID and keep the rest (if any)
const linkUrl = location.pathname.replace(/(\/.[^/]*(\/.*)?)/g, (match, firstUrlSegment, restOfUrl) => `/${shop._id}${restOfUrl || ""}`);

return (
<MenuItem className={classes.menuItem} value={shop._id} key={shop._id}>
<Link
className={classNames(classes.root, className)}
to={linkUrl}
>
<img
alt={shop.name}
className={classes.logo}
src={customLogoFromUrlInput || customLogoFromUpload || defaultLogo}
width={size}
/>
{shouldShowShopName &&
<Typography
variant="h3"
component="span"
>
{shop.name}
</Typography>
}
</Link>
</MenuItem>
);
})}
<MenuItem className={classes.menuItem} value="new-shop" key="new-shop">
<Link
className={classNames([classes.root, className, classes.newShopLink])}
to={"/new-shop"}
>
<div className={classNames([classes.logo, classes.plusIcon])} style={{ width: size }}>
<FontAwesomeIcon icon={faPlus} />
</div>
<Typography
variant="h3"
component="span"
>
New Shop
</Typography>
</Link>
</MenuItem>
</Select>
);
}

ShopSelectorWithData.propTypes = {
className: PropTypes.string,
classes: PropTypes.object,
linkTo: PropTypes.string,
shopId: PropTypes.string,
shouldShowShopName: PropTypes.bool,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
viewer: PropTypes.object
};

ShopSelectorWithData.defaultProps = {
linkTo: "/",
shouldShowShopName: false,
size: 60
};

export default compose(
withComponents,
withStyles(styles, { name: "RuiShopSelectorWithData" })
)(ShopSelectorWithData);
1 change: 1 addition & 0 deletions imports/client/ui/components/ShopSelectorWithData/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./ShopSelectorWithData";
18 changes: 13 additions & 5 deletions imports/client/ui/components/Sidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import CloseIcon from "mdi-material-ui/Close";
import { Translation } from "/imports/plugins/core/ui/client/components";
import useIsAppLoading from "/imports/client/ui/hooks/useIsAppLoading.js";
import useCurrentShopId from "../../hooks/useCurrentShopId";
import ShopLogoWithData from "../ShopLogoWithData";
import ShopSelectorWithData from "../ShopSelectorWithData";
import useOperatorRoutes from "../../hooks/useOperatorRoutes";

const activeClassName = "nav-item-active";
Expand Down Expand Up @@ -97,7 +97,8 @@ function Sidebar(props) {
isMobile,
isSidebarOpen,
onDrawerClose,
setIsSettingsOpen
setIsSettingsOpen,
viewer
} = props;

const [isAppLoading] = useIsAppLoading();
Expand Down Expand Up @@ -132,7 +133,7 @@ function Sidebar(props) {
<NavLink
activeClassName={activeClassName}
className={classes.link}
to={route.href || route.path}
to={(route.href || route.path).replace(":shopId", currentShopId)}
key={route.path}
onClick={() => {
setIsSettingsOpen(false);
Expand Down Expand Up @@ -164,7 +165,13 @@ function Sidebar(props) {
position="sticky"
>
<Toolbar className={classes.toolbar}>
<ShopLogoWithData className={classes.shopLogo} shouldShowShopName size={32} />
<ShopSelectorWithData
className={classes.shopLogo}
shouldShowShopName
shopId={currentShopId}
size={32}
viewer={viewer}
/>

<Hidden mdUp>
<Fab classes={{ root: classes.closeButton }} onClick={onDrawerClose} size="small">
Expand All @@ -187,7 +194,8 @@ Sidebar.propTypes = {
isMobile: PropTypes.bool,
isSidebarOpen: PropTypes.bool.isRequired,
onDrawerClose: PropTypes.func.isRequired,
setIsSettingsOpen: PropTypes.func.isRequired
setIsSettingsOpen: PropTypes.func.isRequired,
viewer: PropTypes.object
};

Sidebar.defaultProps = {
Expand Down
18 changes: 17 additions & 1 deletion imports/client/ui/hooks/useAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ query getViewer {
lastName
name
primaryEmailAddress
adminUIShops {
_id
brandAssets {
navbarBrandImage {
large
}
}
name
shopLogoUrls {
primaryShopLogoUrl
}
}
}
}
`;
Expand Down Expand Up @@ -48,7 +60,9 @@ export default function useAuth() {
setAccessToken(accessToken);

const [getViewer, {
data: viewerData
data: viewerData,
loading,
refetch
}] = useLazyQuery(
viewerQuery,
{
Expand Down Expand Up @@ -79,7 +93,9 @@ export default function useAuth() {
};

return {
isViewerLoading: loading,
logout,
refetchViewer: refetch,
viewer: viewerData ? viewerData.viewer : null
};
}
Loading

0 comments on commit 4f3c448

Please sign in to comment.