-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert TaxCloud panel to React #3121
Changes from all commits
2a0e841
25e8e71
3ac4445
62b4e8a
7e207c1
ad232c2
f896862
95022d8
e07e2d0
5771d1a
f765915
6de1358
336a7f0
bbda8bf
cf4e711
a75cdb6
cb310f4
1cd18ee
10a12e3
31de683
820a58a
31a313f
839f32c
863139e
9617a46
8b218fe
a8aa5c7
ab24292
bba0eb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as TaxCloudSettingsForm } from "./taxCloudSettingsForm"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React from "react"; | ||
import PropTypes from "prop-types"; | ||
import { Form } from "/imports/plugins/core/ui/client/components"; | ||
import { Components } from "@reactioncommerce/reaction-components"; | ||
import { TaxCloudPackageConfig } from "../../lib/collections/schemas"; | ||
|
||
/** | ||
* @file TaxCloudSettingsForm is a React Component used to change TaxCloud | ||
* settings. | ||
* @module TaxCloudSettingsForm | ||
*/ | ||
|
||
/** | ||
* @method TaxCloudSettingsForm | ||
* @summary renders a form for updating TaxCloud settings. | ||
* @param {Object} props - some data for use by this component. | ||
* @property {Function} handleSubmit - a function for saving new TaxCloud settings. | ||
* @property {Array} hiddenFields - the fields (of the TaxCloud Package) to hide from the form. | ||
* @property {Object} settings - the value of the "settings" field in the TaxCloud Package. | ||
* @property {Object} shownFields - info about the fields the form is to show. | ||
* @since 1.5.2 | ||
* @return {Node} - a React node containing the TaxCloud settings form. | ||
*/ | ||
const TaxCloudSettingsForm = (props) => { | ||
const { handleSubmit, hiddenFields, settings, shownFields } = props; | ||
return ( | ||
<div className="rui taxcloud-settings-form"> | ||
{!settings.taxcloud.apiLoginId && | ||
<div className="alert alert-info"> | ||
<Components.Translation defaultValue="Add API Login ID to enable" i18nkey="admin.taxSettings.taxcloudCredentials" /> | ||
<a href="https://www.taxcloud.com/" target="_blank">TaxCloud</a> | ||
</div> | ||
} | ||
<Form | ||
schema={TaxCloudPackageConfig} | ||
doc={{ settings }} | ||
docPath={"settings.taxcloud"} | ||
name={"settings.taxcloud"} | ||
fields={shownFields} | ||
hideFields={hiddenFields} | ||
onSubmit={handleSubmit} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
/** | ||
* @name TaxCloudSettingsForm propTypes | ||
* @type {propTypes} | ||
* @param {Object} props - React PropTypes | ||
* @property {Function} handleSubmit - a function that saves new TaxCloud settings. | ||
* @property {Array} hiddenFields - an array of the TaxCloud Package's fields | ||
* to hide from the settings form. | ||
* @property {Object} settings - the value of the "settings" field in the TaxCloud Package. | ||
* @property {Object} shownFields - info about the fields of the TaxCloud Package | ||
* that the settings form will allow users to change. | ||
* @return {Array} React propTypes | ||
*/ | ||
TaxCloudSettingsForm.propTypes = { | ||
handleSubmit: PropTypes.func, | ||
hiddenFields: PropTypes.arrayOf(PropTypes.string), | ||
settings: PropTypes.object, | ||
shownFields: PropTypes.object | ||
}; | ||
|
||
export default TaxCloudSettingsForm; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as TaxCloudSettingsFormContainer } from "./taxCloudSettingsFormContainer"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { compose, withProps } from "recompose"; | ||
import { composeWithTracker, registerComponent } from "@reactioncommerce/reaction-components"; | ||
import { Meteor } from "meteor/meteor"; | ||
import { Reaction, i18next } from "/client/api"; | ||
import { TaxCloudPackageConfig } from "../../lib/collections/schemas"; | ||
import { TaxCloudSettingsForm } from "../components"; | ||
|
||
/** | ||
* @file This is a container for TaxCloudSettingsForm. | ||
* @module taxCloudSettingsFormContainer | ||
*/ | ||
|
||
const handlers = { | ||
/** | ||
* handleSubmit | ||
* @method | ||
* @summary event handler for when new TaxCloud settings are submitted. | ||
* @param {Object} event - event info. | ||
* @param {Object} changedInfo - info about the new TaxCloud settings. | ||
* @param {String} targetField - where to save the new settings in the TaxCloud Package. | ||
* @since 1.5.2 | ||
* @return {null} - returns nothing | ||
*/ | ||
handleSubmit(event, changedInfo, targetField) { | ||
if (!changedInfo.isValid) { | ||
return; | ||
} | ||
Meteor.call("package/update", "taxes-taxcloud", targetField, changedInfo.doc.settings.taxcloud, (error) => { | ||
if (error) { | ||
Alerts.toast( | ||
i18next.t("admin.update.updateFailed", { defaultValue: "Failed to update TaxCloud settings." }), | ||
"error" | ||
); | ||
return; | ||
} | ||
Alerts.toast( | ||
i18next.t("admin.update.updateSucceeded", { defaultValue: "TaxCloud settings updated." }), | ||
"success" | ||
); | ||
}); | ||
} | ||
}; | ||
|
||
const composer = (props, onData) => { | ||
const shownFields = { | ||
["settings.taxcloud.apiKey"]: TaxCloudPackageConfig._schema["settings.taxcloud.apiKey"], | ||
["settings.taxcloud.apiLoginId"]: TaxCloudPackageConfig._schema["settings.taxcloud.apiLoginId"] | ||
}; | ||
const hiddenFields = [ | ||
"settings.taxcloud.enabled", | ||
"settings.taxcloud.refreshPeriod", | ||
"settings.taxcloud.taxCodeUrl" | ||
]; | ||
|
||
const shopId = Reaction.getShopId(); | ||
const packageSub = Meteor.subscribe("Packages", shopId); | ||
if (packageSub.ready()) { | ||
const packageData = Reaction.getPackageSettings("taxes-taxcloud"); | ||
onData(null, { settings: packageData.settings, shownFields, hiddenFields }); | ||
} | ||
}; | ||
|
||
registerComponent("TaxCloudSettingsForm", TaxCloudSettingsForm, [ | ||
withProps(handlers), composeWithTracker(composer) | ||
]); | ||
|
||
export default compose(withProps(handlers), composeWithTracker(composer))(TaxCloudSettingsForm); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,5 @@ | ||
<template name="taxCloudSettings"> | ||
|
||
{{#unless packageData.settings.taxcloud.apiLoginId}} | ||
<div class="alert alert-info"> | ||
<span data-i18n="admin.taxSettings.taxcloudCredentials">Add API Login ID to enable</span> | ||
<a href="https://www.taxcloud.com/" target="_blank">TaxCloud</a> | ||
</div> | ||
{{/unless}} | ||
|
||
<div> | ||
{{#autoForm collection=Collections.Packages schema=packageConfigSchema doc=packageData type="update" id="taxcloud-update-form"}} | ||
<div class="panel-body"> | ||
{{> afQuickField name='settings.taxcloud.apiLoginId' class='form-control'}} | ||
{{> afQuickField name='settings.taxcloud.apiKey' class='form-control'}} | ||
<!-- {{> afQuickField name='settings.taxcloud.refreshPeriod' class='form-control'}} | ||
{{> afQuickField name='settings.taxcloud.taxCodeUrl' class='form-control'}} --> | ||
</div> | ||
{{> shopSettingsSubmitButton}} | ||
{{/autoForm}} | ||
{{> React taxCloudForm}} | ||
</div> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,15 @@ | ||
import { Meteor } from "meteor/meteor"; | ||
import { Template } from "meteor/templating"; | ||
import { AutoForm } from "meteor/aldeed:autoform"; | ||
import { Packages } from "/lib/collections"; | ||
import { TaxCodes } from "/imports/plugins/core/taxes/lib/collections"; | ||
import { Reaction, i18next } from "/client/api"; | ||
import { TaxCloudPackageConfig } from "../../lib/collections/schemas"; | ||
import { TaxCloudSettingsFormContainer } from "../containers"; | ||
|
||
Template.taxCloudSettings.helpers({ | ||
packageConfigSchema() { | ||
return TaxCloudPackageConfig; | ||
}, | ||
packageData() { | ||
return Packages.findOne({ | ||
name: "taxes-taxcloud", | ||
shopId: Reaction.getShopId() | ||
}); | ||
} | ||
}); | ||
|
||
|
||
AutoForm.hooks({ | ||
"taxcloud-update-form": { | ||
onSuccess: function () { | ||
if (!TaxCodes.findOne({ taxCodeProvider: "taxes-taxcloud" })) { | ||
Meteor.call("taxcloud/getTaxCodes", (err, res) => { | ||
if (res && Array.isArray(res)) { | ||
Alerts.toast(i18next.t("admin.taxSettings.shopTaxMethodsSaved"), | ||
"success"); | ||
res.forEach((code) => { | ||
Meteor.call("taxes/insertTaxCodes", Reaction.getShopId(), code, | ||
"taxes-taxcloud"); | ||
}); | ||
} | ||
}); | ||
} else { | ||
Alerts.toast(i18next.t("admin.taxSettings.shopTaxMethodsAlreadySaved"), | ||
"success"); | ||
} | ||
}, | ||
onError: function (operation, error) { | ||
return Alerts.toast( | ||
`${i18next.t("admin.taxSettings.shopTaxMethodsFailed")} ${error}`, | ||
"error"); | ||
} | ||
/** | ||
* @method taxCloudForm | ||
* @summary returns a component for updating the TaxCloud settings for | ||
* this app. | ||
* @since 1.5.2 | ||
* @return {Object} - an object containing the component to render. | ||
*/ | ||
taxCloudForm() { | ||
return { component: TaxCloudSettingsFormContainer }; | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { Meteor } from "meteor/meteor"; | ||
import { Match } from "meteor/check"; | ||
import { Factory } from "meteor/dburles:factory"; | ||
import { expect } from "meteor/practicalmeteor:chai"; | ||
import { sinon } from "meteor/practicalmeteor:sinon"; | ||
import { Packages } from "/lib/collections"; | ||
import { Reaction } from "/server/api"; | ||
|
||
describe("Update Package", function () { | ||
let sandbox; | ||
|
||
beforeEach(function () { | ||
sandbox = sinon.sandbox.create(); | ||
}); | ||
|
||
afterEach(function () { | ||
sandbox.restore(); | ||
}); | ||
|
||
describe("package/update", function () { | ||
it("should throw an 'Access Denied' error for non-admins", function (done) { | ||
const pkgUpdateSpy = sandbox.spy(Packages, "update"); | ||
const examplePackage = Factory.create("examplePackage"); | ||
|
||
function updatePackage() { | ||
return Meteor.call("package/update", examplePackage.name, "settings", {}); | ||
} | ||
expect(updatePackage).to.throw(Meteor.Error, /Access Denied/); | ||
expect(pkgUpdateSpy).to.not.have.been.called; | ||
|
||
return done(); | ||
}); | ||
|
||
it("should throw an error when supplied with an argument of the wrong type", function (done) { | ||
const pkgUpdateSpy = sandbox.spy(Packages, "update"); | ||
sandbox.stub(Reaction, "getShopId", () => "randomId"); | ||
sandbox.stub(Reaction, "hasPermission", () => true); | ||
|
||
function updatePackage(packageName, field, value) { | ||
return Meteor.call("package/update", packageName, field, value); | ||
} | ||
expect(() => updatePackage([], "someField", { foo: "bar" })).to.throw(Match.Error, /Match error: Expected string, got object/); | ||
expect(() => updatePackage("somePackage", [], { foo: "bar" })).to.throw(Match.Error, /Match error: Expected string, got object/); | ||
expect(() => updatePackage("somePackage", "someField", "")).to.throw(Match.Error, /Match error: Expected object, got string/); | ||
expect(pkgUpdateSpy).to.not.have.been.called; | ||
|
||
return done(); | ||
}); | ||
|
||
it("should be able to update any Package", function (done) { | ||
const packageUpdateSpy = sandbox.spy(Packages, "update"); | ||
const oldPackage = Factory.create("examplePackage"); | ||
|
||
sandbox.stub(Reaction, "getShopId", () => oldPackage.shopId); | ||
sandbox.stub(Reaction, "hasPermission", () => true); | ||
const packageName = oldPackage.name; | ||
const newValues = { | ||
enabled: true, | ||
apiUrl: "http://foo-bar.com/api/v1" | ||
}; | ||
Meteor.call("package/update", packageName, "settings", newValues); | ||
expect(packageUpdateSpy).to.have.been.called; | ||
const updatedPackage = Packages.findOne({ name: packageName }); | ||
expect(oldPackage.settings.enabled).to.not.equal(updatedPackage.settings.enabled); | ||
expect(oldPackage.settings.apiUrl).to.not.equal(updatedPackage.settings.apiUrl); | ||
|
||
return done(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,44 @@ | ||||
import { Meteor } from "meteor/meteor"; | ||||
import { check } from "meteor/check"; | ||||
import { Packages } from "/lib/collections"; | ||||
import { Reaction } from "/server/api"; | ||||
|
||||
/** | ||||
* @method updatePackage | ||||
* @summary updates the data stored for a certain Package. | ||||
* @param {String} packageName - the name of the Package to update. | ||||
* @param {String} field - the part of the Package's data that is to | ||||
* be updated. | ||||
* @param {Object} value - the new data that's to be stored for the said | ||||
* Package. | ||||
* @since 1.5.1 | ||||
* @return {Object} - returns an object with info about the update operation. | ||||
*/ | ||||
export function updatePackage(packageName, field, value) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for properly documenting this method :) It looks really similar to this method reaction/server/methods/core/registry.js Line 17 in e560937
In our new docs: http://api.docs.reactioncommerce.com/Methods_Registry.html Could you use that method instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a look at using that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @machikoyasuda I have considered using the method you mentioned, but I don't think it's appropriate to use it here because how it works (e.g Collection.upsert, name.split("/").splice(-1)) is different from how
|
||||
check(packageName, String); | ||||
check(field, String); | ||||
check(value, Object); | ||||
|
||||
const userId = Meteor.userId(); | ||||
const shopId = Reaction.getShopId(); | ||||
if (!Reaction.hasPermission([packageName], userId, shopId)) { | ||||
throw new Meteor.Error("access-denied", `Access Denied. You don't have permissions for the ${packageName} package.`); | ||||
} | ||||
|
||||
const updateResult = Packages.update({ | ||||
name: packageName, | ||||
shopId: shopId | ||||
}, { | ||||
$set: { | ||||
[field]: value | ||||
} | ||||
}); | ||||
if (updateResult !== 1) { | ||||
throw new Meteor.Error("server-error", `An error occurred while updating the package ${packageName}.`); | ||||
} | ||||
return updateResult; | ||||
} | ||||
|
||||
Meteor.methods({ | ||||
"package/update": updatePackage | ||||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Server methods require a test