diff --git a/imports/client/ui/components/Sidebar/Sidebar.js b/imports/client/ui/components/Sidebar/Sidebar.js index d3973c3510..0cfe6d9638 100644 --- a/imports/client/ui/components/Sidebar/Sidebar.js +++ b/imports/client/ui/components/Sidebar/Sidebar.js @@ -1,10 +1,8 @@ import React from "react"; import { compose, withState } from "recompose"; import { NavLink, withRouter } from "react-router-dom"; -import classNames from "classnames"; import PropTypes from "prop-types"; import AppBar from "@material-ui/core/AppBar"; -import Collapse from "@material-ui/core/Collapse"; import Fab from "@material-ui/core/Fab"; import Hidden from "@material-ui/core/Hidden"; import Toolbar from "@material-ui/core/Toolbar"; @@ -14,7 +12,6 @@ import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemText from "@material-ui/core/ListItemText"; import Drawer from "@material-ui/core/Drawer"; import withStyles from "@material-ui/core/styles/withStyles"; -import SettingsIcon from "mdi-material-ui/Settings"; 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"; @@ -97,7 +94,6 @@ const styles = (theme) => ({ function Sidebar(props) { const { classes, - history, isMobile, isSidebarOpen, onDrawerClose, @@ -108,7 +104,6 @@ function Sidebar(props) { const [isAppLoading] = useIsAppLoading(); const [currentShopId] = useCurrentShopId(); const primaryRoutes = useOperatorRoutes({ groups: ["navigation"] }); - const settingRoutes = useOperatorRoutes({ groups: ["settings"] }); let drawerProps = { classes: { @@ -138,7 +133,7 @@ function Sidebar(props) { { setIsSettingsOpen(false); @@ -158,53 +153,6 @@ function Sidebar(props) { ))} - - { - // Push the first setting route when opened, but not on mobile - if (!isSettingsOpen && !isMobile) { - const [firstRoute] = settingRoutes; - - if (firstRoute) { - history.push(firstRoute.path); - } - } - setIsSettingsOpen(!isSettingsOpen); - }} - > - - - - - - - - - - {settingRoutes.map((route) => ( - - - - - - - - ))} - ); } diff --git a/imports/client/ui/layouts/ContentViewFullLayout.js b/imports/client/ui/layouts/ContentViewFullLayout.js index 084a7eecc7..df368efe3a 100644 --- a/imports/client/ui/layouts/ContentViewFullLayout.js +++ b/imports/client/ui/layouts/ContentViewFullLayout.js @@ -1,6 +1,6 @@ /** - * Component provies a fill width and height, non-scrollable container - * for dashboard layouts that want to defin their on scroll zones. + * Component provides a fill width and height, non-scrollable container + * for dashboard layouts that want to define their on scroll zones. */ import React from "react"; import PropTypes from "prop-types"; diff --git a/imports/client/ui/layouts/ContentViewPrimaryDetailLayout.js b/imports/client/ui/layouts/ContentViewPrimaryDetailLayout.js index d269eec016..023c5d9dfd 100644 --- a/imports/client/ui/layouts/ContentViewPrimaryDetailLayout.js +++ b/imports/client/ui/layouts/ContentViewPrimaryDetailLayout.js @@ -44,6 +44,7 @@ const useStyles = makeStyles((theme) => ({ }, sidebar: { flex: "1 1 auto", + minWidth: 330, maxWidth: 330, height: `calc(100vh - ${theme.mixins.toolbar.minHeight}px)`, overflowY: "auto", diff --git a/imports/plugins/core/address/client/components/AddressValidationSettingsForm.js b/imports/plugins/core/address/client/components/AddressValidationSettingsForm.js index 6fca12602a..20fdb9683d 100644 --- a/imports/plugins/core/address/client/components/AddressValidationSettingsForm.js +++ b/imports/plugins/core/address/client/components/AddressValidationSettingsForm.js @@ -9,7 +9,7 @@ import Button from "@reactioncommerce/catalyst/Button"; import Grid from "@material-ui/core/Grid"; import MenuItem from "@material-ui/core/MenuItem"; import TextField from "@material-ui/core/TextField"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; @@ -217,7 +217,7 @@ export default function AddressValidationSettingsForm(props) { { deleteRule({ variables: { @@ -234,7 +234,7 @@ export default function AddressValidationSettingsForm(props) { } diff --git a/imports/plugins/core/address/client/components/AddressValidationSettingsRegion.js b/imports/plugins/core/address/client/components/AddressValidationSettingsRegion.js new file mode 100644 index 0000000000..cbce53c974 --- /dev/null +++ b/imports/plugins/core/address/client/components/AddressValidationSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function AddressValidationSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.addressValidation.header")} + + + > + ); +} diff --git a/imports/plugins/core/address/client/components/ShopAddressValidationSettings.js b/imports/plugins/core/address/client/components/ShopAddressValidationSettings.js index c3fdb813ba..b656d151d2 100644 --- a/imports/plugins/core/address/client/components/ShopAddressValidationSettings.js +++ b/imports/plugins/core/address/client/components/ShopAddressValidationSettings.js @@ -6,7 +6,7 @@ import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import Dialog from "@material-ui/core/Dialog"; import DialogContent from "@material-ui/core/DialogContent"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import CountryOptions from "@reactioncommerce/api-utils/CountryOptions.js"; import { i18next } from "/client/api"; import AddressValidationSettingsForm from "./AddressValidationSettingsForm"; diff --git a/imports/plugins/core/address/client/index.js b/imports/plugins/core/address/client/index.js index aaa082fbca..2de86b10b0 100644 --- a/imports/plugins/core/address/client/index.js +++ b/imports/plugins/core/address/client/index.js @@ -1,10 +1,19 @@ import { registerOperatorRoute } from "/imports/client/ui"; +import AddressValidationSettingsRegion from "./components/AddressValidationSettingsRegion"; import ShopAddressValidationSettings from "./containers/ShopAddressValidationSettings"; +import { registerBlock } from "@reactioncommerce/reaction-components"; registerOperatorRoute({ group: "settings", - MainComponent: ShopAddressValidationSettings, - path: "/address-validation-settings", - priority: 900, + MainComponent: AddressValidationSettingsRegion, + path: "/settings/address-validation-settings", + priority: 170, sidebarI18nLabel: "addressValidation.title" }); + +registerBlock({ + region: "AddressValidationSettings", + name: "AddressValidationSettings", + component: ShopAddressValidationSettings, + priority: 1 +}); diff --git a/imports/plugins/core/dashboard/client/components/SettingsDashboard.js b/imports/plugins/core/dashboard/client/components/SettingsDashboard.js new file mode 100644 index 0000000000..d5598e00d2 --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/SettingsDashboard.js @@ -0,0 +1,43 @@ +import React from "react"; +import i18next from "i18next"; +import { Switch, Route } from "react-router-dom"; +import { + Container +} from "@material-ui/core"; +import PrimaryAppBar from "/imports/client/ui/components/PrimaryAppBar"; +import ContentViewPrimaryDetailLayout from "/imports/client/ui/layouts/ContentViewPrimaryDetailLayout"; +import useOperatorRoutes from "/imports/client/ui/hooks/useOperatorRoutes"; +import SettingsList from "./SettingsList"; + +/** + * @name SettingsDashboard + * @returns {React.component} a functional React component + */ +export default function SettingsDashboard() { + const settingsRoutes = useOperatorRoutes({ groups: ["settings"] }); + return ( + + } + PrimaryComponent={ + + } + DetailComponent={ + + + { + settingsRoutes.map((settingRoute) => ( + + )) + } + + + } + /> + ); +} diff --git a/imports/plugins/core/dashboard/client/components/SettingsList.js b/imports/plugins/core/dashboard/client/components/SettingsList.js new file mode 100644 index 0000000000..a2792460ef --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/SettingsList.js @@ -0,0 +1,66 @@ +import React from "react"; +import i18next from "i18next"; +import { + List, + ListItemText, + makeStyles, + ListItem +} from "@material-ui/core"; +import clsx from "classnames"; +import { useHistory } from "react-router-dom"; +import useOperatorRoutes from "imports/client/ui/hooks/useOperatorRoutes"; + +const useStyles = makeStyles((theme) => ({ + listItemContainer: { + "&:hover $listItemAction": { + display: "block" + } + }, + listItem: { + paddingLeft: theme.spacing(4) + } +})); + +/** + * @summary A list settings for a shop + * @returns {Node} React node + */ +export default function SettingsList() { + const classes = useStyles(); + const history = useHistory(); + const settingsRoutes = useOperatorRoutes({ groups: ["settings"] }); + let settingsList = []; + + if (Array.isArray(settingsRoutes)) { + settingsList = settingsRoutes.map((setting) => ( + ( + + {i18next.t(setting.sidebarI18nLabel) } + {children} + + )} + className={clsx({ + [classes.listItem]: true + })} + button + onClick={() => history.push(setting.path)} + > + + + )); + } + + return ( + + {settingsRoutes && Array.isArray(settingsRoutes) && settingsList} + + ); +} diff --git a/imports/plugins/core/dashboard/client/components/ShopAddressForm.js b/imports/plugins/core/dashboard/client/components/ShopAddressForm.js new file mode 100644 index 0000000000..931506a028 --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/ShopAddressForm.js @@ -0,0 +1,238 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import i18next from "i18next"; +import SimpleSchema from "simpl-schema"; +import Button from "@reactioncommerce/catalyst/Button"; +import TextField from "@reactioncommerce/catalyst/TextField"; +import useReactoForm from "reacto-form/cjs/useReactoForm"; +import muiOptions from "reacto-form/cjs/muiOptions"; +import muiCheckboxOptions from "reacto-form/esm/muiCheckboxOptions"; +import { + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + Grid +} from "@material-ui/core"; +import useShopSettings from "../hooks/useShopSettings"; + +const shopAddress = new SimpleSchema({ + company: { + type: String, + optional: true + }, + fullName: { + type: String, + min: 1 + }, + address1: { + type: String, + min: 1 + }, + address2: { + type: String, + optional: true + }, + city: { + type: String, + min: 1 + }, + region: { + type: String, + min: 1 + }, + postal: { + type: String, + min: 1 + }, + country: { + type: String, + min: 1 + }, + phone: { + type: String, + regEx: SimpleSchema.RegEx.Phone + }, + isCommercial: { + type: Boolean, + defaultValue: false + } +}); + +const validator = shopAddress.getFormValidator(); + +/** + * Shop address settings form block component + * @param {Object} props component props + * @returns {Node} React node + */ +function ShopAddressForm({ isEditMode, isInitialView, setIsEditMode }) { + const [isSubmitting, setIsSubmitting] = useState(false); + const { onUpdateShop, shop } = useShopSettings(); + + const dialogTitle = isInitialView ? + i18next.t("admin.settings.address.addNewAddress") + : + i18next.t("admin.settings.address.editAddress"); + + let initialValues = {}; + if (shop.addressBook) { + initialValues = { value: shop.addressBook[0] }; + } + + const { + getFirstErrorMessage, + getInputProps, + hasErrors, + submitForm + } = useReactoForm({ + async onSubmit(formData) { + setIsSubmitting(true); + await onUpdateShop({ addressBook: [shopAddress.clean(formData)] }); + setIsSubmitting(false); + setIsEditMode(false); + }, + validator(formData) { + return validator(shopAddress.clean(formData)); + }, + ...initialValues + }); + + const handleSubmit = (event) => { + event.preventDefault(); + submitForm(); + }; + + const handleOnCloseDialog = () => { + setIsEditMode(false); + }; + + return ( + + + {dialogTitle} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + label={i18next.t("admin.settings.address.isCommercialLabel")} + {...getInputProps("isCommercial", muiCheckboxOptions)} + /> + + + + + + Cancel + + + {isSubmitting ? i18next.t("admin.settings.saveProcessing") : i18next.t("app.save")} + + + + ); +} + +ShopAddressForm.propTypes = { + isEditMode: PropTypes.bool.isRequired, + isInitialView: PropTypes.bool.isRequired, + setIsEditMode: PropTypes.func.isRequired +}; + +export default ShopAddressForm; diff --git a/imports/plugins/core/dashboard/client/components/ShopAddressSettings.js b/imports/plugins/core/dashboard/client/components/ShopAddressSettings.js new file mode 100644 index 0000000000..21977fba88 --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/ShopAddressSettings.js @@ -0,0 +1,104 @@ +import React, { Fragment, useState } from "react"; +import i18next from "i18next"; +import { + Box, + Button, + Card, + CardContent, + CardHeader, + CircularProgress, + makeStyles, + Typography +} from "@material-ui/core"; +import useShopSettings from "../hooks/useShopSettings"; +import ShopAddressForm from "./ShopAddressForm"; + +const useStyles = makeStyles((theme) => ({ + card: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3) + }, + textField: { + minWidth: 350 + }, + saveButton: { + textAlign: "right" + } +})); + +/** + * Shop address settings form block component + * @returns {Node} React node + */ +function ShopAddressSettings() { + const classes = useStyles(); + const [isEditMode, setIsEditMode] = useState(false); + const { loading, shop: { addressBook } } = useShopSettings(); + let readOnlyMode = false; + let isInitialView = false; + let address = null; + + if (loading) return ; + + // If an address has been set, render in read only mode + if (addressBook && addressBook[0]) { + ([address] = addressBook); + + if (!isEditMode) readOnlyMode = true; + } else { + isInitialView = true; + } + + const actionButtonText = isInitialView + ? i18next.t("admin.settings.address.addNewAddress") + : i18next.t("admin.settings.address.edit"); + + const handleOnEdit = () => { + setIsEditMode(true); + }; + + return ( + + + + {isInitialView && + + {i18next.t("admin.settings.address.initialViewText")} + + } + { + (readOnlyMode) + ? + + {address.company} + {address.fullName} + {address.address1} + {address.address2} + {`${address.city}, ${address.region} ${address.postal}`} + {address.country} + {address.phone} + + : + + } + {!isEditMode && + + + {actionButtonText} + + + } + + + ); +} + +export default ShopAddressSettings; diff --git a/imports/plugins/core/dashboard/client/components/ShopLogoUrls.js b/imports/plugins/core/dashboard/client/components/ShopLogoUrls.js index 07c8df478e..43813a27cf 100644 --- a/imports/plugins/core/dashboard/client/components/ShopLogoUrls.js +++ b/imports/plugins/core/dashboard/client/components/ShopLogoUrls.js @@ -17,6 +17,11 @@ import { Components } from "@reactioncommerce/reaction-components"; import { i18next } from "/client/api"; import withShop from "/imports/plugins/core/graphql/lib/hocs/withShop"; +const CardContainer = styled(Card)` + margin-bottom: 24px + margin-top: 24px +`; + const PaddedField = styled(Field)` margin-bottom: 30px; `; @@ -85,7 +90,7 @@ class ShopLogoUrls extends Component { const { primaryShopLogoUrl } = shopLogoUrls || {}; return ( - + - + ); } } diff --git a/imports/plugins/core/dashboard/client/components/ShopSettingsForm.js b/imports/plugins/core/dashboard/client/components/ShopSettingsForm.js new file mode 100644 index 0000000000..b4f96e412a --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/ShopSettingsForm.js @@ -0,0 +1,179 @@ +import React, { useState } from "react"; +import i18next from "i18next"; +import SimpleSchema from "simpl-schema"; +import Button from "@reactioncommerce/catalyst/Button"; +import TextField from "@reactioncommerce/catalyst/TextField"; +import useReactoForm from "reacto-form/cjs/useReactoForm"; +import muiCheckboxOptions from "reacto-form/esm/muiCheckboxOptions"; +import muiOptions from "reacto-form/cjs/muiOptions"; +import { + Card, + CardContent, + CardHeader, + CircularProgress, + FormControlLabel, + Grid, + makeStyles, + Checkbox +} from "@material-ui/core"; +import useShopSettings from "../hooks/useShopSettings"; + +const useStyles = makeStyles((theme) => ({ + card: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3) + }, + saveButton: { + textAlign: "right" + } +})); + +const shopSettings = new SimpleSchema({ + "allowGuestCheckout": { + type: Boolean, + optional: true + }, + "name": { + type: String, + min: 1 + }, + "emails": { + type: Array + }, + "emails.$": new SimpleSchema({ + address: { + type: String, + regEx: SimpleSchema.RegEx.Email + } + }), + "slug": { + type: String, + min: 1 + }, + "description": { + type: String, + optional: true + }, + "keywords": { + type: String, + optional: true + } +}); + +const validator = shopSettings.getFormValidator(); + +/** + * Shop settings form block component + * @returns {Node} React node + */ +export default function ShopSettings() { + const classes = useStyles(); + const [isSubmitting, setIsSubmitting] = useState(false); + const { loading, onUpdateShop, shop } = useShopSettings(); + const { + getFirstErrorMessage, + getInputProps, + hasErrors, + submitForm + } = useReactoForm({ + async onSubmit(formData) { + setIsSubmitting(true); + await onUpdateShop(shopSettings.clean(formData)); + setIsSubmitting(false); + }, + validator(formData) { + return validator(shopSettings.clean(formData)); + }, + value: shop + }); + + if (loading) return ; + + const handleSubmit = (event) => { + event.preventDefault(); + submitForm(); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + } + label={i18next.t("admin.settings.shop.allowGuestCheckout")} + {...getInputProps("allowGuestCheckout", muiCheckboxOptions)} + /> + + + + {i18next.t("app.save")} + + + + + + ); +} diff --git a/imports/plugins/core/dashboard/client/components/ShopSettingsRegion.js b/imports/plugins/core/dashboard/client/components/ShopSettingsRegion.js new file mode 100644 index 0000000000..3bc6b0df13 --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/ShopSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function ShopSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.shop.header")} + + + > + ); +} diff --git a/imports/plugins/core/dashboard/client/components/StorefrontUrls.js b/imports/plugins/core/dashboard/client/components/StorefrontUrls.js index d8197a04ab..2e992ae57f 100644 --- a/imports/plugins/core/dashboard/client/components/StorefrontUrls.js +++ b/imports/plugins/core/dashboard/client/components/StorefrontUrls.js @@ -1,17 +1,18 @@ -import React, { Component, Fragment } from "react"; +import React, { Component } from "react"; import PropTypes from "prop-types"; import gql from "graphql-tag"; -import styled from "styled-components"; import { Form } from "reacto-form"; import { Mutation } from "react-apollo"; import { compose } from "recompose"; import withStyles from "@material-ui/core/styles/withStyles"; -import Button from "@material-ui/core/Button"; -import Card from "@material-ui/core/Card"; -import CardHeader from "@material-ui/core/CardHeader"; -import CardActions from "@material-ui/core/CardActions"; -import CardContent from "@material-ui/core/CardContent"; -import Grid from "@material-ui/core/Grid"; +import { Button } from "@reactioncommerce/catalyst"; +import { + Box, + Card, + CardHeader, + CardActions, + CardContent +} from "@material-ui/core"; import Typography from "@material-ui/core/Typography"; import ErrorsBlock from "@reactioncommerce/components/ErrorsBlock/v1"; import Field from "@reactioncommerce/components/Field/v1"; @@ -20,17 +21,15 @@ import { i18next } from "/client/api"; import withPrimaryShopId from "/imports/plugins/core/graphql/lib/hocs/withPrimaryShopId"; import withShop from "/imports/plugins/core/graphql/lib/hocs/withShop"; -const PaddedField = styled(Field)` - margin-bottom: 30px; -`; - -const RightAlignedGrid = styled(Grid)` - text-align: right; -`; - -const styles = () => ({ +const styles = (theme) => ({ + card: { + marginBottom: theme.spacing(3) + }, helpText: { marginTop: "10px" + }, + field: { + marginBottom: theme.spacing(4) } }); @@ -107,7 +106,7 @@ class StorefrontUrls extends Component { const { storefrontHomeUrl, storefrontLoginUrl, storefrontOrderUrl, storefrontOrdersUrl, storefrontAccountProfileUrl } = storefrontUrls || {}; return ( - + {(mutationFunc) => ( - - { - this.form = formRef; - }} - onChange={this.handleFormChange} - onSubmit={(data) => this.handleUpdateUrls(data, mutationFunc)} - value={shop} - > - - { + this.form = formRef; + }} + onChange={this.handleFormChange} + onSubmit={(data) => this.handleUpdateUrls(data, mutationFunc)} + value={shop} + > + + + - - - - + + + + - - - - + + + + - - - {i18next.t( - "shopSettings.storefrontUrls.storefrontOrderUrlHelpText", - "In order for links inside of order emails to work, you must provide both an `:orderId` and `:token` in this field. These act as placeholders that are replaced with the correct data in your email template when an order email is generated. For example: http://shop.example.com/my-orders/:orderId?token=:token" - )} - - - - + + {i18next.t( + "shopSettings.storefrontUrls.storefrontOrderUrlHelpText", + "In order for links inside of order emails to work, you must provide both an `:orderId` and `:token` in this field. These act as placeholders that are replaced with the correct data in your email template when an order email is generated. For example: http://shop.example.com/my-orders/:orderId?token=:token" + )} + + + + + - - - - + + + + - - - - - - - - - {i18next.t("app.save")} - - - - - - + placeholder={ + i18next.t( + "shopSettings.storefrontUrls.storefrontAccountProfileUrlDescription", + "URL of your shops account profile homepage" + ) + } + value={storefrontAccountProfileUrl || ""} + /> + + + + + + + {i18next.t("app.save")} + + + + )} diff --git a/imports/plugins/core/dashboard/client/components/SystemSettingsRegion.js b/imports/plugins/core/dashboard/client/components/SystemSettingsRegion.js new file mode 100644 index 0000000000..455dfea0ad --- /dev/null +++ b/imports/plugins/core/dashboard/client/components/SystemSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function SystemSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.system.header")} + + + > + ); +} diff --git a/imports/plugins/core/dashboard/client/graphql/fragments/shopCommon.js b/imports/plugins/core/dashboard/client/graphql/fragments/shopCommon.js new file mode 100644 index 0000000000..e0a4706827 --- /dev/null +++ b/imports/plugins/core/dashboard/client/graphql/fragments/shopCommon.js @@ -0,0 +1,29 @@ +import gql from "graphql-tag"; + +export default gql` + fragment ShopCommon on Shop { + _id + allowGuestCheckout + addressBook { + company + fullName + address1 + address2 + city + region + postal + country + phone + isCommercial + } + description + emails { + address + } + keywords + name + slug + } +`; + + diff --git a/imports/plugins/core/dashboard/client/graphql/mutations/updateShop.js b/imports/plugins/core/dashboard/client/graphql/mutations/updateShop.js new file mode 100644 index 0000000000..7bd23cbd79 --- /dev/null +++ b/imports/plugins/core/dashboard/client/graphql/mutations/updateShop.js @@ -0,0 +1,13 @@ +import gql from "graphql-tag"; +import shopCommon from "../fragments/shopCommon"; + +export default gql` + mutation updateShop($input: UpdateShopInput!) { + updateShop(input: $input) { + shop { + ...ShopCommon + } + } + } + ${shopCommon} +`; diff --git a/imports/plugins/core/dashboard/client/graphql/queries/shop.js b/imports/plugins/core/dashboard/client/graphql/queries/shop.js new file mode 100644 index 0000000000..ed94a40c2a --- /dev/null +++ b/imports/plugins/core/dashboard/client/graphql/queries/shop.js @@ -0,0 +1,11 @@ +import gql from "graphql-tag"; +import shopCommonFragment from "../fragments/shopCommon"; + +export default gql` + query shopQuery($id: ID!) { + shop(id: $id) { + ...ShopCommon + } + } + ${shopCommonFragment} +`; diff --git a/imports/plugins/core/dashboard/client/hooks/useShopSettings.js b/imports/plugins/core/dashboard/client/hooks/useShopSettings.js new file mode 100644 index 0000000000..3031370d8e --- /dev/null +++ b/imports/plugins/core/dashboard/client/hooks/useShopSettings.js @@ -0,0 +1,68 @@ +import i18next from "i18next"; +import { useMutation, useLazyQuery } from "@apollo/react-hooks"; +import { useSnackbar } from "notistack"; +import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId"; +import shopQuery from "../graphql/queries/shop"; +import updateShopMutation from "../graphql/mutations/updateShop"; + +/** + * @method useShopSettings + * @summary useShopSettings hook + * @param {Object} args input arguments + * @param {String} args.shopId Id of the shop to update settings on + * @param {Object} args.fields Shop setting fields to update + * @returns {Object} The updated shop object + */ +function useShopSettings(args = {}) { + const { + shopId: providedShopId + } = args; + const [currentShopId] = useCurrentShopId(); + const [updateShop] = useMutation(updateShopMutation); + const shopId = providedShopId || currentShopId; + const { enqueueSnackbar } = useSnackbar(); + const [fetchShop, { called, loading, data: shopQueryResult, refetch: refetchShopQuery }] = useLazyQuery(shopQuery); + + if (shopId && !called) { + fetchShop({ + variables: { + id: shopId + } + }); + } + + /** + * @method onUpdateShop + * @param {Object} fields Fields in `UpdateShopInput` that can be updated. + * @param {String} .shopId Shop ID of the shop to update settings for. Leave blank for current shop. + * @returns {undefined} no return value + */ + const onUpdateShop = async ({ + shopId: shopIdLocal = shopId, + ...fields + }) => { + try { + await updateShop({ + variables: { + input: { + ...fields, + shopId: shopIdLocal + } + } + }); + enqueueSnackbar(i18next.t("admin.settings.saveSuccess"), { variant: "success" }); + } catch (error) { + enqueueSnackbar(i18next.t("admin.settings.saveFailed"), { variant: "error" }); + } + }; + + return { + loading, + onUpdateShop, + refetchShopQuery, + shop: (shopQueryResult && shopQueryResult.shop) || {}, + shopId + }; +} + +export default useShopSettings; diff --git a/imports/plugins/core/dashboard/client/index.js b/imports/plugins/core/dashboard/client/index.js index 25e4c7de1c..ccbde058e6 100644 --- a/imports/plugins/core/dashboard/client/index.js +++ b/imports/plugins/core/dashboard/client/index.js @@ -1,13 +1,18 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faStore } from "@fortawesome/free-solid-svg-icons"; - -import { registerBlock } from "/imports/plugins/core/components/lib"; +import SettingsIcon from "mdi-material-ui/Settings"; +import { registerBlock } from "@reactioncommerce/reaction-components"; import { registerOperatorRoute } from "/imports/client/ui"; import OperatorLanding from "/imports/plugins/core/dashboard/client/components/OperatorLanding"; +// Settings block regions +import ShopSettingsRegion from "./components/ShopSettingsRegion"; +import SystemSettingsRegion from "./components/SystemSettingsRegion"; + +// Settings import SystemInformation from "./components/SystemInformation"; import ShopLogoUrls from "./components/ShopLogoUrls"; +import SettingsDashboard from "./components/SettingsDashboard"; +import ShopSettingsForm from "./components/ShopSettingsForm"; +import ShopAddressSettings from "./components/ShopAddressSettings"; import StorefrontUrls from "./components/StorefrontUrls"; import "./components/shopBrandImageOption"; @@ -25,33 +30,67 @@ registerOperatorRoute({ }); registerOperatorRoute({ - group: "settings", - priority: 10, - path: "/shop-settings", - MainComponent: "shopSettings", + group: "navigation", + priority: 80, + path: "/settings/:setting?", + href: "/settings/shop", + LayoutComponent: null, + MainComponent: SettingsDashboard, // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , + SidebarIconComponent: SettingsIcon, + sidebarI18nLabel: "admin.settings.settingsLabel" +}); + +// Shop settings region +registerOperatorRoute({ + group: "settings", + MainComponent: ShopSettingsRegion, + priority: 110, + path: "/settings/shop", sidebarI18nLabel: "admin.settings.shopSettingsLabel" }); registerOperatorRoute({ group: "settings", - MainComponent: SystemInformation, - path: "/system", - priority: 1000, + MainComponent: SystemSettingsRegion, + path: "/settings/system", + priority: 300, sidebarI18nLabel: "shopSettings.systemInfo.title" }); +// Settings blocks registerBlock({ region: "ShopSettings", - name: "ShopLogoUrls", - component: ShopLogoUrls, + name: "ShopSettingsGeneral", + component: ShopSettingsForm, + priority: 1 +}); + +registerBlock({ + region: "ShopSettings", + name: "ShopAddress", + component: ShopAddressSettings, priority: 2 }); registerBlock({ region: "ShopSettings", + name: "ShopLogoUrls", + component: ShopLogoUrls, + priority: 3 +}); + +registerBlock({ + region: "EmailSettings", name: "StorefrontUrls", component: StorefrontUrls, - priority: 3 + priority: 2 +}); + +// System settings blocks +registerBlock({ + region: "SystemSettings", + name: "SystemSettingsGeneral", + component: SystemInformation, + priority: 1 }); diff --git a/imports/plugins/core/email/client/components/EmailSettingsRegion.js b/imports/plugins/core/email/client/components/EmailSettingsRegion.js new file mode 100644 index 0000000000..db86e98c34 --- /dev/null +++ b/imports/plugins/core/email/client/components/EmailSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function EmailSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.email.header")} + + + > + ); +} diff --git a/imports/plugins/core/email/client/containers/EmailSettings.js b/imports/plugins/core/email/client/containers/EmailSettings.js index e3098c09dc..507f126e45 100644 --- a/imports/plugins/core/email/client/containers/EmailSettings.js +++ b/imports/plugins/core/email/client/containers/EmailSettings.js @@ -1,18 +1,12 @@ -import React, { Component, Fragment } from "react"; -import { Blocks } from "@reactioncommerce/reaction-components"; +import React, { Component } from "react"; import EmailLogs from "./emailLogs"; export default class EmailSettings extends Component { render() { return ( - - - - - - - - + <> + + > ); } } diff --git a/imports/plugins/core/email/client/index.js b/imports/plugins/core/email/client/index.js index 0b1f85eda0..f10b156f5e 100644 --- a/imports/plugins/core/email/client/index.js +++ b/imports/plugins/core/email/client/index.js @@ -1,15 +1,19 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faEnvelope } from "@fortawesome/free-solid-svg-icons"; - import { registerOperatorRoute } from "/imports/client/ui"; +import { registerBlock } from "@reactioncommerce/reaction-components"; +import EmailSettingsRegion from "./components/EmailSettingsRegion"; import EmailSettings from "./containers/EmailSettings"; registerOperatorRoute({ group: "settings", - path: "/email", - MainComponent: EmailSettings, - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , + path: "/settings/email", + MainComponent: EmailSettingsRegion, + priority: 150, sidebarI18nLabel: "admin.dashboard.emailLabel" }); + +registerBlock({ + component: EmailSettings, + name: "EmailSettingsGeneral", + priority: 1, + region: "EmailSettings" +}); diff --git a/imports/plugins/core/i18n/client/components/LocalizationSettingsRegion.js b/imports/plugins/core/i18n/client/components/LocalizationSettingsRegion.js new file mode 100644 index 0000000000..3fcdde2f42 --- /dev/null +++ b/imports/plugins/core/i18n/client/components/LocalizationSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function LocalizationSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.localization.header")} + + + > + ); +} diff --git a/imports/plugins/core/i18n/client/index.js b/imports/plugins/core/i18n/client/index.js index f109f61304..09584494aa 100644 --- a/imports/plugins/core/i18n/client/index.js +++ b/imports/plugins/core/i18n/client/index.js @@ -1,17 +1,21 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faGlobe } from "@fortawesome/free-solid-svg-icons"; - import { registerOperatorRoute } from "/imports/client/ui"; +import { registerBlock } from "@reactioncommerce/reaction-components"; +import LocalizationSettingsRegion from "./components/LocalizationSettingsRegion"; import Localization from "./containers/localizationSettings"; export { default as LocalizationSettings } from "./containers/localizationSettings"; registerOperatorRoute({ group: "settings", - MainComponent: Localization, - path: "/localization", - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , - sidebarI18nLabel: "admin.i18nSettings.shopLocalization" + MainComponent: LocalizationSettingsRegion, + path: "/settings/localization", + sidebarI18nLabel: "admin.i18nSettings.shopLocalization", + priority: 160 +}); + +registerBlock({ + component: Localization, + name: "LocalizationSettingsGeneral", + priority: 1, + region: "LocalizationSettings" }); diff --git a/imports/plugins/core/inventory/client/InventorySettings.js b/imports/plugins/core/inventory/client/InventorySettings.js index 234dd2f14a..655cf2f96a 100644 --- a/imports/plugins/core/inventory/client/InventorySettings.js +++ b/imports/plugins/core/inventory/client/InventorySettings.js @@ -7,6 +7,14 @@ import CardContent from "@material-ui/core/CardContent"; import CardHeader from "@material-ui/core/CardHeader"; import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + card: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3) + } +})); /** * Inventory settings form block component @@ -20,6 +28,7 @@ function InventorySettings(props) { updateInventoryShopSettings, shopId } = props; + const classes = useStyles(); if (isLoadingInventoryShopSettings) return ; @@ -28,7 +37,7 @@ function InventorySettings(props) { } = inventoryShopSettings || {}; return ( - + - ); } diff --git a/imports/plugins/core/payments/client/PaymentSettingsRegion.js b/imports/plugins/core/payments/client/PaymentSettingsRegion.js new file mode 100644 index 0000000000..4cd2e6ac77 --- /dev/null +++ b/imports/plugins/core/payments/client/PaymentSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function PaymentSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.payment.header")} + + + > + ); +} diff --git a/imports/plugins/core/payments/client/index.js b/imports/plugins/core/payments/client/index.js index ad8f6c4eea..61727e6abd 100644 --- a/imports/plugins/core/payments/client/index.js +++ b/imports/plugins/core/payments/client/index.js @@ -1,15 +1,19 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCreditCard } from "@fortawesome/free-solid-svg-icons"; import { registerOperatorRoute } from "/imports/client/ui"; -import PaymentSettings from "./PaymentSettings.js"; +import { registerBlock } from "@reactioncommerce/reaction-components"; +import PaymentSettingsRegion from "./PaymentSettingsRegion"; +import PaymentSettings from "./PaymentSettings"; registerOperatorRoute({ group: "settings", - MainComponent: PaymentSettings, - priority: 20, - path: "/payment", - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , + MainComponent: PaymentSettingsRegion, + priority: 120, + path: "/settings/payment", sidebarI18nLabel: "admin.settings.paymentSettingsLabel" }); + +registerBlock({ + region: "PaymentSettings", + name: "PaymentSettings", + component: PaymentSettings, + priority: 1 +}); diff --git a/imports/plugins/core/shipping/client/components/DefaultParcelSizeForm.js b/imports/plugins/core/shipping/client/components/DefaultParcelSizeForm.js index 6829885579..495fc7c841 100644 --- a/imports/plugins/core/shipping/client/components/DefaultParcelSizeForm.js +++ b/imports/plugins/core/shipping/client/components/DefaultParcelSizeForm.js @@ -8,7 +8,7 @@ import SimpleSchema from "simpl-schema"; import Button from "@reactioncommerce/catalyst/Button"; import Grid from "@material-ui/core/Grid"; import TextField from "@material-ui/core/TextField"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; @@ -159,7 +159,7 @@ export default function DefaultParcelSizeForm(props) { diff --git a/imports/plugins/core/shipping/client/components/Shipping.js b/imports/plugins/core/shipping/client/components/ShippingSettings.js similarity index 61% rename from imports/plugins/core/shipping/client/components/Shipping.js rename to imports/plugins/core/shipping/client/components/ShippingSettings.js index c3e45a1690..e15b850e59 100644 --- a/imports/plugins/core/shipping/client/components/Shipping.js +++ b/imports/plugins/core/shipping/client/components/ShippingSettings.js @@ -1,14 +1,19 @@ import React from "react"; -import { makeStyles } from "@material-ui/styles"; -import Card from "@material-ui/core/Card"; -import CardContent from "@material-ui/core/CardContent"; -import CardHeader from "@material-ui/core/CardHeader"; -import { Blocks } from "@reactioncommerce/reaction-components"; -import { i18next } from "/client/api"; +import { + Card, + CardContent, + CardHeader, + makeStyles, + Typography +} from "@material-ui/core"; +import i18next from "i18next"; import useCurrentShop from "/imports/client/ui/hooks/useCurrentShop.js"; import DefaultParcelSizeForm from "./DefaultParcelSizeForm.js"; const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + }, topCard: { marginBottom: theme.spacing(2) } @@ -16,18 +21,19 @@ const useStyles = makeStyles((theme) => ({ /** * @summary Renders shipping page - * @param {Object} props Component props * @return {React.Node} React node */ -export default function Shipping(props) { +export default function Shipping() { const classes = useStyles(); const { refetchShop, shop } = useCurrentShop(); if (!shop) return null; return ( - - Shipping + <> + + {i18next.t("admin.shippingSettings.header")} + - - + > ); } diff --git a/imports/plugins/core/shipping/client/components/ShippingSettingsRegion.js b/imports/plugins/core/shipping/client/components/ShippingSettingsRegion.js new file mode 100644 index 0000000000..017c83bc88 --- /dev/null +++ b/imports/plugins/core/shipping/client/components/ShippingSettingsRegion.js @@ -0,0 +1,13 @@ +import React from "react"; +import { Blocks } from "@reactioncommerce/reaction-components"; + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function ShippingSettingsRegion(props) { + return ( + + ); +} diff --git a/imports/plugins/core/shipping/client/index.js b/imports/plugins/core/shipping/client/index.js index 6b8026b6e4..d6ae16245e 100644 --- a/imports/plugins/core/shipping/client/index.js +++ b/imports/plugins/core/shipping/client/index.js @@ -1,16 +1,19 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faShippingFast } from "@fortawesome/free-solid-svg-icons"; - +import { registerBlock } from "@reactioncommerce/reaction-components"; import { registerOperatorRoute } from "/imports/client/ui"; -import Shipping from "./components/Shipping"; +import ShippingSettingsRegion from "./components/ShippingSettingsRegion"; +import ShippingSettings from "./components/ShippingSettings"; registerOperatorRoute({ group: "settings", - MainComponent: Shipping, - priority: 40, - path: "/shipping", - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , + MainComponent: ShippingSettingsRegion, + priority: 140, + path: "/settings/shipping", sidebarI18nLabel: "admin.dashboard.shippingLabel" }); + +registerBlock({ + component: ShippingSettings, + name: "GeneralShippingSettings", + priority: 1, + region: "ShippingSettings" +}); diff --git a/imports/plugins/core/taxes/client/components/TaxSettings.js b/imports/plugins/core/taxes/client/components/TaxSettings.js index 1b15a3b53c..f768132435 100644 --- a/imports/plugins/core/taxes/client/components/TaxSettings.js +++ b/imports/plugins/core/taxes/client/components/TaxSettings.js @@ -1,34 +1,29 @@ import React from "react"; -import { Blocks } from "@reactioncommerce/reaction-components"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import CardHeader from "@material-ui/core/CardHeader"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import GeneralTaxSettings from "../containers/GeneralTaxSettings"; const useStyles = makeStyles((theme) => ({ topCard: { - marginBottom: theme.spacing(1) + marginBottom: theme.spacing(2) } })); /** * @summary React component for the main Tax Settings area - * @param {Object} props - React props * @returns {Node} React node */ -export default function TaxSettings(props) { +export default function TaxSettings() { const classes = useStyles(); return ( - - - - - - - - - + + + + + + ); } diff --git a/imports/plugins/core/taxes/client/components/TaxSettingsRegion.js b/imports/plugins/core/taxes/client/components/TaxSettingsRegion.js new file mode 100644 index 0000000000..5f475bc812 --- /dev/null +++ b/imports/plugins/core/taxes/client/components/TaxSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function TaxSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.taxes.header")} + + + > + ); +} diff --git a/imports/plugins/core/taxes/client/index.js b/imports/plugins/core/taxes/client/index.js index 9f5ea9d4bc..a623894bb4 100644 --- a/imports/plugins/core/taxes/client/index.js +++ b/imports/plugins/core/taxes/client/index.js @@ -1,15 +1,21 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faUniversity } from "@fortawesome/free-solid-svg-icons"; +import { registerBlock } from "@reactioncommerce/reaction-components"; import { registerOperatorRoute } from "/imports/client/ui"; +import TaxSettingsRegion from "./components/TaxSettingsRegion"; import TaxSettings from "./components/TaxSettings"; registerOperatorRoute({ group: "settings", - MainComponent: TaxSettings, - priority: 30, - path: "/tax-settings", - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , + MainComponent: TaxSettingsRegion, + priority: 130, + path: "/settings/tax-settings", sidebarI18nLabel: "admin.dashboard.taxesLabel" }); + +registerBlock({ + component: TaxSettings, + name: "GeneralTaxSettings", + priority: 1, + region: "TaxSettings" +}); + + diff --git a/imports/plugins/core/templates/client/components/EmailTemplateSettingsRegion.js b/imports/plugins/core/templates/client/components/EmailTemplateSettingsRegion.js new file mode 100644 index 0000000000..b16dc3113e --- /dev/null +++ b/imports/plugins/core/templates/client/components/EmailTemplateSettingsRegion.js @@ -0,0 +1,30 @@ +import React from "react"; +import i18next from "i18next"; +import { Blocks } from "@reactioncommerce/reaction-components"; +import { + makeStyles, + Typography +} from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + header: { + marginBottom: theme.spacing(4) + } +})); + +/** + * @summary Renders payment settings page + * @param {Object} props Component props + * @return {React.Node} React node + */ +export default function EmailTemplateSettingsRegion(props) { + const classes = useStyles(); + return ( + <> + + {i18next.t("admin.settings.emailTemplates.header")} + + + > + ); +} diff --git a/imports/plugins/core/templates/client/index.js b/imports/plugins/core/templates/client/index.js index 7c0aa120a5..5a9538c0e5 100644 --- a/imports/plugins/core/templates/client/index.js +++ b/imports/plugins/core/templates/client/index.js @@ -1,16 +1,22 @@ -import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faColumns } from "@fortawesome/free-solid-svg-icons"; - +import { registerBlock } from "@reactioncommerce/reaction-components"; import { registerOperatorRoute } from "/imports/client/ui"; +import EmailTemplateSettingsRegion from "./components/EmailTemplateSettingsRegion"; +import { getReactComponentOrBlazeTemplate } from "/imports/plugins/core/components/lib/ReactComponentOrBlazeTemplate"; + import "./templates/settings.html"; import "./templates/settings.js"; registerOperatorRoute({ group: "settings", - path: "/templates", - MainComponent: "templateSettings", - // eslint-disable-next-line react/display-name - SidebarIconComponent: (props) => , - sidebarI18nLabel: "admin.settings.templateSettingsLabel" + path: "/settings/templates", + MainComponent: EmailTemplateSettingsRegion, + sidebarI18nLabel: "admin.settings.templateSettingsLabel", + priority: 190 +}); + +registerBlock({ + region: "EmailTemplateSettings", + name: "EmailTemplateSettingsGeneral", + component: () => getReactComponentOrBlazeTemplate("templateSettings"), + priority: 1 }); diff --git a/imports/plugins/included/discount-codes/client/DiscountCodes.js b/imports/plugins/included/discount-codes/client/DiscountCodes.js index c12fe3d0ca..a559f54e92 100644 --- a/imports/plugins/included/discount-codes/client/DiscountCodes.js +++ b/imports/plugins/included/discount-codes/client/DiscountCodes.js @@ -1,5 +1,5 @@ import React from "react"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import CardHeader from "@material-ui/core/CardHeader"; diff --git a/imports/plugins/included/sitemap-generator/client/components/SitemapSettings.js b/imports/plugins/included/sitemap-generator/client/components/SitemapSettings.js index 08e133fbc9..da59b04f20 100644 --- a/imports/plugins/included/sitemap-generator/client/components/SitemapSettings.js +++ b/imports/plugins/included/sitemap-generator/client/components/SitemapSettings.js @@ -1,9 +1,8 @@ import React from "react"; -import PropTypes from "prop-types"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import { Components } from "@reactioncommerce/reaction-components"; import Select from "@reactioncommerce/components/Select/v1"; -import { useMutation, useQuery } from "@apollo/react-hooks"; +import { useMutation, useLazyQuery } from "@apollo/react-hooks"; import Button from "@reactioncommerce/catalyst/Button"; import Card from "@material-ui/core/Card"; import CardActions from "@material-ui/core/CardActions"; @@ -16,11 +15,14 @@ import Logger from "/client/modules/logger"; import generateSitemapsMutation from "../mutations/generateSitemaps"; import updateShopSettingsMutation from "../mutations/updateShopSettings"; import shopSettingsQuery from "../queries/shopSettings"; +import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId"; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ card: { // Without this, the Select dropdown menu gets cut off at the bottom of the card - overflow: "visible" + overflow: "visible", + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3) } })); @@ -41,29 +43,29 @@ const refreshOptions = [ /** * Sitemap settings form block component - * @param {Object} props Component props * @returns {Node} React node */ -function SitemapSettings(props) { - const { - shopId - } = props; - +export default function SitemapSettings() { + const [shopId] = useCurrentShopId(); const classes = useStyles(); + const [getShopSettings, { loading: loadingSettings, data, error, called }] = useLazyQuery(shopSettingsQuery); - const { loading, data, error } = useQuery(shopSettingsQuery, { - onError(fetchError) { - Logger.error(fetchError); - }, - variables: { - shopId - } - }); + if (error) { + Logger.error(error); + } + + if (shopId && !called) { + getShopSettings({ + variables: { + shopId + } + }); + } const [generateSitemaps] = useMutation(generateSitemapsMutation); const [updateShopSettings, { loading: isUpdatingShopSettings }] = useMutation(updateShopSettingsMutation); - if (loading) return ; + if (!called || loadingSettings) return ; if (error) { return ( @@ -75,7 +77,7 @@ function SitemapSettings(props) { ); } - const { sitemapRefreshPeriod } = data.shopSettings; + const { sitemapRefreshPeriod } = data && data.shopSettings; const onGenerateClick = () => { generateSitemaps(); @@ -121,9 +123,3 @@ function SitemapSettings(props) { ); } - -SitemapSettings.propTypes = { - shopId: PropTypes.string.isRequired -}; - -export default SitemapSettings; diff --git a/imports/plugins/included/sitemap-generator/client/index.js b/imports/plugins/included/sitemap-generator/client/index.js index ddc661764e..1d8142dfcb 100644 --- a/imports/plugins/included/sitemap-generator/client/index.js +++ b/imports/plugins/included/sitemap-generator/client/index.js @@ -1,4 +1,4 @@ -import { registerBlock } from "/imports/plugins/core/components/lib"; +import { registerBlock } from "@reactioncommerce/reaction-components"; import SitemapSettings from "./components/SitemapSettings"; registerBlock({ diff --git a/imports/plugins/included/taxes-rates/client/components/CustomTaxRateForm.js b/imports/plugins/included/taxes-rates/client/components/CustomTaxRateForm.js index 9977028e35..e707946ab2 100644 --- a/imports/plugins/included/taxes-rates/client/components/CustomTaxRateForm.js +++ b/imports/plugins/included/taxes-rates/client/components/CustomTaxRateForm.js @@ -9,7 +9,7 @@ import Button from "@reactioncommerce/catalyst/Button"; import Grid from "@material-ui/core/Grid"; import MenuItem from "@material-ui/core/MenuItem"; import TextField from "@material-ui/core/TextField"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; import CountryOptions from "@reactioncommerce/api-utils/CountryOptions.js"; @@ -288,7 +288,7 @@ export default function CustomTaxRateForm(props) { { deleteTaxRate({ variables: { diff --git a/imports/plugins/included/taxes-rates/client/components/CustomTaxRatesSettings.js b/imports/plugins/included/taxes-rates/client/components/CustomTaxRatesSettings.js index 3826c606c0..9de6742e7a 100644 --- a/imports/plugins/included/taxes-rates/client/components/CustomTaxRatesSettings.js +++ b/imports/plugins/included/taxes-rates/client/components/CustomTaxRatesSettings.js @@ -7,7 +7,7 @@ import CardContent from "@material-ui/core/CardContent"; import CardHeader from "@material-ui/core/CardHeader"; import Dialog from "@material-ui/core/Dialog"; import DialogContent from "@material-ui/core/DialogContent"; -import { makeStyles } from "@material-ui/styles"; +import { makeStyles } from "@material-ui/core"; import { Components } from "@reactioncommerce/reaction-components"; import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId"; import useCustomTaxRates from "../hooks/useCustomTaxRates.js"; diff --git a/imports/plugins/included/taxes-rates/client/index.js b/imports/plugins/included/taxes-rates/client/index.js index 26536445dc..0aa8db67db 100644 --- a/imports/plugins/included/taxes-rates/client/index.js +++ b/imports/plugins/included/taxes-rates/client/index.js @@ -1,4 +1,4 @@ -import { registerBlock } from "/imports/plugins/core/components/lib"; +import { registerBlock } from "@reactioncommerce/reaction-components"; import CustomTaxRatesSettings from "./components/CustomTaxRatesSettings.js"; registerBlock({ diff --git a/package-lock.json b/package-lock.json index ba2bebbbc0..dcf5a3d9c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2665,19 +2665,19 @@ } }, "@material-ui/core": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.0.tgz", - "integrity": "sha512-zrrr8mPU5DDBYaVil4uJYauW41PjSn5otn7cqGsmWOY0t90fypr9nNgM7rRJaPz2AP6oRSDx1kBQt2igf5uelg==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.2.tgz", + "integrity": "sha512-fSf/yBuE5GR7dA+FiQAAGY7HrCN/8RaYApi9tx3IKMiJIJkRCHk+E2lktgJZ+QRsaqCACLo2lwhU2CW5aeO0UQ==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/styles": "^4.9.0", - "@material-ui/system": "^4.7.1", + "@material-ui/system": "^4.9.1", "@material-ui/types": "^5.0.0", "@material-ui/utils": "^4.7.1", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", - "hoist-non-react-statics": "^3.2.1", + "hoist-non-react-statics": "^3.3.2", "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", @@ -2793,9 +2793,9 @@ } }, "@material-ui/lab": { - "version": "4.0.0-alpha.40", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.40.tgz", - "integrity": "sha512-VwXCNFJKfctu9Ot9XP5u2SSzXpm2Fn7F/o08bUfrJDkMCuRc8MCGVnNhT+guZRZa35rR97uWKc3SGQ/LAv8yEg==", + "version": "4.0.0-alpha.42", + "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.42.tgz", + "integrity": "sha512-JbKEMIXSslh03u6HNU1Pp1VXd9ycJ1dqkI+iQK6yR+Sng2mvMKzJ80GCV5ROXAXwwNnD8zHOopLZNIpTsEAVgQ==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.7.1", @@ -2804,77 +2804,6 @@ "react-is": "^16.8.0" }, "dependencies": { - "@material-ui/utils": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.7.1.tgz", - "integrity": "sha512-+ux0SlLdlehvzCk2zdQ3KiS3/ylWvuo/JwAGhvb8dFVvwR21K28z0PU9OQW2PGogrMEdvX3miEI5tGxTwwWiwQ==", - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0" - } - }, - "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" - } - } - }, - "@material-ui/styles": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.0.tgz", - "integrity": "sha512-nJHum4RqYBPWsjL/9JET8Z02FZ9gSizlg/7LWVFpIthNzpK6OQ5OSRR4T4x9/p+wK3t1qNn3b1uI4XpnZaPxOA==", - "requires": { - "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.7.4", - "@material-ui/types": "^5.0.0", - "@material-ui/utils": "^4.7.1", - "clsx": "^1.0.2", - "csstype": "^2.5.2", - "hoist-non-react-statics": "^3.2.1", - "jss": "^10.0.3", - "jss-plugin-camel-case": "^10.0.3", - "jss-plugin-default-unit": "^10.0.3", - "jss-plugin-global": "^10.0.3", - "jss-plugin-nested": "^10.0.3", - "jss-plugin-props-sort": "^10.0.3", - "jss-plugin-rule-value-function": "^10.0.3", - "jss-plugin-vendor-prefixer": "^10.0.3", - "prop-types": "^15.7.2" - }, - "dependencies": { - "@emotion/hash": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz", - "integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==" - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "jss": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.4.tgz", - "integrity": "sha512-GqHmeDK83qbqMAVjxyPfN1qJVTKZne533a9bdCrllZukUM8npG/k+JumEPI86IIB5ifaZAHG2HAsUziyxOiooQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "dependencies": { - "csstype": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", - "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" - } - } - }, "react-is": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", @@ -2883,20 +2812,15 @@ } }, "@material-ui/system": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.7.1.tgz", - "integrity": "sha512-zH02p+FOimXLSKOW/OT2laYkl9bB3dD1AvnZqsHYoseUaq0aVrpbl2BGjQi+vJ5lg8w73uYlt9zOWzb3+1UdMQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.1.tgz", + "integrity": "sha512-CLrJK2aKNWNwruGVTRf+rLz96P4jmozpY2UaCE6hBTa1oGsQ396YXOQQABQ4c0igawmdyf5iQb0zs9j5zsAf1w==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.7.1", "prop-types": "^15.7.2" } }, - "@material-ui/types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz", - "integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==" - }, "@material-ui/utils": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.7.1.tgz", @@ -3990,7 +3914,7 @@ "dependencies": { "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { "base64-js": "^1.0.2", diff --git a/package.json b/package.json index 0882d613ec..f455845651 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,8 @@ "@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/free-solid-svg-icons": "^5.7.1", "@fortawesome/react-fontawesome": "^0.1.4", - "@material-ui/core": "^4.9.0", - "@material-ui/lab": "^4.0.0-alpha.40", - "@material-ui/styles": "^4.9.0", + "@material-ui/core": "^4.9.2", + "@material-ui/lab": "^4.0.0-alpha.42", "@reactioncommerce/catalyst": "^1.19.1", "@reactioncommerce/components": "^0.69.0", "@reactioncommerce/components-context": "1.2.0",