@@ -109,8 +135,12 @@ class CardHeader extends Component {
return (
- {this.renderTitle()}
- {this.props.children}
+
+ {this.renderTitle()}
+
+
+ {this.props.children}
+
);
}
diff --git a/imports/plugins/core/ui/client/components/cards/index.js b/imports/plugins/core/ui/client/components/cards/index.js
index 2f7708ed970..4e0220909be 100644
--- a/imports/plugins/core/ui/client/components/cards/index.js
+++ b/imports/plugins/core/ui/client/components/cards/index.js
@@ -3,3 +3,4 @@ export { default as CardHeader } from "./cardHeader";
export { default as CardTitle } from "./cardTitle";
export { default as CardBody } from "./cardBody";
export { default as CardGroup } from "./cardGroup";
+export { default as SettingsCard } from "./settingsCard";
diff --git a/imports/plugins/core/ui/client/components/cards/settingsCard.js b/imports/plugins/core/ui/client/components/cards/settingsCard.js
new file mode 100644
index 00000000000..2e41371f5f6
--- /dev/null
+++ b/imports/plugins/core/ui/client/components/cards/settingsCard.js
@@ -0,0 +1,67 @@
+/**
+ * Settings Card is a composite component to standardize the
+ * creation settings cards (panels) in the dashboard.
+ */
+
+import React, { Component, PropTypes } from "react";
+import Blaze from "meteor/gadicc:blaze-react-component";
+import { Card, CardHeader, CardBody } from "/imports/plugins/core/ui/client/components";
+
+class SettingsCard extends Component {
+ static propTypes = {
+ children: PropTypes.node,
+ enabled: PropTypes.bool,
+ expanded: PropTypes.bool,
+ i18nKeyTitle: PropTypes.string,
+ icon: PropTypes.string,
+ name: PropTypes.string,
+ onExpand: PropTypes.func,
+ onSwitchChange: PropTypes.func,
+ template: PropTypes.any,
+ title: PropTypes.string
+ }
+
+ handleSwitchChange = (event, isChecked) => {
+ if (typeof this.props.onSwitchChange === "function") {
+ this.props.onSwitchChange(event, isChecked, this.props.name, this);
+ }
+ }
+
+ renderCardBody() {
+ if (this.props.template) {
+ return (
+
+ );
+ }
+
+ return this.props.children;
+ }
+
+ render() {
+ return (
+
+
+
+ {this.renderCardBody()}
+
+
+ );
+ }
+}
+
+export default SettingsCard;
diff --git a/imports/plugins/core/ui/client/components/forms/form.js b/imports/plugins/core/ui/client/components/forms/form.js
new file mode 100644
index 00000000000..9e2b4ab00dd
--- /dev/null
+++ b/imports/plugins/core/ui/client/components/forms/form.js
@@ -0,0 +1,255 @@
+import React, { Component, PropTypes } from "react";
+import { map, update, set, at, isEqual } from "lodash";
+import classnames from "classnames";
+import { toCamelCase } from "/lib/api";
+import { Switch, Button, TextField, FormActions } from "../";
+
+class Form extends Component {
+ static propTypes = {
+ doc: PropTypes.object,
+ docPath: PropTypes.string,
+ hideFields: PropTypes.arrayOf(PropTypes.string),
+ name: PropTypes.string,
+ onSubmit: PropTypes.func,
+ schema: PropTypes.object
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ doc: props.doc,
+ schema: this.validationSchema(),
+ isValid: undefined
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (isEqual(nextProps.doc, this.props.doc) === false) {
+ this.setState({
+ doc: nextProps.doc,
+ schema: this.validationSchema()
+ });
+ }
+ }
+
+
+ validationSchema() {
+ const { docPath } = this.props;
+
+ if (docPath) {
+ const objectKeys = this.objectKeys[docPath + "."];
+ if (Array.isArray(objectKeys)) {
+ // Use the objectKeys from parent fieldset to generate
+ // actual form fields
+ const fieldNames = objectKeys.map((fieldName) => {
+ return `${docPath}.${fieldName}`;
+ });
+
+ return this.props.schema.pick(fieldNames).newContext();
+ }
+ }
+
+ return this.props.schema.namedContext();
+ }
+
+ get objectKeys() {
+ return this.props.schema._objectKeys;
+ }
+
+ get schema() {
+ return this.props.schema._schema;
+ }
+
+ valueForField(fieldName) {
+ const picked = at(this.state.doc, fieldName);
+
+ if (Array.isArray(picked) && picked.length) {
+ return picked[0];
+ }
+
+ return undefined;
+ }
+
+ validate() {
+ const { docPath } = this.props;
+
+ // Create a smaller document in order to validate without extra fields
+ const docToValidate = set(
+ {},
+ docPath,
+ at(this.state.doc, this.props.docPath)[0]
+ );
+
+ // Clean any fields not in schame to avoid needless validation errors
+ const cleanedObject = this.state.schema._simpleSchema.clean(docToValidate);
+
+ // Finally validate the document
+ this.setState({
+ isValid: this.state.schema.validate(cleanedObject)
+ });
+ }
+
+ isFieldHidden(fieldName) {
+ if (Array.isArray(this.props.hideFields) && this.props.hideFields.indexOf(fieldName) >= 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ handleChange = (event, value, name) => {
+ const newdoc = update(this.state.doc, name, () => {
+ return value;
+ });
+
+ this.setState({
+ doc: newdoc
+ }, () => {
+ this.validate();
+ });
+ }
+
+ handleSubmit = (event) => {
+ event.preventDefault();
+
+ this.validate();
+
+ if (this.props.onSubmit) {
+ this.props.onSubmit(event, {
+ doc: this.state.doc,
+ isValid: this.state.isValid
+ }, this.props.name);
+ }
+ }
+
+ renderFormField(field) {
+ const sharedProps = {
+ i18nKeyLabel: `settings.${toCamelCase(field.name)}`,
+ key: field.name,
+ label: field.label,
+ name: field.name
+ };
+
+ let fieldElement;
+ let helpText;
+
+ switch (field.type) {
+ case "boolean":
+ fieldElement = (
+
+ );
+ break;
+ case "string":
+ fieldElement = (
+
+ );
+ break;
+ default:
+ return null;
+ }
+
+ let fieldHasError = false;
+
+ if (this.state.isValid === false) {
+ this.state.schema._invalidKeys
+ .filter((v) => v.name === field.name)
+ .map((validationError) => {
+ const message = this.state.schema.keyErrorMessage(validationError.name);
+ fieldHasError = true;
+
+ helpText = (
+
+ {message}
+
+ );
+ });
+ }
+
+ const formGroupClassName = classnames({
+ "rui": true,
+ "form-group": true,
+ "has-error": fieldHasError
+ });
+
+ return (
+
+ {fieldElement}
+ {helpText}
+
+ );
+ }
+
+ renderField(field) {
+ const { fieldName } = field;
+
+ if (this.isFieldHidden(fieldName) === false) {
+ const fieldSchema = this.schema[fieldName];
+ const fieldProps = {
+ ...fieldSchema,
+ name: fieldName,
+ type: typeof fieldSchema.type()
+ };
+
+ return this.renderFormField(fieldProps);
+ }
+
+ return null;
+ }
+
+ renderWithSchema() {
+ const { docPath } = this.props;
+
+ if (this.props.schema) {
+ if (docPath) {
+ return map(this.schema, (field, key) => { // eslint-disable-line consistent-return
+ if (key.endsWith(docPath)) {
+ const objectKeys = this.objectKeys[docPath + "."];
+ if (Array.isArray(objectKeys)) {
+ // Use the objectKeys from parent fieldset to generate
+ // actual form fields
+ return objectKeys.map((fieldName) => {
+ const fullFieldName = docPath ? `${docPath}.${fieldName}` : fieldName;
+ return this.renderField({ fieldName: fullFieldName });
+ });
+ }
+
+ return this.renderField({ fieldName: key });
+ }
+ });
+ }
+
+ return map(this.schema, (field, key) => { // eslint-disable-line consistent-return
+ return this.renderField({ fieldName: key });
+ });
+ }
+
+ return null;
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Form;
diff --git a/imports/plugins/core/ui/client/components/forms/formActions.js b/imports/plugins/core/ui/client/components/forms/formActions.js
new file mode 100644
index 00000000000..560bb08f1c4
--- /dev/null
+++ b/imports/plugins/core/ui/client/components/forms/formActions.js
@@ -0,0 +1,22 @@
+import React, { Component, PropTypes } from "react";
+
+class FormActions extends Component {
+ static propTypes = {
+ children: PropTypes.node
+ }
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+export default FormActions;
diff --git a/imports/plugins/core/ui/client/components/forms/index.js b/imports/plugins/core/ui/client/components/forms/index.js
new file mode 100644
index 00000000000..1fa2120c500
--- /dev/null
+++ b/imports/plugins/core/ui/client/components/forms/index.js
@@ -0,0 +1,2 @@
+export { default as Form } from "./form";
+export { default as FormActions } from "./formActions";
diff --git a/imports/plugins/core/ui/client/components/index.js b/imports/plugins/core/ui/client/components/index.js
index 2afee9cd476..15676cb5d19 100644
--- a/imports/plugins/core/ui/client/components/index.js
+++ b/imports/plugins/core/ui/client/components/index.js
@@ -14,13 +14,14 @@ export { Translation, Currency } from "./translation";
export { default as Tooltip } from "./tooltip/tooltip";
export { Metadata, Metafield } from "./metadata";
export { TagList, TagItem } from "./tags";
-export { Card, CardHeader, CardBody, CardGroup, CardTitle } from "./cards";
+export { Card, CardHeader, CardBody, CardGroup, CardTitle, SettingsCard } from "./cards";
export { MediaGallery, MediaItem } from "./media";
export { default as FlatButton } from "./button/flatButton";
export { default as SortableTable } from "./table/table";
export { Checkbox } from "./checkbox";
export { default as Loading } from "./loading/loading";
export { default as FieldGroup } from "./forms/fieldGroup";
+export * from "./forms";
export * from "./toolbar";
export { default as Popover } from "./popover/popover";
export * from "./menu";
diff --git a/imports/plugins/included/default-theme/client/styles/cards.less b/imports/plugins/included/default-theme/client/styles/cards.less
index 574d0bfccd8..2106ec219c4 100644
--- a/imports/plugins/included/default-theme/client/styles/cards.less
+++ b/imports/plugins/included/default-theme/client/styles/cards.less
@@ -137,6 +137,10 @@
.padding-left(20px);
}
+.rui.card-header .content-view .image {
+ margin-right: 1rem;
+}
+
.rui.card-header .action-view {
display: flex;
flex: 0 0 auto;
diff --git a/imports/plugins/included/payments-example/client/index.js b/imports/plugins/included/payments-example/client/index.js
index 0b32b322c05..a2e0dea67b9 100644
--- a/imports/plugins/included/payments-example/client/index.js
+++ b/imports/plugins/included/payments-example/client/index.js
@@ -1,2 +1,2 @@
import "./checkout/example";
-import "./settings/example";
+import "./settings/templates/example";
diff --git a/imports/plugins/included/payments-example/client/settings/components/exampleSettingsForm.js b/imports/plugins/included/payments-example/client/settings/components/exampleSettingsForm.js
new file mode 100644
index 00000000000..b75015f3441
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/components/exampleSettingsForm.js
@@ -0,0 +1,41 @@
+import React, { Component, PropTypes } from "react";
+import { FieldGroup, Translation } from "/imports/plugins/core/ui/client/components";
+
+class ExampleSettingsForm extends Component {
+ render() {
+ const { packageData } = this.props;
+
+ return (
+
+ { !packageData.settings.apiKey &&
+
+
+
+ }
+
+
+
+
+ );
+ }
+}
+
+ExampleSettingsForm.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ packageData: PropTypes.object
+};
+
+export default ExampleSettingsForm;
+
diff --git a/imports/plugins/included/payments-example/client/settings/components/index.js b/imports/plugins/included/payments-example/client/settings/components/index.js
new file mode 100644
index 00000000000..b49222b0c7f
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/components/index.js
@@ -0,0 +1 @@
+export { default as ExampleSettingsForm } from "./exampleSettingsForm.js";
diff --git a/imports/plugins/included/payments-example/client/settings/containers/exampleSettingsFormContainer.js b/imports/plugins/included/payments-example/client/settings/containers/exampleSettingsFormContainer.js
new file mode 100644
index 00000000000..5da59806e94
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/containers/exampleSettingsFormContainer.js
@@ -0,0 +1,80 @@
+import React, { Component, PropTypes } from "react";
+import { Meteor } from "meteor/meteor";
+import { composeWithTracker } from "/lib/api/compose";
+import { Packages } from "/lib/collections";
+import { Loading } from "/imports/plugins/core/ui/client/components";
+import { TranslationProvider } from "/imports/plugins/core/ui/client/providers";
+import { Reaction, i18next } from "/client/api";
+import { ExampleSettingsForm } from "../components";
+
+class ExampleSettingsFormContainer extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ apiKey: ""
+ };
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.saveUpdate = this.saveUpdate.bind(this);
+ }
+
+ handleChange(e) {
+ e.preventDefault();
+ this.setState({ apiKey: e.target.value });
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+
+ const packageId = this.props.packageData._id;
+ const settingsKey = this.props.packageData.registry[0].settingsKey;
+ const apiKey = this.state.apiKey;
+
+ const fields = [{
+ property: "apiKey",
+ value: apiKey
+ }];
+
+ this.saveUpdate(fields, packageId, settingsKey);
+ }
+
+ saveUpdate(fields, id, settingsKey) {
+ Meteor.call("registry/update", id, settingsKey, fields, (err) => {
+ if (err) {
+ return Alerts.toast(i18next.t("admin.settings.saveFailed"), "error");
+ }
+ return Alerts.toast(i18next.t("admin.settings.saveSuccess"), "success");
+ });
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+ExampleSettingsFormContainer.propTypes = {
+ packageData: PropTypes.object
+};
+
+const composer = ({}, onData) => {
+ const subscription = Meteor.subscribe("Packages");
+ if (subscription.ready()) {
+ const packageData = Packages.findOne({
+ name: "example-paymentmethod",
+ shopId: Reaction.getShopId()
+ });
+ onData(null, { packageData });
+ }
+};
+
+export default composeWithTracker(composer, Loading)(ExampleSettingsFormContainer);
diff --git a/imports/plugins/included/payments-example/client/settings/containers/index.js b/imports/plugins/included/payments-example/client/settings/containers/index.js
new file mode 100644
index 00000000000..a04ab5d1421
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/containers/index.js
@@ -0,0 +1 @@
+export { default as ExampleSettingsFormContainer } from "./exampleSettingsFormContainer";
diff --git a/imports/plugins/included/payments-example/client/settings/example.html b/imports/plugins/included/payments-example/client/settings/example.html
deleted file mode 100644
index 7b123e41d47..00000000000
--- a/imports/plugins/included/payments-example/client/settings/example.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
- {{#unless packageData.settings.apiKey}}
-
- Example Credentials
-
- {{/unless}}
-
-
- {{#autoForm collection=Collections.Packages schema=ExamplePackageConfig doc=packageData type="update" id="example-update-form"}}
- {{>afQuickField name='settings.apiKey'}}
-
- {{/autoForm}}
-
-
-
-
-
-
-
-
-
-
-
Example Payment Method
-
-
-
- {{#if packageData.settings.apiKey}}
- API Client ID:
- {{else}}
- API Client ID:
- {{/if}}
-
-
-
-
-
-
-
diff --git a/imports/plugins/included/payments-example/client/settings/example.js b/imports/plugins/included/payments-example/client/settings/example.js
deleted file mode 100644
index ce9cdd7ba4f..00000000000
--- a/imports/plugins/included/payments-example/client/settings/example.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Template } from "meteor/templating";
-import { Reaction, i18next } from "/client/api";
-import { Packages } from "/lib/collections";
-import { ExamplePackageConfig } from "../../lib/collections/schemas";
-
-import "./example.html";
-
-
-Template.exampleSettings.helpers({
- ExamplePackageConfig() {
- return ExamplePackageConfig;
- },
- packageData() {
- return Packages.findOne({
- name: "example-paymentmethod",
- shopId: Reaction.getShopId()
- });
- }
-});
-
-
-Template.example.helpers({
- packageData: function () {
- return Packages.findOne({
- name: "example-paymentmethod",
- shopId: Reaction.getShopId()
- });
- }
-});
-
-Template.example.events({
- "click [data-event-action=showExampleSettings]": function () {
- Reaction.showActionView();
- }
-});
-
-AutoForm.hooks({
- "example-update-form": {
- onSuccess: function () {
- return Alerts.toast(i18next.t("admin.settings.saveSuccess"), "success");
- },
- onError: function () {
- return Alerts.toast(`${i18next.t("admin.settings.saveFailed")} ${error}`, "error");
- }
- }
-});
diff --git a/imports/plugins/included/payments-example/client/settings/templates/example.html b/imports/plugins/included/payments-example/client/settings/templates/example.html
new file mode 100644
index 00000000000..f70d1753254
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/templates/example.html
@@ -0,0 +1,5 @@
+
+
+ {{> React ExampleSettings}}
+
+
diff --git a/imports/plugins/included/payments-example/client/settings/templates/example.js b/imports/plugins/included/payments-example/client/settings/templates/example.js
new file mode 100644
index 00000000000..f0793d9c9f1
--- /dev/null
+++ b/imports/plugins/included/payments-example/client/settings/templates/example.js
@@ -0,0 +1,11 @@
+import { ExampleSettingsFormContainer } from "../containers";
+import { Template } from "meteor/templating";
+import "./example.html";
+
+Template.exampleSettings.helpers({
+ ExampleSettings() {
+ return {
+ component: ExampleSettingsFormContainer
+ };
+ }
+});
diff --git a/imports/plugins/included/social/client/components/index.js b/imports/plugins/included/social/client/components/index.js
index 228114006ab..6eef7e64ad6 100644
--- a/imports/plugins/included/social/client/components/index.js
+++ b/imports/plugins/included/social/client/components/index.js
@@ -3,3 +3,4 @@ export { default as Facebook } from "./facebook";
export { default as Twitter } from "./twitter";
export { default as GooglePlus } from "./googleplus";
export { default as Pinterest } from "./pinterest";
+export { default as SocialSettings } from "./settings";
diff --git a/imports/plugins/included/social/client/components/settings.js b/imports/plugins/included/social/client/components/settings.js
new file mode 100644
index 00000000000..37c106212fe
--- /dev/null
+++ b/imports/plugins/included/social/client/components/settings.js
@@ -0,0 +1,110 @@
+import React, { Component, PropTypes } from "react";
+import {
+ CardGroup,
+ SettingsCard,
+ Form
+} from "/imports/plugins/core/ui/client/components";
+import { SocialPackageConfig } from "/lib/collections/schemas/social";
+
+const socialProviders = [
+ {
+ name: "facebook",
+ icon: "fa fa-facebook",
+ fields: ["appId", "appSecret", "profilePage"]
+ },
+ {
+ name: "twitter",
+ icon: "fa fa-twitter",
+ fields: ["username", "profilePage"]
+ },
+ {
+ name: "pinterest",
+ icon: "fa fa-pinterest",
+ fields: ["profilePage"]
+ },
+ {
+ name: "googleplus",
+ icon: "fa fa-google-plus",
+ fields: ["profilePage"]
+ }
+];
+
+class SocialSettings extends Component {
+ static propTypes = {
+ onSettingChange: PropTypes.func,
+ onSettingEnableChange: PropTypes.func,
+ onSettingExpand: PropTypes.func,
+ onSettingsSave: PropTypes.func,
+ packageData: PropTypes.object,
+ preferences: PropTypes.object,
+ providers: PropTypes.arrayOf(PropTypes.string),
+ socialSettings: PropTypes.object
+ }
+
+ getSchemaForField(provider, field) {
+ return SocialPackageConfig._schema[`settings.public.apps.${provider}.${field}`];
+ }
+
+ handleSettingChange = (event, value, name) => {
+ if (typeof this.props.onSettingChange === "function") {
+ const parts = name.split(".");
+ this.props.onSettingChange(parts[0], parts[1], value);
+ }
+ }
+
+ handleSubmit = (event, data, formName) => {
+ if (typeof this.props.onSettingsSave === "function") {
+ this.props.onSettingsSave(formName, data.doc);
+ }
+ }
+
+ renderCards() {
+ if (Array.isArray(socialProviders)) {
+ return socialProviders.map((provider, index) => {
+ const doc = {
+ settings: {
+ ...this.props.packageData.settings
+ }
+ };
+
+ return (
+
+
+
+ );
+ });
+ }
+
+ return null;
+ }
+
+ render() {
+ return (
+
+ {this.renderCards()}
+
+ );
+ }
+}
+
+export default SocialSettings;
diff --git a/imports/plugins/included/social/client/containers/socialContainer.js b/imports/plugins/included/social/client/containers/socialContainer.js
index 17e0856449e..5cbd33f4917 100644
--- a/imports/plugins/included/social/client/containers/socialContainer.js
+++ b/imports/plugins/included/social/client/containers/socialContainer.js
@@ -2,8 +2,7 @@ import React, { Component } from "react";
import { composeWithTracker } from "/lib/api/compose";
import { Reaction } from "/client/api";
import { SocialButtons } from "../components";
-import { createSocialSettings } from "../lib/helpers";
-
+import { createSocialSettings } from "../../lib/helpers";
class SocialContainer extends Component {
render() {
diff --git a/imports/plugins/included/social/client/containers/socialSettingsContainer.js b/imports/plugins/included/social/client/containers/socialSettingsContainer.js
new file mode 100644
index 00000000000..de2439ac5cb
--- /dev/null
+++ b/imports/plugins/included/social/client/containers/socialSettingsContainer.js
@@ -0,0 +1,86 @@
+import React, { Component, PropTypes } from "react";
+import { isEqual } from "lodash";
+import { Meteor } from "meteor/meteor";
+import { composeWithTracker } from "/lib/api/compose";
+import { Reaction, i18next } from "/client/api";
+import { Packages } from "/lib/collections";
+import { SocialSettings } from "../components";
+import { createSocialSettings } from "../../lib/helpers";
+
+class SocialSettingsContainer extends Component {
+ static propTypes = {
+ settings: PropTypes.object
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ settings: props.settings
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (isEqual(nextProps.settings, this.props.settings) === false) {
+ this.setState({
+ settings: nextProps.settings
+ });
+ }
+ }
+
+ handleSettingEnable = (event, isChecked, name) => {
+ Meteor.call("reaction-social/updateSocialSetting", name, "enabled", isChecked);
+ }
+
+ handleSettingExpand = (event, card, name, isExpanded) => {
+ Reaction.updateUserPreferences("reaction-social", "settingsCards", {
+ [name]: isExpanded
+ });
+ }
+
+ handleSettingsSave = (settingName, values) => {
+ Meteor.call("reaction-social/updateSocialSettings", values.settings, (error) => {
+ if (!error) {
+ Alerts.toast(
+ i18next.t("admin.settings.socialSettingsSaved", { defaultValue: "Social settings saved" }),
+ "success"
+ );
+ }
+ });
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+function composer(props, onData) {
+ const subscription = Reaction.Subscriptions.Packages;
+ const preferences = Reaction.getUserPreferences("reaction-social", "settingsCards", {});
+
+ const socialPackage = Packages.findOne({
+ name: "reaction-social"
+ });
+
+ if (subscription.ready()) {
+ onData(null, {
+ preferences: preferences,
+ packageData: socialPackage,
+ socialSettings: createSocialSettings(props)
+ });
+ } else {
+ onData(null, {});
+ }
+}
+
+const decoratedComponent = composeWithTracker(composer)(SocialSettingsContainer);
+
+export default decoratedComponent;
diff --git a/imports/plugins/included/social/client/templates/dashboard/social.html b/imports/plugins/included/social/client/templates/dashboard/social.html
index 82e393300e2..3516fde3fea 100644
--- a/imports/plugins/included/social/client/templates/dashboard/social.html
+++ b/imports/plugins/included/social/client/templates/dashboard/social.html
@@ -1,73 +1,7 @@
- {{#autoForm collection=Collections.Packages schema=Schemas.SocialPackageConfig doc=packageData type="update" id="social-update-form" autosave=true}}
-
-
-
-
- Facebook
-
-
- {{>afQuickField name='settings.public.apps.facebook.enabled' input-col-class="checkbox-switch"}}
-
-
-
- {{>afQuickField name='settings.public.apps.facebook.appId'}}
- {{>afQuickField name='settings.public.apps.facebook.appSecret'}}
- {{>afQuickField name='settings.public.apps.facebook.profilePage'}}
-
-
-
-
-
-
-
-
-
- Twitter
-
-
- {{> afQuickField name="settings.public.apps.twitter.enabled"}}
-
-
-
-
- {{>afQuickField name='settings.public.apps.twitter.username'}}
- {{>afQuickField name='settings.public.apps.twitter.profilePage'}}
-
-
-
-
-
-
-
- Pinterest
-
-
- {{>afQuickField name='settings.public.apps.pinterest.enabled'}}
-
-
-
- {{>afQuickField name='settings.public.apps.pinterest.profilePage'}}
-
-
-
-
-
-
-
- Google+
-
-
- {{>afQuickField name='settings.public.apps.googleplus.enabled'}}
-
-
-
- {{>afQuickField name='settings.public.apps.googleplus.profilePage'}}
-
-
-
-
- {{/autoForm}}
+
+ {{> React SocialSettingsComponent}}
+
diff --git a/imports/plugins/included/social/client/templates/dashboard/social.js b/imports/plugins/included/social/client/templates/dashboard/social.js
index 0e82c0e1083..223e67ce081 100644
--- a/imports/plugins/included/social/client/templates/dashboard/social.js
+++ b/imports/plugins/included/social/client/templates/dashboard/social.js
@@ -1,31 +1,9 @@
-import { Packages } from "/lib/collections";
+import SocialSettingsContainer from "../../containers/socialSettingsContainer";
Template.socialSettings.helpers({
- packageData() {
- return Packages.findOne({
- name: "reaction-social"
- });
- },
-
- checkboxAtts() {
+ SocialSettingsComponent() {
return {
- class: "checkbox-switch"
+ component: SocialSettingsContainer
};
}
});
-
-
-AutoForm.hooks({
- "social-update-form": {
- onSuccess() {
- Alerts.removeSeen();
- return Alerts.toast("Social settings saved.", "success", {
- autoHide: true
- });
- },
- onError(operation, error) {
- Alerts.removeSeen();
- return Alerts.toast(`Social settings update failed. ${error}`, "error");
- }
- }
-});
diff --git a/imports/plugins/included/social/server/i18n/en.json b/imports/plugins/included/social/server/i18n/en.json
index 77ea004f870..4861ca622c4 100644
--- a/imports/plugins/included/social/server/i18n/en.json
+++ b/imports/plugins/included/social/server/i18n/en.json
@@ -10,7 +10,16 @@
"socialDescription": "Social Channel configuration"
},
"settings": {
- "socialSettingsLabel": "Social"
+ "facebook": "Facebook",
+ "twitter": "Twitter",
+ "pinterest": "Pinterest",
+ "googleplus": "Google+",
+ "appId": "App Id",
+ "username": "Username",
+ "appSecret": "App Secret",
+ "profilePage": "Profile Page",
+ "socialSettingsLabel": "Social",
+ "socialSettingsSaved": "Social settings saved"
}
}
}
diff --git a/imports/plugins/included/social/server/index.js b/imports/plugins/included/social/server/index.js
index 573a76f704f..6f9ecf50c9a 100644
--- a/imports/plugins/included/social/server/index.js
+++ b/imports/plugins/included/social/server/index.js
@@ -1,2 +1,3 @@
import "./policy";
import "./i18n";
+import "./methods";
diff --git a/imports/plugins/included/social/server/methods.js b/imports/plugins/included/social/server/methods.js
new file mode 100644
index 00000000000..f00ca374692
--- /dev/null
+++ b/imports/plugins/included/social/server/methods.js
@@ -0,0 +1,44 @@
+import { check, Match } from "meteor/check";
+import { Packages } from "/lib/collections";
+import { Reaction } from "/server/api";
+
+export function updateSocialSetting(provider, field, value) {
+ check(provider, String);
+ check(field, String);
+ check(value, Match.OneOf(String, Boolean));
+
+ if (!Reaction.hasPermission(["reaction-social"])) {
+ throw new Meteor.Error(403, "Access Denied");
+ }
+
+ return Packages.update({
+ name: "reaction-social",
+ shopId: Reaction.getShopId()
+ }, {
+ $set: {
+ [`settings.public.apps.${provider}.${field}`]: value
+ }
+ });
+}
+
+export function updateSocialSettings(values) {
+ check(values, Match.OneOf(Object, String, Boolean, Number, null, undefined));
+
+ if (!Reaction.hasPermission(["reaction-social"])) {
+ throw new Meteor.Error(403, "Access Denied");
+ }
+
+ return Packages.update({
+ name: "reaction-social",
+ shopId: Reaction.getShopId()
+ }, {
+ $set: {
+ settings: values
+ }
+ });
+}
+
+Meteor.methods({
+ "reaction-social/updateSocialSetting": updateSocialSetting,
+ "reaction-social/updateSocialSettings": updateSocialSettings
+});
diff --git a/lib/api/router/metadata.js b/lib/api/router/metadata.js
index 30f26b876fb..633202febb1 100644
--- a/lib/api/router/metadata.js
+++ b/lib/api/router/metadata.js
@@ -56,12 +56,16 @@ export const MetaData = {
title = titleCase(params.slug);
// fallback to route name
} else if (context.route && context.route.name) {
- const routeName = context.route.name;
+ const route = context.route;
+ const routeName = route.name;
// default index to Shop Name
if (routeName === "index") {
title = titleCase(shop.name);
- // default routes to route's name
+ // check for meta in package route
+ } else if (route.options.meta && route.options.meta.title) {
+ title = titleCase(route.options.meta.title);
} else {
+ // default routes to route's name
title = titleCase(routeName);
}
}
diff --git a/lib/collections/schemas/registry.js b/lib/collections/schemas/registry.js
index 39c5e7dce3d..ec491d0d997 100644
--- a/lib/collections/schemas/registry.js
+++ b/lib/collections/schemas/registry.js
@@ -93,6 +93,12 @@ export const Registry = new SimpleSchema({
type: [String],
optional: true,
label: "Audience"
+ },
+ meta: {
+ label: "Meta",
+ type: Object,
+ optional: true,
+ blackbox: true
}
});
diff --git a/lib/collections/schemas/social.js b/lib/collections/schemas/social.js
index b7d4a3b4e68..f8a88acd71c 100644
--- a/lib/collections/schemas/social.js
+++ b/lib/collections/schemas/social.js
@@ -13,6 +13,7 @@ export const SocialProvider = new SimpleSchema({
},
enabled: {
type: Boolean,
+ label: "Enabled",
defaultValue: false,
optional: true
}
@@ -51,6 +52,7 @@ export const SocialPackageConfig = new SimpleSchema([
},
"settings.public.apps.twitter.username": {
type: String,
+ label: "Username",
optional: true
},
"settings.public.apps.pinterest": {
diff --git a/package.json b/package.json
index 2c67b1cd30e..852acc055f5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "reaction",
"description": "Reaction is a modern reactive, real-time event driven ecommerce platform.",
- "version": "0.19.1",
+ "version": "0.20.0",
"main": "main.js",
"directories": {
"test": "tests"
@@ -43,7 +43,7 @@
"handlebars": "^4.0.6",
"i18next": "^7.0.1",
"i18next-browser-languagedetector": "^1.0.1",
- "i18next-localstorage-cache": "^0.3.0",
+ "i18next-localstorage-cache": "^1.1.0",
"i18next-sprintf-postprocessor": "^0.2.2",
"immutable": "^3.8.1",
"jquery": "^3.1.1",