From 7cdff74df7ce8b0ebdfe8cae3712f864d8e36783 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Wed, 8 Apr 2020 10:22:14 -0700 Subject: [PATCH 1/9] refactor: start to de-meteorize discount codes view Signed-off-by: Will Lopez --- .../client/components/DiscountCodeForm.js | 247 ++++++++++++++++++ .../client/components/DiscountCodesTable.js | 151 +++++++++++ .../graphql/Fragments/DiscountCodeCommon.js | 18 ++ .../graphql/mutations/createDiscountCode.js | 12 + .../client/graphql/queries/discountCodes.js | 14 + .../included/discount-codes/client/index.js | 10 +- 6 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js create mode 100644 imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js create mode 100644 imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js create mode 100644 imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js create mode 100644 imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js new file mode 100644 index 0000000000..e7deba5e5d --- /dev/null +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -0,0 +1,247 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import i18next from "i18next"; +import { useMutation } from "@apollo/react-hooks"; +import { useSnackbar } from "notistack"; +import SimpleSchema from "simpl-schema"; +import { Button, TextField } from "@reactioncommerce/catalyst"; +import { + Grid, + makeStyles, + MenuItem +} from "@material-ui/core"; +import muiOptions from "reacto-form/cjs/muiOptions"; +import useReactoForm from "reacto-form/cjs/useReactoForm"; +import createDiscountCodeGQL from "../graphql/mutations/createDiscountCode"; + +const useStyles = makeStyles((theme) => ({ + deleteButton: { + marginRight: theme.spacing(1) + }, + rightAlignedGrid: { + textAlign: "right" + }, + textField: { + marginBottom: theme.spacing(4), + minWidth: 350 + } +})); + +const formSchema = new SimpleSchema({ + "code": { + type: String + }, + "calculation": Object, + "calculation.method": { + type: String + }, + "conditions": Object, + "conditions.enabled": { + type: Boolean, + optional: true, + defaultValue: true + }, + "conditions.accountLimit": { + type: Number, + optional: true + }, + "conditions.redemptionLimit": { + type: Number, + optional: true + }, + "conditions.order": Object, + "conditions.order.min": { + type: Number, + defaultValue: 0 + }, + "description": { + type: String, + optional: true + }, + "discount": { + type: String, + optional: true + }, + "discountMethod": { + type: String + } +}); +const validator = formSchema.getFormValidator(); + +/** + * @summary React component that renders the form for adding, updating, or deleting + * a discount code record. + * @param {Object} props React props + * @return {React.Node} React node + */ +export default function DiscountCodeForm(props) { + const classes = useStyles(); + const [isSubmitting, setIsSubmitting] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + + const { doc, onSuccess = () => { }, shopId } = props; + const calculationMethods = [ + { label: "Credit", value: "credit" }, + { label: "Discount", value: "discount" }, + { label: "Sale", value: "sale" }, + { label: "Shipping", value: "shipping" } + ]; + + const [createDiscountCode] = useMutation(createDiscountCodeGQL, { + ignoreResults: true, + onCompleted() { + setIsSubmitting(false); + onSuccess(); + }, + onError() { + setIsSubmitting(false); + enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + } + }); + + const { + getFirstErrorMessage, + getInputProps, + hasErrors, + submitForm + } = useReactoForm({ + async onSubmit(formData) { + setIsSubmitting(true); + + if (doc) { + // Update discount code + } else { + await createDiscountCode({ + variables: { + input: { + // In case doc has additional fields not allowed, we'll copy just what we want + code: formData.code, + calculation: { + method: formData.calculation.method + }, + conditions: { + enabled: formData.conditions.enabled, + accountLimit: formData.conditions.accountLimit, + redemptionLimit: formData.conditions.redemptionLimit, + order: { + // Set to 0(can be applied infinitely) + min: 0 + } + }, + description: formData.description, + shopId, + discount: formData.discount, + discountMethod: formData.discountMethod + } + } + }); + } + }, + validator(formData) { + return validator({ + // In case doc has additional fields not allowed, we'll copy just what we want + code: formData.code, + calculation: { + method: formData.calculation.method + }, + conditions: { + enabled: formData.conditions.enabled, + accountLimit: formData.conditions.accountLimit, + redemptionLimit: formData.conditions.redemptionLimit, + order: { + // Set to 0(can be applied infinitely) + min: 0 + } + }, + description: formData.description, + shopId, + discount: formData.discount, + discountMethod: formData.discountMethod + }); + }, + value: doc + }); + + let calculationMethodField; + if (Array.isArray(calculationMethods) && calculationMethods.length) { + const options = calculationMethods.map(({ value, label }) => ({ label, value })); + calculationMethodField = ( + { + if (event.key === "Enter") submitForm(); + }} + select + {...getInputProps("calculation.method", muiOptions)} + > + {options.map((option) => ( + + {option.label} + + ))} + + ); + } + + return ( +
+ { + if (event.key === "Enter") submitForm(); + }} + placeholder={i18next.t("admin.discountCode.form.codePlaceholder")} + {...getInputProps("code", muiOptions)} + /> + {calculationMethodField} + + {!!doc && + } + + +
+ ); +} + +DiscountCodeForm.propTypes = { + /** + * Function to call after form is successfully submitted + */ + onSuccess: PropTypes.func, + /** + * Shop ID to create/edit tax rate for + */ + shopId: PropTypes.string.isRequired +}; diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js new file mode 100644 index 0000000000..dac7a8054f --- /dev/null +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js @@ -0,0 +1,151 @@ +import React, { useState, useMemo, useCallback } from "react"; +import i18next from "i18next"; +import { useSnackbar } from "notistack"; +import { + Button, + DataTable, + useDataTable, + useConfirmDialog +} + from "@reactioncommerce/catalyst"; +import { Box, Card, CardHeader, CardContent, makeStyles } from "@material-ui/core"; +import { useApolloClient } from "@apollo/react-hooks"; +import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId"; +import discountCodesQuery from "../graphql/queries/discountCodes"; + +const useStyles = makeStyles({ + card: { + overflow: "visible" + } +}); + +/** + * @name DiscountCodesTable + * @returns {React.Component} A React component + */ +function DiscountCodesTable() { + const apolloClient = useApolloClient(); + const { enqueueSnackbar } = useSnackbar(); + const [isLoading, setIsLoading] = useState(false); + const [pageCount, setPageCount] = useState(1); + const [tableData, setTableData] = useState([]); + const [shopId] = useCurrentShopId(); + + // Create and memoize the column data + const columns = useMemo( + () => [ + { + Header: i18next.t("admin.discountsTable.headers.code"), + accessor: "code" + }, + { + Header: i18next.t("admin.discountsTable.headers.discount"), + accessor: "discount" + }, + { + Header: i18next.t("admin.discountsTable.headers.discountMethod"), + accessor: "calculation.method" + }, + { + Header: i18next.t("admin.discountsTable.headers.conditions.accountLimit"), + accessor: "conditions.accountLimit" + }, + { + Header: i18next.t("admin.discountsTable.headers.conditions.redemptionLimit"), + accessor: "conditions.redemptionLimit" + } + ], + [] + ); + + const onFetchData = useCallback( + async ({ globalFilter, pageIndex, pageSize }) => { + // Wait for shop id to be available before fetching orders. + setIsLoading(true); + if (!shopId) { + return; + } + + const { data, error } = await apolloClient.query({ + query: discountCodesQuery, + variables: { + shopId, + first: pageSize, + offset: pageIndex * pageSize, + filters: { + searchField: globalFilter + } + }, + fetchPolicy: "network-only" + }); + + if (error && error.length) { + enqueueSnackbar(i18next.t("admin.table.error", { variant: "error" })); + return; + } + + // Update the state with the fetched data as an array of objects and the calculated page count + setTableData(data.discountCodes.nodes); + setPageCount(Math.ceil(data.discountCodes.totalCount / pageSize)); + + setIsLoading(false); + }, + [apolloClient, enqueueSnackbar, shopId] + ); + + // Create Discount form modal + const { ConfirmDialog, openDialog } = useConfirmDialog({ + title: i18next.t("admin.discountCode.addDiscountModalTitle"), + content: ( +

Hello from the modal!

+ ), + onConfirm: () => { + console.log("Action confirmed"); + } + }); + + // Row click callback + const onRowClick = useCallback(() => { + openDialog(); + }, [openDialog]); + + const labels = useMemo( + () => ({ + globalFilterPlaceholder: i18next.t("admin.discountsTable.filterPlaceholder") + }), + [] + ); + + const dataTableProps = useDataTable({ + columns, + data: tableData, + labels, + pageCount, + onFetchData, + onRowClick, + getRowId: (row) => row._id + }); + + + const classes = useStyles(); + + return ( + <> + + + + + + + + + + + + + ); +} + +export default DiscountCodesTable; diff --git a/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js b/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js new file mode 100644 index 0000000000..85d577367c --- /dev/null +++ b/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js @@ -0,0 +1,18 @@ +import gql from "graphql-tag"; + +export default gql` + fragment DiscountCodeCommon on DiscountCode { + code + discount + calculation { + method + } + conditions { + enabled + accountLimit + redemptionLimit + } + description + discountMethod + }`; + diff --git a/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js b/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js new file mode 100644 index 0000000000..d77b61f0d1 --- /dev/null +++ b/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js @@ -0,0 +1,12 @@ +import gql from "graphql-tag"; + +export default gql` + mutation createDiscountCodeMutation($input: CreateDiscountCodeInput!) { + createDiscountCode(input: $input) { + nodes { + _id + } + } + } +`; + diff --git a/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js b/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js new file mode 100644 index 0000000000..5d428a4bb1 --- /dev/null +++ b/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js @@ -0,0 +1,14 @@ +import gql from "graphql-tag"; +import discountCodeFragment from "../Fragments/DiscountCodeCommon"; + +export default gql` + query discountCodesQuery($shopId: ID!) { + discountCodes(shopId: $shopId) { + nodes { + ...DiscountCodeCommon + } + totalCount + } + } + ${discountCodeFragment} +`; diff --git a/imports/plugins/included/discount-codes/client/index.js b/imports/plugins/included/discount-codes/client/index.js index 58e97f6888..04fdde8990 100644 --- a/imports/plugins/included/discount-codes/client/index.js +++ b/imports/plugins/included/discount-codes/client/index.js @@ -2,10 +2,18 @@ import { registerBlock } from "/imports/plugins/core/components/lib"; import "./collections/codes"; import "./templates"; import DiscountCodes from "./DiscountCodes.js"; +import DiscountCodesTable from "./components/DiscountCodesTable"; registerBlock({ region: "Discounts", name: "DiscountCodes", - component: DiscountCodes, + component: DiscountCodesTable, priority: 1 }); + +registerBlock({ + region: "Discounts", + name: "DiscountCodesOLD", + component: DiscountCodes, + priority: 2 +}); From b2d1dc975816d2d43281fb836d68fea02800270a Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Wed, 8 Apr 2020 16:47:24 -0700 Subject: [PATCH 2/9] refactor: start to wire up the new discount code form Signed-off-by: Will Lopez --- .../client/components/DiscountCodeForm.js | 240 ++++++++++++------ .../client/components/DiscountCodesTable.js | 32 +-- .../graphql/Fragments/DiscountCodeCommon.js | 25 +- .../graphql/mutations/createDiscountCode.js | 4 +- 4 files changed, 190 insertions(+), 111 deletions(-) diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index e7deba5e5d..680ad7cf8f 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -4,8 +4,12 @@ import i18next from "i18next"; import { useMutation } from "@apollo/react-hooks"; import { useSnackbar } from "notistack"; import SimpleSchema from "simpl-schema"; -import { Button, TextField } from "@reactioncommerce/catalyst"; +import { Button, TextField, ConfirmDialog } from "@reactioncommerce/catalyst"; import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, Grid, makeStyles, MenuItem @@ -14,16 +18,10 @@ import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; import createDiscountCodeGQL from "../graphql/mutations/createDiscountCode"; -const useStyles = makeStyles((theme) => ({ - deleteButton: { - marginRight: theme.spacing(1) - }, - rightAlignedGrid: { - textAlign: "right" - }, - textField: { - marginBottom: theme.spacing(4), - minWidth: 350 +const useStyles = makeStyles(() => ({ + dialogTitle: { + fontSize: 18, + fontWeight: 500 } })); @@ -63,7 +61,8 @@ const formSchema = new SimpleSchema({ optional: true }, "discountMethod": { - type: String + type: String, + defaultValue: "code" } }); const validator = formSchema.getFormValidator(); @@ -75,11 +74,11 @@ const validator = formSchema.getFormValidator(); * @return {React.Node} React node */ export default function DiscountCodeForm(props) { - const classes = useStyles(); const [isSubmitting, setIsSubmitting] = useState(false); + const [isCreateMode, setIsCreateMode] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const { doc, onSuccess = () => { }, shopId } = props; + const { discountCode, onSuccess = () => { }, shopId, isOpen, onCloseDialog } = props; const calculationMethods = [ { label: "Credit", value: "credit" }, { label: "Discount", value: "discount" }, @@ -92,9 +91,11 @@ export default function DiscountCodeForm(props) { onCompleted() { setIsSubmitting(false); onSuccess(); + onCloseDialog(); }, onError() { setIsSubmitting(false); + onCloseDialog(); enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); } }); @@ -102,64 +103,69 @@ export default function DiscountCodeForm(props) { const { getFirstErrorMessage, getInputProps, + isDirty, hasErrors, submitForm } = useReactoForm({ async onSubmit(formData) { setIsSubmitting(true); - if (doc) { + if (discountCode) { // Update discount code } else { + console.log("submitting with data: ", formData); await createDiscountCode({ variables: { input: { - // In case doc has additional fields not allowed, we'll copy just what we want - code: formData.code, - calculation: { - method: formData.calculation.method - }, - conditions: { - enabled: formData.conditions.enabled, - accountLimit: formData.conditions.accountLimit, - redemptionLimit: formData.conditions.redemptionLimit, - order: { + discountCode: { + // In case discountCode has additional fields not allowed, we'll copy just what we want + code: formData.code, + calculation: { + method: formData.calculation.method + }, + conditions: { + enabled: formData.conditions.enabled, + accountLimit: formData.conditions.accountLimit, + redemptionLimit: formData.conditions.redemptionLimit, + order: { // Set to 0(can be applied infinitely) - min: 0 - } - }, - description: formData.description, - shopId, - discount: formData.discount, - discountMethod: formData.discountMethod + min: 0 + } + }, + description: formData.description, + shopId, + discount: formData.discount, + discountMethod: formData.discountMethod + } } } }); } }, validator(formData) { + console.log("data to validate", formData); + return validator({ - // In case doc has additional fields not allowed, we'll copy just what we want + // In case discountCode has additional fields not allowed, we'll copy just what we want code: formData.code, calculation: { method: formData.calculation.method }, conditions: { - enabled: formData.conditions.enabled, - accountLimit: formData.conditions.accountLimit, - redemptionLimit: formData.conditions.redemptionLimit, + // enabled: formData.conditions.enabled, + accountLimit: Number(formData.conditions.accountLimit), + redemptionLimit: Number(formData.conditions.redemptionLimit), order: { // Set to 0(can be applied infinitely) min: 0 } }, - description: formData.description, - shopId, - discount: formData.discount, - discountMethod: formData.discountMethod + // description: formData.description, + discountMethod: "code", + discount: formData.discount }); }, - value: doc + value: discountCode }); let calculationMethodField; @@ -167,7 +173,6 @@ export default function DiscountCodeForm(props) { const options = calculationMethods.map(({ value, label }) => ({ label, value })); calculationMethodField = ( - { - if (event.key === "Enter") submitForm(); - }} - placeholder={i18next.t("admin.discountCode.form.codePlaceholder")} - {...getInputProps("code", muiOptions)} - /> - {calculationMethodField} - - {!!doc && + + + + {i18next.t("admin.discountCode.addDiscountModalTitle")} + + + + + + + + + + + + + + + { + if (event.key === "Enter") submitForm(); + }} + placeholder={i18next.t("admin.discountCode.form.redemptionLimitPlaceholder")} + {...getInputProps("conditions.redemptionLimit", muiOptions)} + /> + + + {calculationMethodField} + + + + + { !isCreateMode && ( + { + // deleteDiscountCode({ + // variables: { + // input: { + // shopId, + // discountCodeId: discountCode._id + // } + // } + // }); + console.log("Delete"); + onCloseDialog(); + }} + > + {({ openDialog }) => ( + + + )} + + )} + } - - + {i18next.t("app.save")} + + + ); } DiscountCodeForm.propTypes = { + /** + * A discount code record + */ + discountCode: PropTypes.object, + /** + * Determines whether the form dialog is open or not + */ + isOpen: PropTypes.bool, + /** + * Function that closes the form dialog + */ + onCloseDialog: PropTypes.func, /** * Function to call after form is successfully submitted */ @@ -243,5 +319,5 @@ DiscountCodeForm.propTypes = { /** * Shop ID to create/edit tax rate for */ - shopId: PropTypes.string.isRequired + shopId: PropTypes.string }; diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js index dac7a8054f..3bc6a0fcbd 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js @@ -4,14 +4,14 @@ import { useSnackbar } from "notistack"; import { Button, DataTable, - useDataTable, - useConfirmDialog + useDataTable } from "@reactioncommerce/catalyst"; import { Box, Card, CardHeader, CardContent, makeStyles } from "@material-ui/core"; import { useApolloClient } from "@apollo/react-hooks"; import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId"; import discountCodesQuery from "../graphql/queries/discountCodes"; +import DiscountCodeForm from "./DiscountCodeForm"; const useStyles = makeStyles({ card: { @@ -29,6 +29,7 @@ function DiscountCodesTable() { const [isLoading, setIsLoading] = useState(false); const [pageCount, setPageCount] = useState(1); const [tableData, setTableData] = useState([]); + const [isDialogOpen, setIsDialogOpen] = useState(false); const [shopId] = useCurrentShopId(); // Create and memoize the column data @@ -93,21 +94,18 @@ function DiscountCodesTable() { [apolloClient, enqueueSnackbar, shopId] ); - // Create Discount form modal - const { ConfirmDialog, openDialog } = useConfirmDialog({ - title: i18next.t("admin.discountCode.addDiscountModalTitle"), - content: ( -

Hello from the modal!

- ), - onConfirm: () => { - console.log("Action confirmed"); - } - }); + const openDialog = () => { + setIsDialogOpen(true); + }; + + const handleOnCloseDialog = () => { + setIsDialogOpen(false); + }; // Row click callback const onRowClick = useCallback(() => { openDialog(); - }, [openDialog]); + }, []); const labels = useMemo( () => ({ @@ -142,9 +140,13 @@ function DiscountCodesTable() { - + - ); } diff --git a/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js b/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js index 85d577367c..042118b888 100644 --- a/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js +++ b/imports/plugins/included/discount-codes/client/graphql/Fragments/DiscountCodeCommon.js @@ -2,17 +2,18 @@ import gql from "graphql-tag"; export default gql` fragment DiscountCodeCommon on DiscountCode { - code - discount - calculation { - method - } - conditions { - enabled - accountLimit - redemptionLimit - } - description - discountMethod + _id + code + discount + calculation { + method + } + conditions { + enabled + accountLimit + redemptionLimit + } + description + discountMethod }`; diff --git a/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js b/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js index d77b61f0d1..146757d1bb 100644 --- a/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js +++ b/imports/plugins/included/discount-codes/client/graphql/mutations/createDiscountCode.js @@ -1,9 +1,9 @@ import gql from "graphql-tag"; export default gql` - mutation createDiscountCodeMutation($input: CreateDiscountCodeInput!) { + mutation createDiscountCode($input: CreateDiscountCodeInput!) { createDiscountCode(input: $input) { - nodes { + discountCode { _id } } From 8e53e1529a97aae23e9045d784d654833e4381f3 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Thu, 9 Apr 2020 16:10:26 -0700 Subject: [PATCH 3/9] refactor: wire up discount code create operation Signed-off-by: Will Lopez --- .../client/components/DiscountCodeForm.js | 91 +++++++------------ .../client/components/DiscountCodesTable.js | 4 +- 2 files changed, 35 insertions(+), 60 deletions(-) diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index 680ad7cf8f..53e2b47ff2 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -4,8 +4,10 @@ import i18next from "i18next"; import { useMutation } from "@apollo/react-hooks"; import { useSnackbar } from "notistack"; import SimpleSchema from "simpl-schema"; +import _ from "lodash"; import { Button, TextField, ConfirmDialog } from "@reactioncommerce/catalyst"; import { + FormLabel, Dialog, DialogActions, DialogContent, @@ -25,7 +27,7 @@ const useStyles = makeStyles(() => ({ } })); -const formSchema = new SimpleSchema({ +const discountCodeSchema = new SimpleSchema({ "code": { type: String }, @@ -33,7 +35,10 @@ const formSchema = new SimpleSchema({ "calculation.method": { type: String }, - "conditions": Object, + "conditions": { + type: Object, + optional: true + }, "conditions.enabled": { type: Boolean, optional: true, @@ -47,25 +52,19 @@ const formSchema = new SimpleSchema({ type: Number, optional: true }, - "conditions.order": Object, - "conditions.order.min": { - type: Number, - defaultValue: 0 - }, "description": { type: String, optional: true }, "discount": { - type: String, - optional: true + type: String }, "discountMethod": { type: String, defaultValue: "code" } }); -const validator = formSchema.getFormValidator(); +const validator = discountCodeSchema.getFormValidator(); /** * @summary React component that renders the form for adding, updating, or deleting @@ -78,7 +77,7 @@ export default function DiscountCodeForm(props) { const [isCreateMode, setIsCreateMode] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const { discountCode, onSuccess = () => { }, shopId, isOpen, onCloseDialog } = props; + const { discountCode, isOpen, onCloseDialog, refetch, shopId } = props; const calculationMethods = [ { label: "Credit", value: "credit" }, { label: "Discount", value: "discount" }, @@ -90,7 +89,7 @@ export default function DiscountCodeForm(props) { ignoreResults: true, onCompleted() { setIsSubmitting(false); - onSuccess(); + refetch(); onCloseDialog(); }, onError() { @@ -113,59 +112,30 @@ export default function DiscountCodeForm(props) { if (discountCode) { // Update discount code } else { - console.log("submitting with data: ", formData); + const discountCodeInput = discountCodeSchema.clean(formData); + if (discountCodeInput.conditions) { + // Set order minimum to 0, this will allow a discount to be + // Redeemed infinitely on any number of orders. + _.set(discountCodeInput, "conditions.order.min", 0); + } + + console.log("submitting with data: ", discountCodeInput); + await createDiscountCode({ variables: { input: { - discountCode: { - // In case discountCode has additional fields not allowed, we'll copy just what we want - code: formData.code, - calculation: { - method: formData.calculation.method - }, - conditions: { - enabled: formData.conditions.enabled, - accountLimit: formData.conditions.accountLimit, - redemptionLimit: formData.conditions.redemptionLimit, - order: { - // Set to 0(can be applied infinitely) - min: 0 - } - }, - description: formData.description, - shopId, - discount: formData.discount, - discountMethod: formData.discountMethod - } + discountCode: discountCodeInput, + shopId } } }); } }, validator(formData) { - console.log("data to validate", formData); - - return validator({ - // In case discountCode has additional fields not allowed, we'll copy just what we want - code: formData.code, - calculation: { - method: formData.calculation.method - }, - conditions: { - // enabled: formData.conditions.enabled, - accountLimit: Number(formData.conditions.accountLimit), - redemptionLimit: Number(formData.conditions.redemptionLimit), - order: { - // Set to 0(can be applied infinitely) - min: 0 - } - }, - // description: formData.description, - discountMethod: "code", - discount: formData.discount - }); + return validator(discountCodeSchema.clean(formData)); }, - value: discountCode + value: discountCode, + logErrorsOnSubmit: true }); let calculationMethodField; @@ -224,6 +194,12 @@ export default function DiscountCodeForm(props) { {...getInputProps("discount", muiOptions)} /> + + {calculationMethodField} + + + Conditions + - - {calculationMethodField} - @@ -315,7 +288,7 @@ DiscountCodeForm.propTypes = { /** * Function to call after form is successfully submitted */ - onSuccess: PropTypes.func, + refetch: PropTypes.func, /** * Shop ID to create/edit tax rate for */ diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js index 3bc6a0fcbd..924c001bff 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js @@ -124,6 +124,7 @@ function DiscountCodesTable() { getRowId: (row) => row._id }); + const { refetch } = dataTableProps; const classes = useStyles(); @@ -141,10 +142,11 @@ function DiscountCodesTable() { ); From ce40299323b0b1ccbacc2db512a4f81583f64ac7 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Thu, 9 Apr 2020 17:01:43 -0700 Subject: [PATCH 4/9] refactor: wire up deleteDiscountCode operation Signed-off-by: Will Lopez --- .../client/components/DiscountCodeForm.js | 55 +++++++++++++------ .../client/components/DiscountCodesTable.js | 17 +++++- .../graphql/mutations/deleteDiscountCode.js | 12 ++++ .../included/discount-codes/client/index.js | 8 --- 4 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 imports/plugins/included/discount-codes/client/graphql/mutations/deleteDiscountCode.js diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index 53e2b47ff2..f53086a907 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -19,11 +19,15 @@ import { import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; import createDiscountCodeGQL from "../graphql/mutations/createDiscountCode"; +import deleteDiscountCodeGQL from "../graphql/mutations/deleteDiscountCode"; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ dialogTitle: { fontSize: 18, fontWeight: 500 + }, + legend: { + marginBottom: theme.spacing(1) } })); @@ -74,10 +78,9 @@ const validator = discountCodeSchema.getFormValidator(); */ export default function DiscountCodeForm(props) { const [isSubmitting, setIsSubmitting] = useState(false); - const [isCreateMode, setIsCreateMode] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const { discountCode, isOpen, onCloseDialog, refetch, shopId } = props; + const { discountCode, isCreateMode, isOpen, onCloseDialog, refetch, shopId } = props; const calculationMethods = [ { label: "Credit", value: "credit" }, { label: "Discount", value: "discount" }, @@ -99,6 +102,20 @@ export default function DiscountCodeForm(props) { } }); + const [deleteDiscountCode] = useMutation(deleteDiscountCodeGQL, { + ignoreResults: true, + onCompleted() { + refetch(); + setIsSubmitting(false); + onCloseDialog(); + }, + onError() { + setIsSubmitting(false); + onCloseDialog(); + enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + } + }); + const { getFirstErrorMessage, getInputProps, @@ -111,7 +128,9 @@ export default function DiscountCodeForm(props) { if (discountCode) { // Update discount code + console.log("Update discount code"); } else { + // Create a new discount code const discountCodeInput = discountCodeSchema.clean(formData); if (discountCodeInput.conditions) { // Set order minimum to 0, this will allow a discount to be @@ -119,8 +138,6 @@ export default function DiscountCodeForm(props) { _.set(discountCodeInput, "conditions.order.min", 0); } - console.log("submitting with data: ", discountCodeInput); - await createDiscountCode({ variables: { input: { @@ -198,7 +215,9 @@ export default function DiscountCodeForm(props) { {calculationMethodField} - Conditions + + Conditions + { - // deleteDiscountCode({ - // variables: { - // input: { - // shopId, - // discountCodeId: discountCode._id - // } - // } - // }); - console.log("Delete"); - onCloseDialog(); + onConfirm={async () => { + await deleteDiscountCode({ + variables: { + input: { + shopId, + discountCodeId: discountCode._id + } + } + }); }} > {({ openDialog }) => ( @@ -277,6 +294,10 @@ DiscountCodeForm.propTypes = { * A discount code record */ discountCode: PropTypes.object, + /** + * Determines if the dialog is in create or edit mode + */ + isCreateMode: PropTypes.bool, /** * Determines whether the form dialog is open or not */ diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js index 924c001bff..46ad879188 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodesTable.js @@ -30,6 +30,8 @@ function DiscountCodesTable() { const [pageCount, setPageCount] = useState(1); const [tableData, setTableData] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); + const [selectedDiscountCode, setSelectedDiscountCode] = useState(null); + const [isCreateMode, setIsCreateMode] = useState(false); const [shopId] = useCurrentShopId(); // Create and memoize the column data @@ -98,12 +100,20 @@ function DiscountCodesTable() { setIsDialogOpen(true); }; + const onCreateDiscountCode = () => { + setIsCreateMode(true); + setSelectedDiscountCode(null); + openDialog(); + }; + const handleOnCloseDialog = () => { setIsDialogOpen(false); }; // Row click callback - const onRowClick = useCallback(() => { + const onRowClick = useCallback((data) => { + setSelectedDiscountCode(data.row.original); + setIsCreateMode(false); openDialog(); }, []); @@ -131,7 +141,7 @@ function DiscountCodesTable() { return ( <> - @@ -142,7 +152,8 @@ function DiscountCodesTable() { Date: Tue, 14 Apr 2020 17:04:07 -0700 Subject: [PATCH 5/9] refactor: wire up update and filtering functionality Signed-off-by: Will Lopez --- .../client/components/DiscountCodeForm.js | 59 ++++++++++++++----- .../graphql/mutations/updateDiscountCode.js | 12 ++++ .../client/graphql/queries/discountCodes.js | 4 +- 3 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 imports/plugins/included/discount-codes/client/graphql/mutations/updateDiscountCode.js diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index f53086a907..796b9f58d5 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -20,6 +20,7 @@ import muiOptions from "reacto-form/cjs/muiOptions"; import useReactoForm from "reacto-form/cjs/useReactoForm"; import createDiscountCodeGQL from "../graphql/mutations/createDiscountCode"; import deleteDiscountCodeGQL from "../graphql/mutations/deleteDiscountCode"; +import updateDiscountCodeGQL from "../graphql/mutations/updateDiscountCode"; const useStyles = makeStyles((theme) => ({ dialogTitle: { @@ -88,31 +89,48 @@ export default function DiscountCodeForm(props) { { label: "Shipping", value: "shipping" } ]; + const onSuccess = () => { + setIsSubmitting(false); + refetch(); + onCloseDialog(); + }; + + const onFailure = () => { + setIsSubmitting(false); + onCloseDialog(); + enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + }; + const [createDiscountCode] = useMutation(createDiscountCodeGQL, { ignoreResults: true, onCompleted() { - setIsSubmitting(false); - refetch(); - onCloseDialog(); + onSuccess(); + enqueueSnackbar(i18next.t("admin.discountCode.createSuccess"), { variant: "success" }); }, onError() { - setIsSubmitting(false); - onCloseDialog(); - enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + onFailure(); } }); const [deleteDiscountCode] = useMutation(deleteDiscountCodeGQL, { ignoreResults: true, onCompleted() { - refetch(); - setIsSubmitting(false); - onCloseDialog(); + onSuccess(); + enqueueSnackbar(i18next.t("admin.discountCode.deleteSuccess"), { variant: "success" }); }, onError() { - setIsSubmitting(false); - onCloseDialog(); - enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + onFailure(); + } + }); + + const [updateDiscountCode] = useMutation(updateDiscountCodeGQL, { + ignoreResults: true, + onCompleted() { + onSuccess(); + enqueueSnackbar(i18next.t("admin.discountCode.updateSuccess"), { variant: "success" }); + }, + onError() { + onFailure(); } }); @@ -127,8 +145,21 @@ export default function DiscountCodeForm(props) { setIsSubmitting(true); if (discountCode) { - // Update discount code - console.log("Update discount code"); + const discountCodeInput = discountCodeSchema.clean(formData); + if (discountCodeInput.conditions) { + // Set order minimum to 0, this will allow a discount to be + // Redeemed infinitely on any number of orders. + _.set(discountCodeInput, "conditions.order.min", 0); + } + await updateDiscountCode({ + variables: { + input: { + discountCode: discountCodeInput, + discountCodeId: discountCode._id, + shopId + } + } + }); } else { // Create a new discount code const discountCodeInput = discountCodeSchema.clean(formData); diff --git a/imports/plugins/included/discount-codes/client/graphql/mutations/updateDiscountCode.js b/imports/plugins/included/discount-codes/client/graphql/mutations/updateDiscountCode.js new file mode 100644 index 0000000000..33ad234e8e --- /dev/null +++ b/imports/plugins/included/discount-codes/client/graphql/mutations/updateDiscountCode.js @@ -0,0 +1,12 @@ +import gql from "graphql-tag"; + +export default gql` + mutation updateDiscountCode($input: UpdateDiscountCodeInput!) { + updateDiscountCode(input: $input) { + discountCode { + _id + } + } + } +`; + diff --git a/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js b/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js index 5d428a4bb1..4fac6dc268 100644 --- a/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js +++ b/imports/plugins/included/discount-codes/client/graphql/queries/discountCodes.js @@ -2,8 +2,8 @@ import gql from "graphql-tag"; import discountCodeFragment from "../Fragments/DiscountCodeCommon"; export default gql` - query discountCodesQuery($shopId: ID!) { - discountCodes(shopId: $shopId) { + query discountCodesQuery($shopId: ID!, $filters: DiscountCodeFilterInput) { + discountCodes(shopId: $shopId, filters: $filters) { nodes { ...DiscountCodeCommon } From 55d3657622cc5ff004d81fefc36f5781cf42b753 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Tue, 14 Apr 2020 17:23:08 -0700 Subject: [PATCH 6/9] refactor: remove Meteor madness Signed-off-by: Will Lopez --- .../discount-codes/client/DiscountCodes.js | 35 --- .../client/collections/codes.js | 11 - .../included/discount-codes/client/index.js | 2 - .../discount-codes/client/templates/index.js | 1 - .../client/templates/settings.html | 71 ------ .../client/templates/settings.js | 210 ------------------ .../included/discount-codes/server/index.js | 2 - .../discount-codes/server/methods/index.js | 1 - .../discount-codes/server/methods/methods.js | 83 ------- .../server/publications/discounts.js | 43 ---- .../discount-codes/server/util/getCart.js | 56 ----- 11 files changed, 515 deletions(-) delete mode 100644 imports/plugins/included/discount-codes/client/DiscountCodes.js delete mode 100644 imports/plugins/included/discount-codes/client/collections/codes.js delete mode 100644 imports/plugins/included/discount-codes/client/templates/index.js delete mode 100644 imports/plugins/included/discount-codes/client/templates/settings.html delete mode 100644 imports/plugins/included/discount-codes/client/templates/settings.js delete mode 100644 imports/plugins/included/discount-codes/server/index.js delete mode 100644 imports/plugins/included/discount-codes/server/methods/index.js delete mode 100644 imports/plugins/included/discount-codes/server/methods/methods.js delete mode 100644 imports/plugins/included/discount-codes/server/publications/discounts.js delete mode 100644 imports/plugins/included/discount-codes/server/util/getCart.js diff --git a/imports/plugins/included/discount-codes/client/DiscountCodes.js b/imports/plugins/included/discount-codes/client/DiscountCodes.js deleted file mode 100644 index a559f54e92..0000000000 --- a/imports/plugins/included/discount-codes/client/DiscountCodes.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -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"; -import { i18next } from "/client/api"; -import ReactComponentOrBlazeTemplate from "/imports/plugins/core/components/lib/ReactComponentOrBlazeTemplate"; -import useCurrentInternalShopId from "/imports/client/ui/hooks/useCurrentInternalShopId.js"; - -const useStyles = makeStyles((theme) => ({ - topCard: { - marginBottom: theme.spacing(2) - } -})); - -/** - * @summary Renders discounts page - * @param {Object} props Component props - * @return {React.Node} React node - */ -export default function DiscountCodes(props) { - const classes = useStyles(); - const [internalShopId] = useCurrentInternalShopId(); - - return ( - - - - - - - ); -} diff --git a/imports/plugins/included/discount-codes/client/collections/codes.js b/imports/plugins/included/discount-codes/client/collections/codes.js deleted file mode 100644 index 36b675ba1a..0000000000 --- a/imports/plugins/included/discount-codes/client/collections/codes.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Mongo } from "meteor/mongo"; -import { DiscountCodes as DiscountSchema } from "../../lib/collections/schemas"; - -/** - * @name DiscountCodes - * @memberof Collections/ClientOnly - * @type {MongoCollection} - */ -export const DiscountCodes = new Mongo.Collection("DiscountCodes"); -// attach discount code specific schema -DiscountCodes.attachSchema(DiscountSchema); diff --git a/imports/plugins/included/discount-codes/client/index.js b/imports/plugins/included/discount-codes/client/index.js index b29fd406ea..5437f5839e 100644 --- a/imports/plugins/included/discount-codes/client/index.js +++ b/imports/plugins/included/discount-codes/client/index.js @@ -1,6 +1,4 @@ import { registerBlock } from "/imports/plugins/core/components/lib"; -import "./collections/codes"; -import "./templates"; import DiscountCodesTable from "./components/DiscountCodesTable"; registerBlock({ diff --git a/imports/plugins/included/discount-codes/client/templates/index.js b/imports/plugins/included/discount-codes/client/templates/index.js deleted file mode 100644 index 825272d31d..0000000000 --- a/imports/plugins/included/discount-codes/client/templates/index.js +++ /dev/null @@ -1 +0,0 @@ -import "./settings"; diff --git a/imports/plugins/included/discount-codes/client/templates/settings.html b/imports/plugins/included/discount-codes/client/templates/settings.html deleted file mode 100644 index 13b599e82d..0000000000 --- a/imports/plugins/included/discount-codes/client/templates/settings.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - diff --git a/imports/plugins/included/discount-codes/client/templates/settings.js b/imports/plugins/included/discount-codes/client/templates/settings.js deleted file mode 100644 index 4ab762f0db..0000000000 --- a/imports/plugins/included/discount-codes/client/templates/settings.js +++ /dev/null @@ -1,210 +0,0 @@ -import { $ } from "meteor/jquery"; -import { Meteor } from "meteor/meteor"; -import { Template } from "meteor/templating"; -import { ReactiveDict } from "meteor/reactive-dict"; -import { AutoForm } from "meteor/aldeed:autoform"; -import { DiscountCodes } from "../collections/codes"; -import { i18next } from "/client/api"; -import { DiscountCodes as DiscountSchema } from "../../lib/collections/schemas"; -import { IconButton, Loading, SortableTable } from "/imports/plugins/core/ui/client/components"; -import "./settings.html"; - -/* eslint no-shadow: ["error", { "allow": ["options"] }] */ -/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "[oO]ptions" }] */ - -Template.customDiscountCodes.onCreated(function () { - this.autorun(() => { - this.subscribe("DiscountCodes"); - }); - - this.state = new ReactiveDict(); - this.state.setDefault({ - isEditing: false, - editingId: null - }); -}); - -Template.customDiscountCodes.helpers({ - editButton() { - const instance = Template.instance(); - const { state } = instance; - const isEditing = state.equals("isEditing", true); - let editingId = state.get("editingId"); - // toggle edit state - if (!isEditing) { - editingId = null; - } - // return icon - return { - component: IconButton, - icon: "fa fa-plus", - onIcon: "fa fa-pencil", - toggle: true, - toggleOn: isEditing, - style: { - position: "relative", - top: "-25px", - right: "8px" - }, - onClick() { - // remove active rows from grid - $(".discount-codes-grid-row").removeClass("active"); - return state.set({ - isEditing: !isEditing, - editingId - }); - } - }; - }, - discountGrid() { - const filteredFields = ["code", "discount", "conditions.redemptionLimit", "calculation.method"]; - const noDataMessage = i18next.t("admin.settings.noCustomDiscountCodesFound"); - const instance = Template.instance(); - - /** - * @description helper to get and select row from griddle into blaze for to select discount row for editing - * @param {Object} options row data - * @returns {undefined} - */ - function editRow(options) { - const currentId = instance.state.get("editingId"); - // isEditing is discount rate object - instance.state.set("isEditing", options.props.data); - instance.state.set("editingId", options.props.data._id); - // toggle edit mode clicking on same row - if (currentId === options.props.data._id) { - instance.state.set("isEditing", null); - instance.state.set("editingId", null); - } - } - - // - // helper adds a class to every grid row - // - const customRowMetaData = { - bodyCssClassName: () => "discount-codes-grid-row" - }; - - // add i18n handling to headers - const customColumnMetadata = []; - filteredFields.forEach((field) => { - const columnMeta = { - accessor: field, - Header: i18next.t(`admin.discountGrid.${field}`) - }; - customColumnMetadata.push(columnMeta); - }); - - // return discount Grid - return { - component: SortableTable, - publication: "DiscountCodes", - collection: DiscountCodes, - matchingResultsCount: "discount-codes-count", - showFilter: true, - rowMetadata: customRowMetaData, - filteredFields, - columns: filteredFields, - noDataMessage, - onRowClick: editRow, - columnMetadata: customColumnMetadata, - externalLoadingComponent: Loading - }; - }, - - instance() { - const instance = Template.instance(); - return instance; - }, - // schema for forms - discountSchema() { - return DiscountSchema; - }, - - discountCode() { - const instance = Template.instance(); - const id = instance.state.get("editingId"); - const discount = DiscountCodes.findOne(id) || {}; - return discount; - } -}); - -// -// on submit lets clear the form state -// -Template.customDiscountCodes.events({ - "submit #discount-codes-update-form"() { - const instance = Template.instance(); - instance.state.set({ - isEditing: false, - editingId: null - }); - }, - "submit #discount-codes-insert-form"() { - const instance = Template.instance(); - instance.state.set({ - isEditing: true, - editingId: null - }); - }, - "click .cancel, .discount-codes-grid-row .active"() { - const instance = Template.instance(); - // remove active rows from grid - instance.state.set({ - isEditing: false, - editingId: null - }); - // ugly hack - $(".discount-codes-grid-row").removeClass("active"); - }, - "click .delete"() { - const confirmTitle = i18next.t("admin.settings.confirmRateDelete"); - const confirmButtonText = i18next.t("app.delete"); - const instance = Template.instance(); - const id = instance.state.get("editingId"); - // confirm delete - Alerts.alert({ - title: confirmTitle, - type: "warning", - showCancelButton: true, - confirmButtonText - }, (isConfirm) => { - if (isConfirm) { - if (id) { - Meteor.call("discounts/deleteCode", id); - instance.state.set({ - isEditing: false, - editingId: null - }); - } - } - }); - }, - "click .discount-codes-grid-row"(event) { - // toggle all rows off, then add our active row - $(".discount-codes-grid-row").removeClass("active"); - Template.instance().$(event.currentTarget).addClass("active"); - } -}); - -// -// Hooks for update and insert forms -// -AutoForm.hooks({ - "discount-codes-update-form": { - onSuccess() { - return Alerts.toast(i18next.t("admin.settings.settingsSaveSuccess"), "success"); - }, - onError(operation, error) { - return Alerts.toast(`${i18next.t("admin.settings.settingsSaveFailure")} ${error}`, "error"); - } - }, - "discount-codes-insert-form": { - onSuccess() { - return Alerts.toast(i18next.t("admin.settings.settingsSaveSuccess"), "success"); - }, - onError(operation, error) { - return Alerts.toast(`${i18next.t("admin.settings.settingsSaveFailure")} ${error}`, "error"); - } - } -}); diff --git a/imports/plugins/included/discount-codes/server/index.js b/imports/plugins/included/discount-codes/server/index.js deleted file mode 100644 index a571f12878..0000000000 --- a/imports/plugins/included/discount-codes/server/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import "./publications/discounts"; -import "./methods"; diff --git a/imports/plugins/included/discount-codes/server/methods/index.js b/imports/plugins/included/discount-codes/server/methods/index.js deleted file mode 100644 index c8abe684b3..0000000000 --- a/imports/plugins/included/discount-codes/server/methods/index.js +++ /dev/null @@ -1 +0,0 @@ -import "./methods"; diff --git a/imports/plugins/included/discount-codes/server/methods/methods.js b/imports/plugins/included/discount-codes/server/methods/methods.js deleted file mode 100644 index 412e2643aa..0000000000 --- a/imports/plugins/included/discount-codes/server/methods/methods.js +++ /dev/null @@ -1,83 +0,0 @@ -import { Meteor } from "meteor/meteor"; -import { check } from "meteor/check"; -import Reaction from "/imports/plugins/core/core/server/Reaction"; -import ReactionError from "@reactioncommerce/reaction-error"; -import { Discounts } from "/imports/plugins/core/discounts/lib/collections"; -import { DiscountCodes as DiscountSchema } from "../../lib/collections/schemas"; - -/** - * @namespace Discounts/Codes/Methods - */ - -// attach discount code specific schema -Discounts.attachSchema(DiscountSchema, { selector: { discountMethod: "code" } }); - -export const methods = { - /** - * @name discounts/addCode - * @method - * @memberof Discounts/Codes/Methods - * @param {Object} doc A Discounts document to be inserted - * @returns {String} Insert result - */ - "discounts/addCode"(doc) { - check(doc, Object); // actual schema validation happens during insert below - - const shopId = Reaction.getShopId(); - - if (!Reaction.hasPermission("reaction:legacy:discounts/create", Reaction.getUserId(), shopId)) { - throw new ReactionError("access-denied", "Access Denied"); - } - - doc.shopId = shopId; - return Discounts.insert(doc); - }, - - /** - * @name discounts/editCode - * @method - * @memberof Discounts/Codes/Methods - * @param {Object} details An object with _id and modifier props - * @returns {String} Update result - */ - "discounts/editCode"(details) { - check(details, { - _id: String, - modifier: Object // actual schema validation happens during update below - }); - - const shopId = Reaction.getShopId(); - - if (!Reaction.hasPermission("reaction:legacy:discounts/update", Reaction.getUserId(), shopId)) { - throw new ReactionError("access-denied", "Access Denied"); - } - - const { _id, modifier } = details; - return Discounts.update({ _id, shopId }, modifier); - }, - - /** - * @name discounts/deleteCode - * @method - * @memberof Discounts/Codes/Methods - * @param {String} discountId discount id to delete - * @returns {String} returns remove result - */ - "discounts/deleteCode"(discountId) { - check(discountId, String); - - const shopId = Reaction.getShopId(); - - if (!Reaction.hasPermission("reaction:legacy:discounts/delete", Reaction.getUserId(), shopId)) { - throw new ReactionError("access-denied", "Access Denied"); - } - - return Discounts.remove({ - _id: discountId, - shopId - }); - } -}; - -// export methods to Meteor -Meteor.methods(methods); diff --git a/imports/plugins/included/discount-codes/server/publications/discounts.js b/imports/plugins/included/discount-codes/server/publications/discounts.js deleted file mode 100644 index 0e11ee443d..0000000000 --- a/imports/plugins/included/discount-codes/server/publications/discounts.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Mongo } from "meteor/mongo"; -import { Meteor } from "meteor/meteor"; -import { Match, check } from "meteor/check"; -import { Counts } from "meteor/tmeasday:publish-counts"; -import { Discounts } from "/imports/plugins/core/discounts/lib/collections"; -import Reaction from "/imports/plugins/core/core/server/Reaction"; - -/** - * Discounts - * @type {Publication} - * @param {Object} query - * @param {Object} options - */ -Meteor.publish("DiscountCodes", function (query, options) { - check(query, Match.Optional(Object)); - check(options, Match.Optional(Object)); - - // check shopId - const shopId = Reaction.getShopId(); - if (!shopId) { - return this.ready(); - } - - const select = query || {}; - // append shopId to query - select.shopId = shopId; - // select.cartId = cartId; - if (!select.discountMethod) { - select.discountMethod = "code"; - } - - // appends a count to the collection - // we're doing this for use with griddleTable - Counts.publish(this, "discount-codes-count", Discounts.find( - select, - options - )); - - // Publishing our Discounts to a client side collection "DiscountCodes" - Mongo.Collection._publishCursor(Discounts.find(select, options), this, "DiscountCodes"); - - return this.ready(); -}); diff --git a/imports/plugins/included/discount-codes/server/util/getCart.js b/imports/plugins/included/discount-codes/server/util/getCart.js deleted file mode 100644 index 3aeae52f6f..0000000000 --- a/imports/plugins/included/discount-codes/server/util/getCart.js +++ /dev/null @@ -1,56 +0,0 @@ -import Logger from "@reactioncommerce/logger"; -import { Meteor } from "meteor/meteor"; -import hashToken from "@reactioncommerce/api-utils/hashToken.js"; -import ReactionError from "@reactioncommerce/reaction-error"; -import { Accounts, Cart } from "/lib/collections"; -import Reaction from "/imports/plugins/core/core/server/Reaction"; - -/** - * @summary Gets the current cart. Assumes a calling context where logged in userID can be retrieved. It works - * in all client code, in server methods, and in server publications. - * @param {String} [cartId] Limit the search by this cart ID if provided. - * @param {Object} [options] Options - * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart - * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` - * @returns {Object} An object with `cart` (the cart for the current account) - * and `account` (the account document in case the calling code needs it without another request) - */ -export default function getCart(cartId, { cartToken, throwIfNotFound = false } = {}) { - const shopId = Reaction.getPrimaryShopId(); - if (!shopId) { - throw new Meteor.Error("not-found", "Cart not found"); - } - - const userId = Reaction.getUserId(); - let account = null; - const selector = { shopId }; - if (cartId) { - selector._id = cartId; - } - - if (cartToken) { - selector.anonymousAccessToken = hashToken(cartToken); - } else { - account = (userId && Accounts.findOne({ userId })) || null; - - if (!account) { - if (throwIfNotFound) { - Logger.error(`Cart not found for user with ID ${userId}`); - throw new ReactionError("not-found", "Cart not found"); - } - - return { account, cart: null }; - } - - selector.accountId = account._id; - } - - const cart = Cart.findOne(selector) || null; - - if (!cart && throwIfNotFound) { - Logger.error(`Cart not found for user with ID ${userId}`); - throw new ReactionError("not-found", "Cart not found"); - } - - return { account, cart }; -} From 8ade18d23b66f913d26725c9d6382ae96be8ca45 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Thu, 16 Apr 2020 10:03:07 -0700 Subject: [PATCH 7/9] fix: wrong translation key Signed-off-by: Will Lopez --- .../discount-codes/client/components/DiscountCodeForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index 796b9f58d5..6a196559e9 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -98,7 +98,7 @@ export default function DiscountCodeForm(props) { const onFailure = () => { setIsSubmitting(false); onCloseDialog(); - enqueueSnackbar(i18next.t("admin.discountCodes.failure"), { variant: "warning" }); + enqueueSnackbar(i18next.t("admin.discountCode.failure"), { variant: "warning" }); }; const [createDiscountCode] = useMutation(createDiscountCodeGQL, { From 76ceece21b4df6cbd55a90471d1e7d69879417c4 Mon Sep 17 00:00:00 2001 From: Will Lopez Date: Thu, 16 Apr 2020 10:18:04 -0700 Subject: [PATCH 8/9] refactor: left align delete button Signed-off-by: Will Lopez --- .../discount-codes/client/components/DiscountCodeForm.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js index 6a196559e9..636f9a295d 100644 --- a/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js +++ b/imports/plugins/included/discount-codes/client/components/DiscountCodeForm.js @@ -23,6 +23,9 @@ import deleteDiscountCodeGQL from "../graphql/mutations/deleteDiscountCode"; import updateDiscountCodeGQL from "../graphql/mutations/updateDiscountCode"; const useStyles = makeStyles((theme) => ({ + deleteButton: { + marginRight: "auto" + }, dialogTitle: { fontSize: 18, fontWeight: 500 @@ -292,7 +295,9 @@ export default function DiscountCodeForm(props) { }} > {({ openDialog }) => ( -