How to build your plugin

Settings

View

APP_MODEL

powrs' users (aka wobblies)

powrs' users' users (aka wobblies' wobblies)

APP_MODEL.attributes

APP_MODEL.attributes.data

Settings consist of PowrSection-s

Templates and Import Section

Content Section

Settings Section

Payment Section

Design Section

PowrSection-s in code

Templates and Import Section

Each config file exports an array of JavaScript objects

const sc = POWr.simpleCopies;

import currencies from '@./../constants/currencies.js';
import {PAYPAL_URI} from '@/../helpers/constants';
// const prePath = './../../../modules/react_components';

//containers
import PowrDrilldown from '@./../modules/react_components/powr_drilldown';
import PowrPrototypeSection from '@./../modules/react_components/powr_prototype_section';
// form elements
import PowrButton from '@./../modules/react_components/powr_button';
import PowrToggle from '@./../modules/react_components/powr_toggle';
import PowrSelect from '@./../modules/react_components/powr_select';
import PowrMultitoggle from '@./../modules/react_components/powr_multitoggle';
import PowrTextInput from '@./../modules/react_components/powr_text_input';
import PowrSettingsLabel from '@./../modules/react_components/powr_settings_label';

// utils
import PowrBannerNotification from '@./../modules/react_components/utils/powr_banner_notification';
import PowrMultiInputs from '@./../modules/react_components/containers/powr_multi_inputs';

import {removeDiscountCodesFeature, showPurpleDemoStar} from '@/../config/apps/form_builder/unblockedFeatures';
import { isInABTest } from '../../../modules/powr_helpers';

// import PaymentProductInfo from '@./../modules/react_components/utils/payment_product_info';
const namespace = 'paypal';
const paypal_url = PAYPAL_URI[window.ENVIRONMENT];
const timeFrames = [
  {
    label: 'Days',
    value: 'D',
  },
  {
    label: 'Weeks',
    value: 'W',
  },
  {
    label: 'Months',
    value: 'M',
  },
  {
    label: 'Years',
    value: 'Y',
  },
];

const subscriptionDetails = [
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.product_cost'),
      namespace: `${namespace}ItemCost`,
      placeholder: sc('app_settings.payment.enter_a_price'),
      validatePrice: true,
      validateMax: 10000000,
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrMultiInputs,
    passedProps: {
      label: sc('app_settings.payment.repeat_payment_every'),
      addFormElementPadding: true,
      innerComponents: [
        {
          powrComponent: PowrTextInput,
          passedProps: {
            namespace: `${namespace}SubscriptionDuration`,
            placeholder: '30',
            validateInteger: true,
            validateLengthMax: 100,
          },
        },
        {
          powrComponent: PowrSelect,
          passedProps: {
            namespace: `${namespace}SubscriptionDurationUnits`,
            options: timeFrames,
          },
        },
      ],
    },
  },
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.trial_on'),
      namespace: `${namespace}TrialOn`,
      addFormElementPadding: true,
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: `model.${namespace}TrialOn`,
          value,
          refreshSettings: true,
        });
      },
    },
  },
  {
    powrComponent: 'div',
    passedProps: {},
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}TrialOn`,
        value: true,
      },
    ],
    innerComponents: [
      {
        powrComponent: PowrTextInput,
        passedProps: {
          label: sc('app_settings.payment.trial_price'),
          namespace: `${namespace}TrialPrice`,
          addFormElementPadding: true,
          placeholder: '0.00',
          validatePrice: true,
          validateMax: 10000000,
        },
      },
      {
        powrComponent: PowrMultiInputs,
        passedProps: {
          label: sc('app_settings.payment.trial_length'),
          addFormElementPadding: true,
          innerComponents: [
            {
              powrComponent: PowrTextInput,
              passedProps: {
                namespace: `${namespace}TrialDuration`,
                addFormElementPadding: true,
                placeholder: '30',
                validateInteger: true,
                validateLengthMax: 100,
              },
            },
            {
              powrComponent: PowrSelect,
              passedProps: {
                namespace: `${namespace}TrialDurationUnits`,
                addFormElementPadding: true,
                options: timeFrames,
              },
            },
          ],
        },
      },
    ],
  },
];

const nonSubscriptionDetails = [
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.product_cost'),
      namespace: `${namespace}ItemCost`,
      placeholder: sc('app_settings.payment.enter_a_price'),
      validatePrice: true,
      validateMax: 1000000,
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.allow_buyer_choose_price'),
      description: sc('components.multiple_apps_descriptions.allow_buyer_choose_price_description'),
      namespace: `${namespace}AllowUserToSpecifyPrice`,
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.show_quantity_input'),
      namespace: 'showQuantityInput',
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.default_quantity'),
      namespace: 'paypalQuantity',
      placeholder: '1',
      validateInteger: true,
      validateMax: 1000000,
      addFormElementPadding: true,
    },
  },

];

const discountPrototype = position => {
  return [
    {
      powrComponent: PowrTextInput,
      passedProps: {
        label: sc('app_settings.payment.discount_code_prototype'),
        namespace: 'discountCode',
        placeholder: sc('app_settings.payment.enter_discount_code'),
        validateLengthMax: 1000,
        addFormElementPadding: true,
        handleChangeComplete: (value, observableStore) => {
          observableStore.model.attributes.discounts[position].discountCode = value;
          observableStore.forceUpdate();
        },
        trimSpaces: true,
      },
      dynamicProps: [
        {
          propName: 'value',
          key: `observableStore.model.attributes.discounts.${position}.discountCode`,
        },
        {
          propName: 'featurePremiumStatus',
          evaluate: () => {
            if (isInABTest('ab_starter_removes_watermark')) {
              return 'pro';
            }
            return 'premium';
          }
        }
      ],
    },
    {
      powrComponent: PowrMultiInputs,
      passedProps: {
        label: sc('app_settings.payment.discount_amount'),
        addFormElementPadding: true,
        innerComponents: [
          {
            powrComponent: PowrMultitoggle,
            passedProps: {
              namespace: 'discountType',
              options: [['$', 'flat'], ['%', 'rate']],
              size: 3,
              handleChangeComplete: (value, observableStore) => {
                observableStore.model.attributes.discounts[position].discountType = value;
                observableStore.forceUpdate();
              },
            },
            dynamicProps: [
              {
                propName: 'value',
                key: `observableStore.model.attributes.discounts.${position}.discountType`,
              },
            ],
          },
          {
            powrComponent: PowrTextInput,
            passedProps: {
              namespace: 'discountAmount',
              placeholder: sc('app_settings.payment.enter_value'),
              validatePrice: true,
              size: 9,
              handleChangeComplete: (value, observableStore) => {
                observableStore.model.attributes.discounts[position].discountAmount = value;
                observableStore.forceUpdate();
              },
            },
            dynamicProps: [
              {
                propName: 'value',
                key: `observableStore.model.attributes.discounts.${position}.discountAmount`,
              },
            ],
          },
        ],
      },
    },
  ];
};

const checkPaymentTypes = (paymentType1, paymentType2) => {
  return observableStore => {
    const allDisabled = !observableStore.model.attributes[paymentType1] && !observableStore.model.attributes[paymentType2];
    if (allDisabled) {
      return 'free';
    }
    if (isInABTest('ab_starter_removes_watermark')) {
      return 'pro';
    }
    return 'premium';
  };
};

const connectPaymentAccounts = [
  {
    powrComponent: 'div',
    passedProps: {
      className: 'form-element small-warning',
      dangerouslySetInnerHTML: {__html: `<i class='fa fa-warning failure'></i> ${sc('formbuilder.payment.connect_warning')}`},
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes',
        check: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
  {
    powrComponent: PowrToggle,
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.allow_paypal_payments'),
      namespace: 'paypalEnabled',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.paypal_connect_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.paypalEnabled',
          value,
          refreshSettings: true,
        });
      },
    },
    dynamicProps:[
      {
        propName: 'featurePremiumStatus',
        evaluateWithStore: checkPaymentTypes('stripeEnabled', 'offlineEnabled')
      },
    ]
  },

  {
    powrComponent: PowrToggle,
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
      {
        key: 'observableStore.model.attributes.paypalEnabled',
        value: true,
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.enable_smart_buttons'),
      namespace: 'smartButtonsEnabled',
      addFormElementPadding: true,
      helpText: sc('formbuilder.payment.smart_button_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.smartButtonsEnabled',
          value,
          refreshSettings: true,
        });
      },
    },
  },
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.paypal_account'),
      namespace: `${namespace}PaypalAccount`,
      addFormElementPadding: true,
      placeholder: sc('app_settings.payment.enter_paypal_email'),
      type: 'email',
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paypalEnabled',
        value: true,
      },
    ],
  },
  {
    powrComponent: 'div',
    conditionals: [
      {
        key: 'observableStore.model.attributes.paypalEnabled',
        value: true,
      },
      {
        key: 'observableStore.model.attributes.paypalPaypalAccount',
        check: paypalPaypalAccount => {
          return !paypalPaypalAccount;
        },
      },
    ],
    passedProps: {
      className: 'small-help text-center',
    },
    innerComponents: [
      {
        powrComponent: 'span',
        passedProps: {
          dangerouslySetInnerHTML: {__html: sc('app_settings.payment.dont_have_paypal')},
        },
      },
      {
        powrComponent: 'a',
        passedProps: {
          className: 'link',
          target: 'blank',
          href: `${paypal_url}/webapps/merchantboarding/webflow/externalpartnerflow?partnerId=J4NURA49NDK6Q}`,
          dangerouslySetInnerHTML: {__html: ' ' + sc('app_settings.payment.create_one_fast')},
        },
      },
    ],
  },

  {
    powrComponent: PowrToggle,
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.allow_credit_card_payments_from_site'),
      namespace: 'stripeEnabled',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.stripe_connect_help'),
      handleChangeComplete: (value, observableStore) => {
        if (!observableStore.model.meta.external_data || !observableStore.model.meta.external_data.stripe_connected) {
          observableStore.model.connectToStripe();
        } else {
          observableStore.updateValue({
            key: 'model.stripeEnabled',
            value,
            refreshSettings: true,
          });
        }
      },
    },
    dynamicProps: [
      {
        propName: 'featurePremiumStatus',
        evaluateWithStore: checkPaymentTypes('paypalEnabled', 'offlineEnabled')
      },
    ],
  },

  {
    powrComponent: PowrToggle,
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.allow_offline_payments'),
      namespace: 'offlineEnabled',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.allow_offline_payments_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.offlineEnabled',
          value,
          refreshSettings: true,
        });
      },
    },
    dynamicProps: [
      {
        propName: 'featurePremiumStatus',
        evaluateWithStore: checkPaymentTypes('paypalEnabled', 'stripeEnabled')
      },
    ],
  },
];

const productDetails = [
  {
    powrComponent: PowrMultitoggle,
    passedProps: {
      namespace: `${namespace}PurchaseType`,
      label: sc('app_settings.payment.type_of_purchase'),
      addFormElementPadding: true,
      options: [['One time purchase', 'BuyNow'], ['Donation', 'Donate'], ['Subscription', 'Recurring']],
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: `model.${namespace}PurchaseType`,
          value,
          refreshSettings: true,
        });
      },
    },
  },
  {
    powrComponent: PowrBannerNotification,
    passedProps: {
      type: 'caution',
      label: sc('app_settings.payment.paypal_business_alert'),
      addFormElementPadding: true,
    },
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}PurchaseType`,
        value: 'Recurring',
      },
    ],
  },
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.product_name'),
      namespace: `${namespace}ItemName`,
      addFormElementPadding: true,
      placeholder: sc('app_settings.payment.product'),
      validateLengthMax: 500,
    },
  },
  {
    powrComponent: PowrSelect,
    passedProps: {
      label: sc('app_settings.payment.currency'),
      addFormElementPadding: true,
      namespace: `${namespace}CurrencyCode`,
      options: currencies,
    },
  },
  {
    powrComponent: 'div',
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}PurchaseType`,
        value: 'Recurring',
      },
    ],
    innerComponents: subscriptionDetails,
  },
  {
    powrComponent: 'div',
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}PurchaseType`,
        check: currentVal => {
          return currentVal !== 'Recurring';
        },
      },
    ],
    innerComponents: nonSubscriptionDetails,
  },
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.show_price_summary'),
      namespace: 'showPriceSummary',
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.allow_note_to_seller'),
      namespace: 'noteToSeller',
      addFormElementPadding: true,
    },
  },
];

const taxDrilldown = [
  {
    powrComponent: PowrMultitoggle,
    passedProps: {
      namespace: `${namespace}TaxType`,
      label: sc('app_settings.payment.tax_type'),
      addFormElementPadding: true,
      options: [
        [sc('app_settings.payment.tax_rate'), 'rate'],
        [sc('app_settings.payment.flat_tax'), 'flat'],
      ],
    },
  },
  {
    powrComponent: PowrTextInput,
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}TaxType`,
        value: 'rate',
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.tax_rate'),
      namespace: `${namespace}TaxRate`,
      placeholder: sc('app_settings.payment.tax_rate_placeholder'),
      validatePrice: true,
      validateMax: 100,
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrTextInput,
    conditionals: [
      {
        key: `observableStore.model.attributes.${namespace}TaxType`,
        value: 'flat',
      },
    ],
    passedProps: {
      label: sc('app_settings.payment.tax_amount'),
      namespace: `${namespace}TaxAmount`,
      placeholder: sc('app_settings.payment.tax_amount_placeholder'),
      validatePrice: true,
      validateMax: 1000000,
      addFormElementPadding: true,
    },
  },
];

const shippingDrilldown = [
  {
    powrComponent: PowrTextInput,
    passedProps: {
      label: sc('app_settings.payment.shipping_price'),
      namespace: `${namespace}ShippingCost`,
      placeholder: sc('app_settings.payment.enter_the_price_to_ship_one_item'),
      validatePrice: true,
      validateMax: 1000000,
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.input_shipping_price'),
    },
  },
  {
  powrComponent: PowrMultitoggle,
    passedProps: {
      namespace: `${namespace}NoShipping`,
      label: sc('app_settings.payment.address_prompt'),
      addFormElementPadding: true,
      options: [
        [sc('app_settings.payment.prompt_for_an_address_but_do_not_require_one'), '0'],
        [sc('app_settings.payment.do_not_prompt_for_an_address'), '1'],
        [sc('app_settings.payment.prompt_for_an_address_and_require_one'), '2'],
      ],
    },
  }
];

const discountCodeDrilldown = [
  {
    powrComponent: PowrSettingsLabel,
    passedProps: {
      namespace: 'notifications',
      label: sc('formbuilder.payment.disount_label'),
      helpText: sc('formbuilder.payment.discount_label_help'),
      addFormElementPadding: true,
    },
  },
  {
    powrComponent: PowrPrototypeSection,
    passedProps: {
      isFirstItemDeletable: true,
      namespace: 'discounts',
      onDelete: (observableStore, position) => {
        const updated = observableStore.model.attributes.discounts.filter((v, i) => i !== position);
        observableStore.updateValue({
          key: 'model.discounts',
          value: updated,
          refreshSettings: true,
        });
        removeDiscountCodesFeature(observableStore);
      },
      onReorder: (observableStore, newOrder) => {
        const updated = newOrder.map(newIndex => observableStore.model.attributes.discounts[newIndex]);
        observableStore.updateValue({
          key: 'model.discounts',
          value: updated,
          refreshSettings: true,
        });
      },
    },
    dynamicProps: [
      {
        propName: 'existingComponents',
        key: 'observableStore.model.attributes.discounts',
        evaluate: data => {
          return _.map(data, (value, position) => {
            return discountPrototype(position);
          });
        },
      },
    ],
  },
  // {
  //   powrComponent: 'div',
  //   passedProps: {
  //     className: 'small-help text-center margin-top-m',
  //   },
  //   innerComponents: [
  //     {
  //       powrComponent: 'span',
  //       passedProps: {
  //         dangerouslySetInnerHTML: {__html: sc('app_settings.payment.discount_codes_case_sensitive')},
  //       },
  //     },
  //   ],
  //   ]
  // },
  {
    powrComponent: PowrButton,
    passedProps: {
      label: sc('app_settings.payment.add_discount_code'),
      namespace: 'view_form_responses',
      addFormElementPadding: true,
      buttonType: 'primary',
      handleChangeComplete: (event, observableStore) => {
        let discounts = [...observableStore.model.attributes.discounts];
        const discountObj = {discountCode: '', discountType: 'flat', discountAmount: ''};
        if (Array.isArray(discounts)) {
          discounts.push(discountObj);
        } else {
          discounts = [discountObj];
        }
        observableStore.updateValue({
          key: 'model.discounts',
          value: discounts,
          refreshSettings: true,
        });
      },
    },
    dynamicProps: [
      {
        propName: 'currentLimitsCount',
        key: 'observableStore.model.attributes.discounts',
        evaluate: currentVal => {
          return currentVal.length;
        },
      },
      {
        propName: 'limits',
        evaluate: () => {
          if (isInABTest('ab_starter_removes_watermark')) {
            return {
              free: 0,
              pro: 3,
              enterprise: 10000,
            };
          }
          return {
            free: 0,
            premium: 1,
            pro: 3,
            enterprise: 10000,
          };
        }
      }
    ],
  },
];
const formbuilderPayments = [
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.require_payment'),
      namespace: 'paymentRequired',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.require_payment_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.paymentRequired',
          value,
          refreshSettings: true,
        });
      },
    },
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'connectPaymentAccounts',
      label: sc('app_settings.payment.connect_payment_accounts'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'warningIcon',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
      showPurpleDemoStar(connectPaymentAccounts)
    ],

    innerComponents: connectPaymentAccounts
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'productDetails',
      label: sc('app_settings.payment.product_details'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(productDetails),
    ],
    innerComponents: productDetails,
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'taxDrilldown',
      label: sc('app_settings.payment.tax'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(taxDrilldown)
    ],
    innerComponents: taxDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'shippingDrilldown',
      label: sc('app_settings.payment.shipping'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(shippingDrilldown)
    ],
    innerComponents: shippingDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'discountCode',
      label: sc('app_settings.payment.discount_code'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(discountCodeDrilldown)
    ],
    innerComponents: discountCodeDrilldown
  },
  {
    powrComponent: PowrButton,
    passedProps: {
      label: sc('app_settings.payment.preview_checkout'),
      buttonType: 'primary',
      namespace: 'previewCheckout',
      handleChangeComplete: (e, observableStore) => {
        window.checkout(e, true);
      },
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'disabled',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
  {
    powrComponent: 'div',
    passedProps: {
      className: 'small-warning text-center pad-top-nil',
      dangerouslySetInnerHTML: {__html: `<i class='fa fa-warning failure'></i> ${sc('app_settings.payment.connect_payment_account')}`},
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
      {
        key: 'observableStore.model.attributes',
        check: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
];

export default formbuilderPayments;
  • powrComponent - actual React component
  • passedProps - react component's props (they are hardcoded and don't change based on user or app)
  • dynamicProps - react component's props which are based on the model (for example if you need to pass an ID or createdAt).
  • conditionals - array of conditions when to show this component, every expression must evaluate to true
  • innerComponents - array of children components

Each item consists of:

const componentRenderer = require('@/../config/apps/sections_renderer').default;
        componentRenderer(self, 'form_builder', [
          {
            component: 'content',
            querySelector: '#powr-form-builder-content',
          },
          {
            component: 'settings',
            querySelector: '#powr-form-builder-settings',
          },
          {
            component: 'payment',
            querySelector: '#powr-form-builder-payment',
          },
          {
            component: 'design',
            querySelector: '#powr-form-builder-design',
          },
          {
            component: 'import',
            querySelector: '#powr-get-started',
          },
        ]);

Rendering sections for FormBuilder

export default (self, appName, configs) => {
  const React = require('react');
  const ReactDOM = require('react-dom');
  const PowrSection = require('@/../modules/react_components/powr_section').default;
  const ObservableStore = require('@/../helpers/observable_store').default;
  let siteColors, siteColorsRef, appsDir = 'apps';
  self.observableStore = new ObservableStore();
  self.observableStore.model = self.model;
  self.observableStore.experiments = window.GLOBALS;

  if (HOST === 'wix') {
    appsDir = 'wix-apps';

    if(Wix) {
      siteColorsRef = {};
      const wixSiteColors = Wix.Styles.getSiteColors();
      _.each(wixSiteColors, (color) => {
        siteColorsRef[color['value'].toLowerCase()] = color['reference']
      });
      siteColors = _.pluck(wixSiteColors, 'value');
      const recentColors = siteColors.splice(0,5);
      try {
        localStorage.setItem('recentColorsArr', JSON.stringify(recentColors));
      } catch (e) {
        debug()('localstorage is disabled');
      }
      Wix.Dashboard.getEditorUrl(function(url) {
        self.$el.find('.openSite').attr('href', url);
      });
      self.observableStore.siteColorsRef = siteColorsRef;
      self.observableStore.siteColors = _.chunk(siteColors.slice(5), 5);
      Wix.Settings.getDashboardAppUrl(function(url) {
        //Set the open dashboard url
        let dashboardUrl;
        if (url) {
          dashboardUrl = url + '&powr_app_id=' + self.model.meta.id;
        } else {
          dashboardUrl = '/apps/' + self.model.meta.id + '/responses';
        }
        self.observableStore.updateValue({
          key: 'dashboardUrl',
          value: dashboardUrl
        });
      });
    }
  }

  configs.forEach((config) => {
    // wix does not have import tab
    if (HOST === 'wix' && config.component === 'import') return;
    let componentsList = require(`@/../config/${appsDir}/${appName}/${config.component}`).default;
    const wrap =
      self.observableStore &&
      self.observableStore.model &&
      self.observableStore.model.meta &&
      appHasReactSections(self.observableStore.model.meta.app_namespace) &&
      config.component === 'design';
    if (wrap) {
      componentsList = wrapToSmartDesign(componentsList);
    }
    const domNode = document.querySelector(`${config.querySelector}`);
    if (domNode) {
      ReactDOM.render(
        <PowrSection componentsList={componentsList} observableStore={self.observableStore} />,
        domNode
      );
    }

  });


}

@/../config/apps/sections_renderer.jsx

import React, {Fragment} from 'react';
import {observer} from 'mobx-react';

import {recursivelyBuildComponents} from '@/../helpers/react_component_builder';

@observer
class PowrSection extends React.Component {
  render() {
    return (
      <Fragment>
        <div className="hid">{this.props.observableStore.randomNumber}</div>
        {recursivelyBuildComponents(this.props)}
      </Fragment>
    );
  }
}

export default PowrSection;

@/../modules/react_components/powr_section.jsx

import React, {Fragment} from 'react';

import {saveUnblockedFeatures} from '@/../config/apps/form_builder/unblockedFeatures';
import {SMART_DESIGN_APPLIED, SMART_DESIGN_CHANGED} from '@./../config/partials/smart_design_wrapper';

export const recursivelyBuildComponents = ({componentsList, observableStore}, index = 0) => {
  let shouldDisplay;
  let counter = index * 10;

  return _.map(componentsList, component => {
    const {powrComponent, passedProps, innerComponents, conditionals, dynamicProps} = component;
    shouldDisplay = true;
    counter++;

    //conditionals
    if (conditionals) {
      for (let i = 0; i < conditionals.length; i++) {
        const condition = conditionals[i];
        let currentVal;

        if (condition.key) {
          currentVal = getValue(observableStore, condition.key.replace(/observableStore(.)?/, ''));
        }

        if (typeof condition.check == 'function') {
          shouldDisplay = condition.check(currentVal);
        } else {
          shouldDisplay = _.isEqual(currentVal, condition.value);
        }

        // Don't build the component if shouldn't display
        // break out of the loop if false
        if (!shouldDisplay) {
          return false;
        }
      }
    }

    //dynamicProps
    let calculatedProps = {};
    if (dynamicProps) {
      _.each(dynamicProps, condition => {
        let currentVal;
        if (condition.key) {
          currentVal = getValue(observableStore, condition.key.replace(/observableStore(.)?/, ''));
        }
        calculatedProps[condition.propName] = currentVal;
        if (typeof condition.evaluate == 'function') {
          calculatedProps[condition.propName] = condition.evaluate(currentVal);
        }
        if (typeof condition.evaluateWithStore == 'function') {
          calculatedProps[condition.propName] = condition.evaluateWithStore(observableStore);
        }
      });
    }

    let props = buildProps(observableStore, powrComponent, passedProps, calculatedProps, counter);

    //innerComponents
    let children;
    if (innerComponents) {
      children = recursivelyBuildComponents({componentsList: innerComponents, observableStore}, index++);
    }
    if (powrComponent === Fragment) {
      props = _.pick(props, ['key']);
    }
    return React.createElement(powrComponent, props, children);
  });
};

// This is helper built to get nested values from an object

/*****
eg object = { model: { attributes: { label: 'Abc',value: '123' } } , meta: {id: 123}}
getValue(object, 'model.attributes.label')
=>'Abc'
getValue(object, 'model.attributes')
=> { label: 'Abc',value: '123' }
getValue(object)
=> { model: { attributes: { label: 'Abc',value: '123' } } , meta: {id: 123}}
*****/

const getValue = (object, key) => {
  if (_.isEmpty(key)) return object;
  const keyList = key.split('.');
  let result = object && object[keyList[0]];
  for (let i = 1; i < keyList.length; i++) {
    result = result && result[keyList[i]];
  }
  return result;
};

const checkPowrComponent = powrComponent => {
  const componentName = powrComponent.name || 
    powrComponent.toString().match(/^function\s*([^\s(]+)/) &&
    powrComponent.toString().match(/^function\s*([^\s(]+)/)[1];;
  
  const result = {
    isPowrComponent: componentName && componentName.match(/powr/i),
    componentName
  }
  return result;
}

const buildProps = (observableStore, powrComponent, passedProps, calculatedProps, counter) => {
  const {isPowrComponent, componentName} = checkPowrComponent(powrComponent);
  const userPremiumStatus = observableStore.model.meta.premium_status;

  if (!isPowrComponent) {
    return {
      ...passedProps,
      ...calculatedProps,
      observableStore: observableStore,
      key: counter,
    };
  }
  // Unless the value is passed from the config grab it from the model.
  if (!isValuePassedIn(passedProps) && !isValuePassedIn(calculatedProps)) {
    const value = passedProps.namespace && observableStore.model.get(passedProps.namespace);
    calculatedProps.value = value;
  }

  // powrComponents need premiumStatus and handleChangeComplete events unlike 'div', 'span', 'a' etc
  return {
    ...passedProps,
    ...calculatedProps,
    key: counter,
    observableStore: observableStore,
    userPremiumStatus: userPremiumStatus,
    handleChangeComplete: (value, ...otherArguments) => {
      if (passedProps.handleChangeComplete) {
        passedProps.handleChangeComplete(value, observableStore, otherArguments);
      } else {
        if (passedProps.namespace) {
          observableStore.updateValue({
            key: `model.${passedProps.namespace}`,
            value,
            refreshSettings: true,
            renderViewStyleOnly: passedProps.renderViewStyleOnly,
          });
        }
      }
      const smartDesignChanged =
        observableStore.model.attributes.smartDesign === SMART_DESIGN_APPLIED &&
        passedProps.namespace !== 'smartDesign' &&
        value !== SMART_DESIGN_APPLIED;
      if (smartDesignChanged) {
        observableStore.updateValue({
          key: 'model.smartDesign',
          value: SMART_DESIGN_CHANGED,
        });
      }
      observableStore.model.triggerChange(!passedProps.ignore);

      saveUnblockedFeatures(observableStore, passedProps, calculatedProps, {userPremiumStatus, componentName, value});
    },
  };
};

const isValuePassedIn = props => {
  return _.includes(_.keys(props), 'value');
};

@/../helpers/react_component_builder.jsx

Payment PowrSection

const formbuilderPayments = [
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.require_payment'),
      namespace: 'paymentRequired',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.require_payment_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.paymentRequired',
          value,
          refreshSettings: true,
        });
      },
    },
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'connectPaymentAccounts',
      label: sc('app_settings.payment.connect_payment_accounts'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'warningIcon',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
      showPurpleDemoStar(connectPaymentAccounts)
    ],

    innerComponents: connectPaymentAccounts
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'productDetails',
      label: sc('app_settings.payment.product_details'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(productDetails),
    ],
    innerComponents: productDetails,
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'taxDrilldown',
      label: sc('app_settings.payment.tax'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(taxDrilldown)
    ],
    innerComponents: taxDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'shippingDrilldown',
      label: sc('app_settings.payment.shipping'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(shippingDrilldown)
    ],
    innerComponents: shippingDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'discountCode',
      label: sc('app_settings.payment.discount_code'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(discountCodeDrilldown)
    ],
    innerComponents: discountCodeDrilldown
  },
  {
    powrComponent: PowrButton,
    passedProps: {
      label: sc('app_settings.payment.preview_checkout'),
      buttonType: 'primary',
      namespace: 'previewCheckout',
      handleChangeComplete: (e, observableStore) => {
        window.checkout(e, true);
      },
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'disabled',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
  {
    powrComponent: 'div',
    passedProps: {
      className: 'small-warning text-center pad-top-nil',
      dangerouslySetInnerHTML: {__html: `<i class='fa fa-warning failure'></i> ${sc('app_settings.payment.connect_payment_account')}`},
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
      {
        key: 'observableStore.model.attributes',
        check: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
];

export default formbuilderPayments;

Payment PowrSection

const formbuilderPayments = [
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.require_payment'),
      namespace: 'paymentRequired',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.require_payment_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.paymentRequired',
          value,
          refreshSettings: true,
        });
      },
    },
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'connectPaymentAccounts',
      label: sc('app_settings.payment.connect_payment_accounts'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'warningIcon',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
      showPurpleDemoStar(connectPaymentAccounts)
    ],

    innerComponents: connectPaymentAccounts
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'productDetails',
      label: sc('app_settings.payment.product_details'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(productDetails),
    ],
    innerComponents: productDetails,
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'taxDrilldown',
      label: sc('app_settings.payment.tax'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(taxDrilldown)
    ],
    innerComponents: taxDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'shippingDrilldown',
      label: sc('app_settings.payment.shipping'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(shippingDrilldown)
    ],
    innerComponents: shippingDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'discountCode',
      label: sc('app_settings.payment.discount_code'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(discountCodeDrilldown)
    ],
    innerComponents: discountCodeDrilldown
  },
  {
    powrComponent: PowrButton,
    passedProps: {
      label: sc('app_settings.payment.preview_checkout'),
      buttonType: 'primary',
      namespace: 'previewCheckout',
      handleChangeComplete: (e, observableStore) => {
        window.checkout(e, true);
      },
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'disabled',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
  {
    powrComponent: 'div',
    passedProps: {
      className: 'small-warning text-center pad-top-nil',
      dangerouslySetInnerHTML: {__html: `<i class='fa fa-warning failure'></i> ${sc('app_settings.payment.connect_payment_account')}`},
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
      {
        key: 'observableStore.model.attributes',
        check: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
];

export default formbuilderPayments;

Payment PowrSection

const formbuilderPayments = [
  {
    powrComponent: PowrToggle,
    passedProps: {
      label: sc('app_settings.payment.require_payment'),
      namespace: 'paymentRequired',
      addFormElementPadding: true,
      helpText: sc('app_settings.payment.require_payment_help'),
      handleChangeComplete: (value, observableStore) => {
        observableStore.updateValue({
          key: 'model.paymentRequired',
          value,
          refreshSettings: true,
        });
      },
    },
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'connectPaymentAccounts',
      label: sc('app_settings.payment.connect_payment_accounts'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'warningIcon',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
      showPurpleDemoStar(connectPaymentAccounts)
    ],

    innerComponents: connectPaymentAccounts
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'productDetails',
      label: sc('app_settings.payment.product_details'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(productDetails),
    ],
    innerComponents: productDetails,
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'taxDrilldown',
      label: sc('app_settings.payment.tax'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(taxDrilldown)
    ],
    innerComponents: taxDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'shippingDrilldown',
      label: sc('app_settings.payment.shipping'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(shippingDrilldown)
    ],
    innerComponents: shippingDrilldown
  },

  {
    powrComponent: PowrDrilldown,
    passedProps: {
      namespace: 'discountCode',
      label: sc('app_settings.payment.discount_code'),
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      showPurpleDemoStar(discountCodeDrilldown)
    ],
    innerComponents: discountCodeDrilldown
  },
  {
    powrComponent: PowrButton,
    passedProps: {
      label: sc('app_settings.payment.preview_checkout'),
      buttonType: 'primary',
      namespace: 'previewCheckout',
      handleChangeComplete: (e, observableStore) => {
        window.checkout(e, true);
      },
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
    ],
    dynamicProps: [
      {
        propName: 'disabled',
        key: 'observableStore.model.attributes',
        evaluate: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
  {
    powrComponent: 'div',
    passedProps: {
      className: 'small-warning text-center pad-top-nil',
      dangerouslySetInnerHTML: {__html: `<i class='fa fa-warning failure'></i> ${sc('app_settings.payment.connect_payment_account')}`},
    },
    conditionals: [
      {
        key: 'observableStore.model.attributes.paymentRequired',
        value: true,
      },
      {
        key: 'observableStore.model.attributes',
        check: attributes => {
          return !attributes.paypalPaypalAccount && !attributes.stripeEnabled && !attributes.offlineEnabled;
        },
      },
    ],
  },
];

export default formbuilderPayments;

plugin

build

How to build your plugin

By Sergey Tyan

How to build your plugin

  • 40