├── .distignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── deploy-plugin.yml │ └── fetch-extendify.yml ├── .gitignore ├── .nvmrc ├── .package.json.swp ├── LICENSE ├── Utils └── Bucket.php ├── admin ├── .nvmrc ├── admin.php ├── api │ └── addons.js ├── components │ ├── Icons.js │ ├── integration-block.js │ ├── logo.js │ ├── notices.js │ └── skeleton-row.js ├── contants.js ├── cpt │ ├── entry.php │ ├── form.php │ └── reusable.php ├── extend │ ├── SlotFillCreator.js │ ├── addonsProvider.js │ ├── index.js │ └── renderAddon.js ├── functions │ └── index.js ├── hooks │ ├── usePrevious.js │ └── useStateCallback.js ├── index.js ├── layout │ ├── header.js │ ├── index.js │ └── navigation.js ├── package-lock.json ├── package.json ├── pages │ ├── dashboard │ │ ├── components │ │ │ └── action.js │ │ └── dashboard.js │ ├── integrations │ │ ├── components │ │ │ ├── availableIntegration.js │ │ │ ├── error.js │ │ │ ├── install_integrations.js │ │ │ └── loading.js │ │ └── integrations.js │ └── settings │ │ ├── components │ │ └── sidebar.js │ │ ├── settings.js │ │ └── subpages │ │ ├── general │ │ ├── components │ │ │ └── messages.js │ │ └── general.js │ │ ├── import │ │ ├── components │ │ │ ├── plugin.js │ │ │ ├── pluginImport.js │ │ │ └── pluginList.js │ │ └── index.import.js │ │ └── integrationSettings │ │ ├── components │ │ └── fields.js │ │ └── integrationSettings.js ├── postcss.config.js ├── redux │ ├── actions │ │ ├── entries │ │ │ ├── getEntries.js │ │ │ ├── setRefetchingStatus.js │ │ │ └── updateEntry.js │ │ ├── generalSettings │ │ │ ├── saveSettings.js │ │ │ └── updateSettings.js │ │ ├── notice │ │ │ ├── addNotice.js │ │ │ └── removeNotice.js │ │ ├── settings │ │ │ ├── loading.js │ │ │ ├── setIntegration.js │ │ │ └── setIntegrationDetails.js │ │ └── types.js │ ├── reducers │ │ ├── entries.js │ │ ├── generalSettings.js │ │ ├── index.js │ │ ├── information.js │ │ ├── notice.js │ │ └── settings.js │ └── store │ │ └── store.js ├── style.scss ├── styles │ ├── _mixins.scss │ ├── _utils.scss │ ├── breadcrumbs.scss │ ├── integrations │ │ └── integrations.scss │ ├── settings │ │ ├── settings.scss │ │ └── sub │ │ │ ├── generalSettings.scss │ │ │ ├── importSettings.scss │ │ │ ├── integrationSettings.scss │ │ │ └── sidebar.scss │ └── variables.scss └── tailwind.config.js ├── assets ├── index.assets.php └── scripts │ └── google_recaptcha.js ├── config ├── externals.js ├── paths.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── controllers ├── forms │ ├── forms.controller.php │ ├── forms.import.controller.php │ └── import-managers │ │ ├── cf7.forms.import.php │ │ └── test.html └── index.php ├── dist ├── admin │ ├── index.asset.php │ ├── index.js │ └── style-index.css ├── blocks.build.js ├── blocks.editor.build.css ├── blocks.style.build.css └── frontend.js ├── gutenberg-forms.php ├── integrations ├── handler.php └── recaptcha │ └── guide │ └── guide.html ├── languages └── forms-gutenberg.pot ├── lib └── block │ ├── api │ └── index.js │ └── block.js ├── package-lock.json ├── package.json ├── readme.md ├── readme.txt ├── scripts ├── build.js ├── start.js └── translate.js ├── src ├── _partials │ ├── _extend_fields.scss │ ├── _keyframes.scss │ ├── _mixins.scss │ ├── _pre_mixins.scss │ └── _variables.scss ├── block │ ├── Icon.js │ ├── api │ │ └── index.js │ ├── block.js │ ├── components │ │ ├── condition.js │ │ ├── datepicker.js │ │ ├── formulaBuilder.js │ │ ├── imagePreview.js │ │ ├── imageUpload.js │ │ ├── searchTags.js │ │ ├── tagList.js │ │ └── tagSelector.js │ ├── constants │ │ └── index.js │ ├── editor.scss │ ├── fieldStyles │ │ └── index.js │ ├── formStyles │ │ └── index.js │ ├── functions │ │ └── index.js │ ├── lib │ │ └── datePicker.scss │ ├── misc │ │ └── helper.js │ └── style.scss ├── blocks.js ├── blocks │ ├── checkbox │ │ ├── block.json │ │ ├── deprecated.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── column │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── components │ │ ├── bulk_add.js │ │ ├── prefix.js │ │ └── suffix.js │ ├── datepicker │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── email │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecation.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── file_upload │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── form-button │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── form-calculation │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── form-column │ │ ├── block.json │ │ ├── components │ │ │ └── introduction.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── form-group │ │ ├── Inspector.js │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── form-steps │ │ ├── childs │ │ │ └── form-step │ │ │ │ ├── block.json │ │ │ │ ├── edit.js │ │ │ │ ├── index.js │ │ │ │ └── save.js │ │ └── root │ │ │ ├── block.json │ │ │ ├── edit.js │ │ │ ├── index.js │ │ │ ├── save.js │ │ │ └── toolbar.js │ ├── gutenberg-forms │ │ ├── Inspector.js │ │ ├── block.json │ │ ├── components │ │ │ ├── Integrations.js │ │ │ ├── fieldPlotter.js │ │ │ ├── introduction.js │ │ │ ├── messages.js │ │ │ └── templateBuilder.js │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ ├── save.js │ │ └── settings_modal │ │ │ ├── components │ │ │ ├── Empty.js │ │ │ ├── PostTypeBlock.js │ │ │ ├── header.js │ │ │ ├── preview_block.js │ │ │ ├── sidebar.js │ │ │ └── templates.js │ │ │ └── modal.js │ ├── hidden │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── message │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── name │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ ├── save.js │ │ └── style.scss │ ├── number │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── phone │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprected.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── progress │ │ ├── block.json │ │ ├── components │ │ │ └── progressBar.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── radio │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── reusable_forms │ │ ├── block.json │ │ ├── components │ │ │ └── introduction.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── select │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── text │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ ├── website │ │ ├── block.json │ │ ├── deprecated │ │ │ ├── deprecated.js │ │ │ ├── edit.js │ │ │ └── save.js │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js │ └── yes-no │ │ ├── block.json │ │ ├── edit.js │ │ ├── index.js │ │ └── save.js ├── common.scss ├── constants.js ├── extend │ └── index.js └── init.php ├── tagsHandler └── tagHandler.php └── triggers ├── email.php └── validator.php /.distignore: -------------------------------------------------------------------------------- 1 | # A set of files you probably don't want in your WordPress.org distribution 2 | .distignore 3 | .editorconfig 4 | .git 5 | .gitignore 6 | .gitlab-ci.yml 7 | .travis.yml 8 | .DS_Store 9 | .browserslistrc 10 | .eslintrc.json 11 | .eslintignore 12 | .npmrc 13 | .nvmrc 14 | .stylelintrc 15 | .stylelintignore 16 | babel.config.js 17 | Thumbs.db 18 | behat.yml 19 | bin 20 | circle.yml 21 | composer.json 22 | composer.lock 23 | Gruntfile.js 24 | package.json 25 | package-lock.json 26 | phpunit.xml 27 | phpunit.xml.dist 28 | multisite.xml 29 | multisite.xml.dist 30 | phpcs.ruleset.xml 31 | phpcs.xml 32 | phpcs.xml.dist 33 | README.md 34 | wp-cli.local.yml 35 | tests 36 | node_modules 37 | *.zip 38 | *.tar.gz 39 | tests 40 | config 41 | postcss.config.js 42 | yarn.lock 43 | .wordpress-org 44 | .github 45 | LICENSE.md 46 | .babelrc 47 | DOCKER_ENV 48 | docker_tag 49 | output.log 50 | webpack.config.js 51 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [*.yml] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | 24 | [*.txt] 25 | end_of_line = crlf 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/*.build.js 3 | **/node_modules/** 4 | **/vendor/** 5 | build 6 | coverage 7 | cypress 8 | node_modules 9 | vendor 10 | -------------------------------------------------------------------------------- /.github/workflows/deploy-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to WordPress.org 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | jobs: 7 | tag: 8 | name: New tag 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v1 13 | - name: Using Node version ${{ matrix.node-version }} 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | 18 | - name: WordPress Plugin Deploy 19 | uses: 10up/action-wordpress-plugin-deploy@stable 20 | env: 21 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} 22 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }} 23 | SLUG: forms-gutenberg 24 | -------------------------------------------------------------------------------- /.github/workflows/fetch-extendify.yml: -------------------------------------------------------------------------------- 1 | name: Fetch Extendify and create PR 2 | on: workflow_dispatch 3 | jobs: 4 | fetch-and-pr: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout code 8 | uses: actions/checkout@v2 9 | - name: Setup PHP 10 | uses: shivammathur/setup-php@v2 11 | - name: Download and replace code 12 | run: | 13 | # 1. Clean up the existing directory 14 | rm -rf ./extendify-sdk && mkdir ./extendify-sdk && mkdir ./extendify-tmp 15 | # 2. Fetch from .org 16 | curl -LO https://downloads.wordpress.org/plugin/extendify.latest-stable.zip 17 | # 3. Unzip to temp directory 18 | unzip ./extendify.latest-stable.zip -d ./extendify-tmp 19 | # 4. Copy to the sdk directory 20 | cp -R ./extendify-tmp/extendify/* ./extendify-sdk 21 | # 5. Cleanup directories and remove zip 22 | rm -rf ./extendify-tmp && rm ./extendify.latest-stable.zip 23 | # 6. Remove main file header content 24 | php -w ./extendify-sdk/extendify.php > ./ext.tmp && mv ./ext.tmp ./extendify-sdk/extendify.php 25 | - name: Create Pull Request 26 | uses: peter-evans/create-pull-request@v3 27 | with: 28 | commit-message: Update Extendify Library 29 | title: Update Extendify Library 30 | branch: update-extendify 31 | branch-suffix: random 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | dist/dashboard/blocks 4 | 5 | ## Uncomment line below if you prefer to 6 | ## keep compiled files out of version control 7 | # dist/ 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.22.12 2 | -------------------------------------------------------------------------------- /.package.json.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikolaystrikhar/gutenberg-forms/6106944153b490a8846ccec3c68f6dd6cf8602c5/.package.json.swp -------------------------------------------------------------------------------- /Utils/Bucket.php: -------------------------------------------------------------------------------- 1 | $file_path, 30 | 'filename' => $file_name, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /admin/.nvmrc: -------------------------------------------------------------------------------- 1 | 14.21.2 2 | -------------------------------------------------------------------------------- /admin/api/addons.js: -------------------------------------------------------------------------------- 1 | import { isEqual } from 'lodash'; 2 | const { applyFilters } = wp.hooks; 3 | 4 | /** 5 | * Will check if the addon is available in the registered addons list 6 | * @param {string} slug of the addons 7 | * @return {boolean} existence 8 | */ 9 | 10 | export const addonExist = (slug) => { 11 | const addonsList = applyFilters('cwp_gf.registerAddon', []); 12 | const requiredAddon = addonsList.filter((addon) => 13 | isEqual(addon.slug, slug) 14 | ); 15 | 16 | return requiredAddon.length !== 0 ? true : false; 17 | }; 18 | -------------------------------------------------------------------------------- /admin/components/notices.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { get, isEmpty } from 'lodash'; 4 | import { Notice } from '@wordpress/components'; 5 | import { removeNotice } from '../redux/actions/notice/removeNotice'; 6 | 7 | function Notices(props) { 8 | return ( 9 |
10 | {props.notice.data.map((notice, index) => { 11 | const noticeStatus = get(notice, 'status'); 12 | const status = isEmpty(noticeStatus) ? 'warning' : noticeStatus; 13 | const message = get(notice, 'message'); 14 | const id = get(notice, 'id'); 15 | 16 | return ( 17 | props.removeNotice(id)} 23 | > 24 |

25 |
26 | ); 27 | })} 28 |
29 | ); 30 | } 31 | 32 | const mapStateToProps = (state) => { 33 | const { notice } = state; 34 | 35 | return { 36 | notice, 37 | }; 38 | }; 39 | 40 | const mapDispatchToProps = { 41 | removeNotice, 42 | }; 43 | 44 | export default connect(mapStateToProps, mapDispatchToProps)(Notices); 45 | -------------------------------------------------------------------------------- /admin/components/skeleton-row.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function SkeletonRow(props) { 4 | return
; 5 | } 6 | 7 | export default SkeletonRow; 8 | -------------------------------------------------------------------------------- /admin/contants.js: -------------------------------------------------------------------------------- 1 | export const TEXT_DOMAIN = 'forms-gutenberg'; // text-domain 2 | export const proxy = 'https://api.airtable.com/v0/appywrdcoWllMrj0o/Plugins'; 3 | export const airtable_key = 'keyL7TsjgPHy6A6CT'; 4 | -------------------------------------------------------------------------------- /admin/cpt/reusable.php: -------------------------------------------------------------------------------- 1 | post_status === 'publish' ? $post->post_content : ''; 8 | } 9 | 10 | function short_code_callback( $atts ) { 11 | extract( shortcode_atts( 12 | array( 13 | 'id' => '', 14 | ), 15 | $atts 16 | ) ); 17 | 18 | $content = get_block( $id ); 19 | 20 | return $content; 21 | } 22 | 23 | function register_form_shortcode( $post_type ) { 24 | // adding a custom short_code that reflects to the post_id; 25 | add_shortcode( 26 | 'gutenberg_form', 27 | 'short_code_callback' 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /admin/extend/SlotFillCreator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inserter will just act as an anonymous parent wrapper for inserting child components 3 | * can be used to extend dashboard features 4 | */ 5 | 6 | import React from 'react'; 7 | import { createSlotFill } from '@wordpress/components'; 8 | 9 | export function SlotFillCreator(fill_name) { 10 | const modified_fill_name = 'gf_extend_'.concat(fill_name); 11 | const { Fill, Slot } = createSlotFill(modified_fill_name); 12 | 13 | return { 14 | Fill, 15 | Slot, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /admin/extend/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EXTEND FEATURES 3 | * This directory is responsible for managing all the dashboard features that can be extended 4 | * by using Addons / Integrations 5 | * 6 | * This extend object is formatted by their respected pages and their sub-page then 7 | * their respective components 8 | */ 9 | 10 | import { SlotFillCreator } from './SlotFillCreator'; 11 | import { set, get, each } from 'lodash'; 12 | const availableIntegrations = get(cwp_global, 'settings.integrations'); 13 | 14 | export const extend = { 15 | dashboard: { 16 | cards: {}, 17 | }, 18 | entries: { 19 | entry: { 20 | details: { 21 | footer: { 22 | actions: SlotFillCreator( 23 | 'entries_entry_details_footer_actions' 24 | ), 25 | links: SlotFillCreator( 26 | 'entries_entry_details_footer_links' 27 | ), 28 | }, 29 | }, 30 | }, 31 | }, 32 | settings: { 33 | integrations: { 34 | fields: {}, 35 | }, 36 | }, 37 | }; 38 | 39 | each(availableIntegrations, (_, name) => { 40 | extend['settings']['integrations']['fields'][name] = SlotFillCreator( 41 | `settings_integrations_${name}_fields` 42 | ); 43 | }); 44 | 45 | set(window, 'cwp_gf.extend', extend); 46 | -------------------------------------------------------------------------------- /admin/extend/renderAddon.js: -------------------------------------------------------------------------------- 1 | import { get, set, clone } from 'lodash'; 2 | import { addonExist } from '../api/addons'; 3 | import { TEXT_DOMAIN } from '../contants'; 4 | const { addFilter, doAction } = wp.hooks; 5 | const GlobalComponents = get(window, 'cwp_gf'); 6 | const { __ } = wp.i18n; 7 | 8 | /** 9 | * 10 | * Will register a new addon for gutenberg forms 11 | * @param {string} slug - Addon Slug 12 | * @param {object} config - Addon Configurations 13 | * 14 | * @example 15 | * 16 | * 17 | registerAddon("cwp_gf_report_entry_export_btn", { 18 | renderSlot: "entries.entry.details.footer.actions", 19 | render: () =>

HELLO WORLD

, 20 | }); 21 | 22 | * 23 | */ 24 | 25 | function registerAddon(slug, config) { 26 | if (typeof slug !== 'string') { 27 | console.error(__(`Addon Slug must be of type string.`, TEXT_DOMAIN)); 28 | } 29 | 30 | if (typeof config !== 'object') { 31 | console.error( 32 | __(`Addon Config for ${slug} must be an object`, TEXT_DOMAIN) 33 | ); 34 | } 35 | 36 | if (!'parent' in config || typeof config.parent !== 'string') { 37 | console.error( 38 | __( 39 | `Addon ${slug} must have a valid parent integration`, 40 | TEXT_DOMAIN 41 | ) 42 | ); 43 | } 44 | 45 | if (addonExist(slug)) { 46 | console.error(__(`Addon with slug ${slug} already exist`, TEXT_DOMAIN)); 47 | return; // cannot execute code further due to duplication issue 48 | } 49 | 50 | const data = { 51 | slug, 52 | config, 53 | }; 54 | 55 | addFilter( 56 | 'cwp_gf.registerAddon', 57 | 'cwp/gutenberg-forms/registerAddon', 58 | (addons) => { 59 | const newAddons = clone(addons); 60 | 61 | newAddons.push(data); 62 | return newAddons; 63 | } 64 | ); 65 | 66 | doAction('cwp_gf.update_addons'); 67 | } 68 | 69 | set(window, 'cwp_gf.extend.registerAddon', registerAddon); 70 | -------------------------------------------------------------------------------- /admin/functions/index.js: -------------------------------------------------------------------------------- 1 | import { SETTINGS_SAVING, SETTINGS_SAVED } from '../redux/actions/types'; 2 | import { __ } from '@wordpress/i18n'; 3 | 4 | export function parseSettingStatus(status) { 5 | switch (status) { 6 | case SETTINGS_SAVING: 7 | return __('Saving', 'forms-gutenberg'); 8 | 9 | case SETTINGS_SAVED: 10 | return __('Saved', 'forms-gutenberg'); 11 | } 12 | } 13 | 14 | export function parse_guide(html) { 15 | let breaker = ''; 16 | const pages = html.split(breaker); 17 | 18 | return pages; 19 | } 20 | 21 | export function makeid(length) { 22 | var result = ''; 23 | var characters = 24 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 25 | var charactersLength = characters.length; 26 | for (var i = 0; i < length; i++) { 27 | result += characters.charAt( 28 | Math.floor(Math.random() * charactersLength) 29 | ); 30 | } 31 | return result; 32 | } 33 | 34 | /** 35 | * Will convert object into query string 36 | * @param {object} params the query object 37 | * @return {string} query string 38 | */ 39 | export function httpQuery(params) { 40 | const qs = Object.keys(params) 41 | .map((key) => `${key}=${params[key]}`) 42 | .join('&'); 43 | return qs; 44 | } 45 | 46 | /** 47 | * Will return the array count to a certain range 48 | * @param {number} range 49 | * @return {array} 50 | */ 51 | export function createArrayToNum(range) { 52 | let requiredArray = []; 53 | 54 | for (let i = 0; i <= range; ++i) { 55 | requiredArray.push(i); 56 | } 57 | 58 | return requiredArray; 59 | } 60 | -------------------------------------------------------------------------------- /admin/hooks/usePrevious.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | /** 4 | * Will return the previous instance of the given value in the functional component 5 | * @param {any} value 6 | */ 7 | 8 | export function usePrevious(value) { 9 | const ref = useRef(); 10 | useEffect(() => { 11 | ref.current = value; 12 | }); 13 | return ref.current; 14 | } 15 | -------------------------------------------------------------------------------- /admin/hooks/useStateCallback.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react'; 2 | /** 3 | * Will allow passing the callback when the state updated 4 | * @param {object} initialState 5 | */ 6 | 7 | export function useStateCallback(initialState) { 8 | const [state, setState] = useState(initialState); 9 | const cbRef = useRef(null); // mutable ref to store current callback 10 | 11 | const setStateCallback = (state, cb) => { 12 | cbRef.current = cb; // store passed callback to ref 13 | setState(state); 14 | }; 15 | 16 | useEffect(() => { 17 | // cb.current is `null` on initial render, so we only execute cb on state *updates* 18 | if (cbRef.current) { 19 | cbRef.current(state); 20 | cbRef.current = null; // reset callback after execution 21 | } 22 | }, [state]); 23 | 24 | return [state, setStateCallback]; 25 | } 26 | -------------------------------------------------------------------------------- /admin/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | /** 3 | * WordPress dependencies 4 | */ 5 | const { Component } = wp.element; 6 | import './extend'; 7 | import './extend/renderAddon'; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | import { render } from 'react-dom'; 13 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 14 | import Layout from './layout'; 15 | import Dashboard from './pages/dashboard/dashboard'; 16 | import Integrations from './pages/integrations/integrations'; 17 | import Notices from './components/notices'; 18 | import Settings from './pages/settings/settings'; 19 | import { Provider } from 'react-redux'; 20 | import store from './redux/store/store'; 21 | import { SlotFillProvider } from '@wordpress/components'; 22 | import AddonsProvider from './extend/addonsProvider'; 23 | import './style.scss'; 24 | 25 | class App extends Component { 26 | render() { 27 | return ( 28 | 29 | 30 | 31 | { 34 | const { hash } = props.location; 35 | 36 | const CurrentPage = () => { 37 | if (hash.startsWith('#/dashboard')) { 38 | return ; 39 | } else if ( 40 | hash.startsWith('#/integrations') 41 | ) { 42 | return ; 43 | } else if (hash.startsWith('#/settings')) { 44 | return ; 45 | } else { 46 | return ; 47 | } 48 | }; 49 | 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | }} 60 | /> 61 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | const root = document.querySelector('#cwp-gutenberg-forms-dashboard-root'); 69 | 70 | if (root) { 71 | render(, root); 72 | } 73 | -------------------------------------------------------------------------------- /admin/layout/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Logo from '../components/logo'; 3 | import Navigation from './Navigation'; 4 | import { withRouter } from 'react-router-dom'; 5 | 6 | const Header = props => ( 7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ); 27 | 28 | export default withRouter(Header); 29 | -------------------------------------------------------------------------------- /admin/layout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './header'; 3 | 4 | function Layout(props) { 5 | return ( 6 |
7 |
8 |
9 | {props.children} 10 |
11 |
12 | ); 13 | } 14 | 15 | export default Layout; 16 | -------------------------------------------------------------------------------- /admin/layout/navigation.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { TabPanel, Button } from '@wordpress/components'; 3 | import { isEmpty, isEqual, get } from 'lodash'; 4 | import { withRouter } from 'react-router-dom'; 5 | 6 | const { __ } = wp.i18n; 7 | 8 | function Navigation(props) { 9 | const [currentTab, setCurrentTab] = useState(''); // current tab name here... 10 | 11 | const { hash, pathname, search } = props.location; 12 | const customTabs = get(props, 'tabs'); 13 | 14 | const tabs = isEmpty(customTabs) 15 | ? [ 16 | { 17 | name: '#/dashboard', 18 | title: __('Dashboard', 'forms-gutenberg'), 19 | }, 20 | { 21 | name: '#/integrations', 22 | title: __('Extensions', 'forms-gutenberg'), 23 | }, 24 | { 25 | name: '#/settings', 26 | title: __('Settings', 'forms-gutenberg'), 27 | }, 28 | ] 29 | : customTabs; 30 | 31 | const getInitialTab = () => { 32 | let initialTab = isEmpty(hash) ? '#/dashboard' : hash; 33 | 34 | tabs.forEach((tab) => { 35 | if (initialTab.startsWith(tab.name)) { 36 | initialTab = tab.name; 37 | } 38 | }); 39 | 40 | return initialTab; 41 | }; 42 | 43 | useEffect(() => { 44 | setCurrentTab(getInitialTab()); 45 | }, []); 46 | 47 | useEffect(() => { 48 | setCurrentTab(getInitialTab()); // subscribing to whenever page hash changes to update the active button 49 | }, [hash]); 50 | 51 | const onSelect = (tab) => { 52 | if (isEqual(tab.name, currentTab)) return; // if the page is already active 53 | 54 | setCurrentTab(tab.name); 55 | 56 | props.history.push({ 57 | pathname, 58 | search, 59 | hash: tab.name, 60 | }); 61 | }; 62 | 63 | return ( 64 | 81 | ); 82 | } 83 | 84 | export default withRouter(Navigation); 85 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-awesome-plugin", 3 | "version": "1.0.0", 4 | "description": "I build, therefore I'm awesome.", 5 | "scripts": { 6 | "build": "wp-scripts build ./index.js --output-path=./../dist/admin", 7 | "start": "wp-scripts start ./index.js --output-path=./../dist/admin", 8 | "packages-update": "wp-scripts packages-update" 9 | }, 10 | "devDependencies": { 11 | "@tailwindcss/forms": "^0.5.3", 12 | "@wordpress/scripts": "^19.1.0", 13 | "autoprefixer": "^10.4.13", 14 | "postcss": "^8.4.21", 15 | "tailwindcss": "^3.2.4" 16 | }, 17 | "dependencies": { 18 | "@nivo/bar": "^0.79.1", 19 | "@nivo/core": "^0.79.0", 20 | "css-loader": "^6.5.1", 21 | "node-sass": "^7.0.1", 22 | "postcss-loader": "^6.2.1", 23 | "prettier": "^2.5.1", 24 | "react-redux": "^7.2.6", 25 | "react-router": "^5.2.1", 26 | "react-router-dom": "^5.2.1", 27 | "redux-devtools-extension": "^2.13.9", 28 | "redux-thunk": "^2.4.1", 29 | "sass-loader": "^12.4.0", 30 | "unitize": "0.0.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /admin/pages/dashboard/components/action.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Action = ({ data }) => ( 4 |
5 |
6 | 10 |
11 | 12 |
13 |

14 | 15 | 16 | {data.title} 17 | 18 |

19 | 20 |

21 | {data.description} 22 |

23 |
24 | 25 | 38 |
39 | ); 40 | 41 | export default Action; 42 | -------------------------------------------------------------------------------- /admin/pages/dashboard/dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from "react-redux"; 3 | import Action from "./components/action"; 4 | 5 | const Dashboard = props => ( 6 |
7 | {props.information.cards.map((data, key) => )} 8 |
9 | ); 10 | 11 | const mapStateToProps = (state) => { 12 | return { 13 | information: state.information, 14 | }; 15 | }; 16 | 17 | export default connect(mapStateToProps, null)(Dashboard); 18 | -------------------------------------------------------------------------------- /admin/pages/integrations/components/error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@wordpress/components'; 3 | 4 | function FetchError(props) { 5 | return ( 6 |
7 |

8 | Oops! Something went wrong{' '} 9 | 12 |

13 |
14 | ); 15 | } 16 | 17 | export default FetchError; 18 | -------------------------------------------------------------------------------- /admin/pages/integrations/components/install_integrations.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, Fragment } from 'react'; 2 | import axios from 'axios'; 3 | import { proxy, airtable_key } from '../../../contants'; 4 | import { get, isEmpty, map } from 'lodash'; 5 | import Loading from './loading'; 6 | import FetchError from './error'; 7 | import AvailableIntegration from './availableIntegration'; 8 | const { __ } = wp.i18n; 9 | 10 | function InstallIntegrations() { 11 | const [availableIntegrations, setAvailableIntegrations] = useState([]); 12 | const [loading, setLoading] = useState(false); 13 | const [error, setError] = useState(false); 14 | 15 | const fetchRecords = () => { 16 | setLoading(true); 17 | setError(false); 18 | 19 | // TODO: Remove addons fetching. They should be static. 20 | axios 21 | .get(proxy, { 22 | headers: { 23 | Authorization: `Bearer ${airtable_key}`, 24 | }, 25 | }) 26 | .then((response) => { 27 | const records = get(response, 'data.records'); 28 | const fetched_records = isEmpty(records) ? [] : records; // null exception 29 | const global_integrations = get( 30 | window, 31 | 'cwp_global.settings.integrations' 32 | ); 33 | 34 | const filtered_records = fetched_records.filter((record) => { 35 | const plugin_id = get(record, 'fields.plugin_id'); 36 | 37 | if (plugin_id in global_integrations) { 38 | return false; 39 | } 40 | 41 | return true; 42 | }); // filtering the integrations that are already installed 43 | 44 | setAvailableIntegrations(filtered_records); 45 | setLoading(false); 46 | setError(false); 47 | }) 48 | .catch((err) => { 49 | console.error(err); 50 | setLoading(false); 51 | setError(true); 52 | }); 53 | }; 54 | 55 | useEffect(() => { 56 | fetchRecords(); 57 | }, []); 58 | 59 | return ( 60 |
61 | {!loading && !error && !isEmpty(availableIntegrations) && ( 62 |

63 | {__('Available', 'forms-gutenberg')} 64 |

65 | )} 66 | {loading && !error ? ( 67 | 68 | ) : ( 69 |
70 | {map(availableIntegrations, (integration, index) => { 71 | return ( 72 | 76 | ); 77 | })} 78 |
79 | )} 80 | {!loading && error && } 81 |
82 | ); 83 | } 84 | 85 | export default InstallIntegrations; 86 | -------------------------------------------------------------------------------- /admin/pages/integrations/components/loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spinner } from '@wordpress/components'; 3 | 4 | function Loading() { 5 | return ( 6 |
7 |

8 | Finding More Available Addons / Integrations 9 |

10 |
11 | ); 12 | } 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /admin/pages/integrations/integrations.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IntegrationBlock from '../../components/integration-block'; 3 | import { map, isEmpty, get } from 'lodash'; 4 | import { connect } from 'react-redux'; 5 | import { Snackbar } from '@wordpress/components'; 6 | import { parseSettingStatus } from '../../functions'; 7 | import InstallIntegrations from './components/install_integrations'; 8 | const { __ } = wp.i18n; 9 | 10 | function Integrations(props) { 11 | const { integrations, loading } = props.settings; 12 | 13 | return ( 14 |
15 |

16 | {__('Installed', 'forms-gutenberg')} 17 |

18 | 19 |
20 |
21 | {map(integrations, (integration, name) => { 22 | const { 23 | title, 24 | banner, 25 | description, 26 | fields, 27 | enable, 28 | guide, 29 | is_pro, 30 | } = integration; 31 | 32 | const is_disabled = get(integration, 'is_disabled'); 33 | const error = get(integration, 'error'); 34 | const guide_url = get(integration, 'guide_url'); 35 | 36 | return ( 37 | 52 | ); 53 | })} 54 |
55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 | {!isEmpty(loading) && ( 63 | {parseSettingStatus(loading)} 64 | )} 65 |
66 |
67 | ); 68 | } 69 | 70 | const mapStateToProps = (state) => { 71 | const { settings } = state; 72 | 73 | return { 74 | settings, 75 | }; 76 | }; 77 | 78 | export default connect(mapStateToProps, null)(Integrations); 79 | -------------------------------------------------------------------------------- /admin/pages/settings/components/sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { MenuGroup, MenuItem } from '@wordpress/components'; 4 | import { map, isEqual, isEmpty, get, each } from 'lodash'; 5 | 6 | function Sidebar(props) { 7 | const { integrations } = props.settings; 8 | const { hash, pathname, search } = props.location; 9 | 10 | const getActiveClass = (name) => { 11 | const currentHash = '#/settings/' + name; 12 | const searchHash = hash === '#/settings' ? '#/settings/general' : hash; 13 | 14 | if (isEqual(searchHash, currentHash)) { 15 | return 'gufo-font-medium gufo-text-gray-900 gufo-bg-gray-100'; 16 | } 17 | 18 | return ''; 19 | }; 20 | 21 | const getActiveSettingsPage = () => { 22 | let activePage = ''; 23 | 24 | each(integrations, (integrations, name) => { 25 | const buildHash = '#/settings/' + name; 26 | 27 | if (isEqual(buildHash, hash)) { 28 | activePage = name; 29 | } 30 | }); 31 | 32 | return activePage; 33 | }; 34 | 35 | const onSelect = (tab) => { 36 | props.history.push({ 37 | pathname, 38 | search, 39 | hash: '#/settings/' + tab, 40 | }); 41 | }; 42 | 43 | const basicClass = "gufo-cursor-pointer gufo-text-gray-900 hover:gufo-text-gray-900 hover:gufo-bg-gray-50 hover:gufo-text-gray-900 hover:gufo-bg-gray-50 gufo-group gufo-rounded-md gufo-px-3 gufo-py-2 gufo-flex gufo-items-center gufo-text-sm "; 44 | 45 | return ( 46 | 79 | ); 80 | } 81 | 82 | const mapStateToProps = (state) => { 83 | const { settings } = state; 84 | 85 | return { settings }; 86 | }; 87 | 88 | export default connect(mapStateToProps, null)(Sidebar); 89 | -------------------------------------------------------------------------------- /admin/pages/settings/settings.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Sidebar from './components/sidebar'; 3 | import General from './subpages/general/general'; 4 | import IntegrationSettings from './subpages/integrationSettings/integrationSettings'; 5 | import { connect } from 'react-redux'; 6 | import { Snackbar } from '@wordpress/components'; 7 | import { isEmpty } from 'lodash'; 8 | import { parseSettingStatus } from '../../functions'; 9 | import ImportSettingsPage from './subpages/import/index.import'; 10 | 11 | function Settings(props) { 12 | const { hash } = props.location; 13 | const { loading } = props.settings; 14 | 15 | const CurrentSettings = () => { 16 | if (hash === '#/settings/general') { 17 | return ; 18 | } else if (hash === '#/settings/import') { 19 | return ; 20 | } else if (hash === '#/settings') { 21 | return ; 22 | } else { 23 | return ; 24 | } 25 | }; 26 | 27 | return ( 28 | <> 29 |
30 | 31 | 32 |
33 | 34 |
35 |
36 | 37 | {!isEmpty(loading) && ( 38 | 39 | {parseSettingStatus(loading)} 40 | 41 | )} 42 | 43 | ); 44 | } 45 | 46 | const mapStateToProps = (state) => { 47 | return { 48 | settings: state.settings, 49 | }; 50 | }; 51 | 52 | export default connect(mapStateToProps, null)(Settings); 53 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/general/components/messages.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { map, clone, set } from 'lodash'; 4 | import { TextControl } from '@wordpress/components'; 5 | import { updateSettings } from '../../../../../redux/actions/generalSettings/updateSettings'; 6 | const { __ } = wp.i18n; 7 | 8 | function Messages(props) { 9 | const [state, setState] = useState({ 10 | messages: props.generalSettings.messages, 11 | }); 12 | 13 | const handleChange = (value, field_name) => { 14 | const newMessages = clone(state.messages); 15 | 16 | set(newMessages, field_name, value); 17 | 18 | setState({ 19 | messages: newMessages, 20 | }); 21 | 22 | props.updateSettings({ 23 | messages: newMessages, 24 | }); 25 | }; 26 | 27 | return ( 28 |
29 |

30 | {__('General', 'forms-gutenberg')} 31 |

32 | 33 |

34 |
35 | {map(state.messages, (field, field_name) => { 36 | const { label, value } = field; 37 | 38 | return ( 39 | { 41 | const newValue = { 42 | ...field, 43 | value: v, 44 | }; 45 | 46 | handleChange(newValue, field_name); 47 | }} 48 | label={label} 49 | value={value} 50 | key={label} 51 | /> 52 | ); 53 | })} 54 |
55 |
56 | ); 57 | } 58 | 59 | const mapStateToProps = (state) => { 60 | const { generalSettings } = state; 61 | 62 | return { 63 | generalSettings, 64 | }; 65 | }; 66 | 67 | const mapDispatchToProps = { 68 | updateSettings, 69 | }; 70 | 71 | export default connect(mapStateToProps, mapDispatchToProps)(Messages); // connecting to the redux-store 72 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/general/general.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Messages from './components/messages'; 3 | import { Button } from '@wordpress/components'; 4 | import { connect } from 'react-redux'; 5 | import { saveSettings } from '../../../../redux/actions/generalSettings/saveSettings'; 6 | 7 | function General(props) { 8 | const { saveSettings } = props; 9 | 10 | return ( 11 |
12 | 13 |
14 | 17 |
18 |
19 | ); 20 | } 21 | 22 | const mapDispatchToProps = { 23 | saveSettings, 24 | }; 25 | 26 | const mapStateToProps = (state) => { 27 | return { 28 | settings: state.settings, 29 | }; 30 | }; 31 | 32 | export default connect(mapStateToProps, mapDispatchToProps)(General); 33 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/import/components/plugin.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { get, isEmpty } from 'lodash'; 3 | 4 | import { 5 | Card, 6 | CardBody, 7 | CardDivider, 8 | CardFooter, 9 | CardHeader, 10 | CardMedia, 11 | } from '@wordpress/components'; 12 | import { TEXT_DOMAIN } from '../../../../../contants'; 13 | 14 | const { Icon, Button } = wp.components; 15 | const { __ } = wp.i18n; 16 | 17 | function Plugin(props) { 18 | const data = props.plugin; 19 | const name = get(data, 'Name'); 20 | const description = get(data, 'description'); 21 | const icon = get(data, 'icon'); 22 | 23 | return ( 24 | 25 | 26 |

27 | 28 | {!isEmpty(icon) && } 29 | {name} 30 | 31 | 34 |

35 |

{description}

36 |
37 |
38 | ); 39 | } 40 | 41 | export default Plugin; 42 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/import/components/pluginList.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import axios from 'axios'; 3 | import { get, map, isEmpty } from 'lodash'; 4 | import Plugin from './plugin'; 5 | import { TEXT_DOMAIN } from '../../../../../contants'; 6 | 7 | const { Spinner, Button, Icon } = wp.components; 8 | const { __ } = wp.i18n; 9 | 10 | /** 11 | * Will render a plugin list that forms are available for importing 12 | */ 13 | 14 | function PluginList(props) { 15 | const [plugins, setPlugins] = useState([]); 16 | const [loading, setLoading] = useState(false); 17 | const [error, setError] = useState(false); 18 | 19 | const restUrl = get(window, 'cwp_global.rest_url'); 20 | const proxy = restUrl + 'gutenberg-forms/forms/v1/imports/plugins'; 21 | 22 | const fetchPlugins = () => { 23 | setLoading(true); 24 | setError(false); 25 | 26 | axios 27 | .get(proxy) 28 | .then((response) => { 29 | setLoading(false); 30 | setError(false); 31 | setPlugins(response.data); 32 | }) 33 | .catch((error) => { 34 | setLoading(false); 35 | setError(true); 36 | }); 37 | }; 38 | 39 | useEffect(fetchPlugins, []); 40 | 41 | return ( 42 |
43 | {!loading && !error && ( 44 |

45 | {__('Import forms from other plugins', 'forms-gutenberg')} 46 |

47 | )} 48 | {!loading && 49 | !error && 50 | map(plugins, (plugin, idx) => ( 51 | props.onSelect(pl)} 53 | plugin={plugin} 54 | key={idx} 55 | /> 56 | ))} 57 | {loading && !error && ( 58 |
59 | 60 | 61 | {__('Fetching Available Imports', TEXT_DOMAIN)} 62 | 63 |
64 | )} 65 | {!loading && error && ( 66 |
67 | 68 | {__('Failed to Fetch Available Imports', TEXT_DOMAIN)}{' '} 69 | 76 | 77 |
78 | )} 79 | {!loading && !error && isEmpty(plugins) && ( 80 |
81 | 82 | 83 | {__( 84 | 'No Supported Form Plugin found to import!', 85 | TEXT_DOMAIN 86 | )} 87 | 88 |
89 | )} 90 |
91 | ); 92 | } 93 | 94 | export default PluginList; 95 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/import/index.import.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import PluginList from './components/pluginList'; 4 | import { isEmpty } from 'lodash'; 5 | import PluginImport from './components/pluginImport'; 6 | import { TEXT_DOMAIN } from '../../../../contants'; 7 | 8 | const { __ } = wp.i18n; 9 | 10 | /** 11 | * All available imports from 3rd party plugins like contact form 12 | */ 13 | 14 | function Import(props) { 15 | const [state, setState] = useState({ 16 | // trying to be elaborative ;) 17 | selectedPluginToImportForms: null, 18 | }); 19 | 20 | /** 21 | * When a plugin is selected to import below function will fire. 22 | * This plugin will be selected for further configuration for importing forms 23 | * @param {object} plugin 24 | */ 25 | 26 | const onSelect = (plugin) => { 27 | // updating the selected plugin 28 | 29 | setState({ ...state, selectedPluginToImportForms: plugin }); 30 | }; 31 | 32 | const onExit = () => { 33 | // going back to the plugin lists screen 34 | setState({ ...state, selectedPluginToImportForms: null }); 35 | }; 36 | 37 | const { selectedPluginToImportForms } = state; 38 | 39 | return ( 40 |
41 |
48 | 49 |
50 | {!isEmpty(selectedPluginToImportForms) && ( 51 | 55 | )} 56 |
57 | ); 58 | } 59 | 60 | export default withRouter(Import); 61 | -------------------------------------------------------------------------------- /admin/pages/settings/subpages/integrationSettings/integrationSettings.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Fields from './components/fields'; 4 | import { has, get, isEmpty } from 'lodash'; 5 | 6 | function IntegrationSettings(props) { 7 | const { hash } = props.location; 8 | const { integrations } = props.settings; 9 | 10 | const getName = () => { 11 | const queries = hash.split('/'); 12 | 13 | const name = queries[queries.length - 1]; 14 | 15 | return name; 16 | }; 17 | 18 | const hasIntegration = has(integrations, getName()); 19 | 20 | const hasFields = 21 | has(integrations, getName()) && 22 | !isEmpty(get(integrations[getName()], 'fields')); 23 | 24 | return ( 25 |
26 | {hasIntegration && hasFields && } 27 |
28 | ); 29 | } 30 | 31 | const mapStateToProps = (state) => { 32 | return { 33 | settings: state.settings, 34 | }; 35 | }; 36 | 37 | export default connect(mapStateToProps, null)(IntegrationSettings); 38 | -------------------------------------------------------------------------------- /admin/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /admin/redux/actions/entries/getEntries.js: -------------------------------------------------------------------------------- 1 | import { 2 | ENTRIES_FETCHED, 3 | ENTRIES_FETCHING, 4 | ENTRIES_FETCH_FAILED, 5 | } from '../types'; 6 | import axios from 'axios'; 7 | import { get, omit } from 'lodash'; 8 | import { httpQuery } from '../../../functions'; 9 | import store from '../../store/store'; 10 | 11 | const rest_url = get(window, 'cwp_global.rest_url'); 12 | 13 | export const getEntries = 14 | ( 15 | currentPage, // current page index to be fetched 16 | queryFilters = {}, // filters that will be assigned when fetching the entries 17 | omitFilters = [], // filters that will be omitted when fetching the entries 18 | callback = () => null, // callback on success, 19 | hash = '' // currentPage Hash 20 | ) => 21 | (dispatch) => { 22 | const entriesState = store.getState().entries; 23 | const perPage = get(entriesState, 'perPage'); 24 | const stateFilters = get(entriesState, 'filters'); 25 | const currentPageInState = get(entriesState, 'currentPage'); 26 | 27 | let filters = { 28 | ...stateFilters, 29 | ...queryFilters, 30 | page: currentPage === null ? currentPageInState : currentPage, 31 | per_page: perPage, 32 | }; 33 | 34 | filters = omit(filters, omitFilters); 35 | 36 | const filters_query = httpQuery(filters); 37 | 38 | // temp fix. 39 | let proxy; 40 | if ( rest_url.includes( 'wp-json' ) ) { 41 | proxy = rest_url.concat(`wp/v2/cwp_gf_entries?${filters_query}`); 42 | } else { 43 | proxy = rest_url.concat(`wp/v2/cwp_gf_entries&${filters_query}`); 44 | } 45 | 46 | dispatch({ 47 | type: ENTRIES_FETCHING, 48 | }); 49 | 50 | axios 51 | .get(proxy) 52 | .then((response) => { 53 | const totalPages = get(response.headers, 'x-wp-totalpages'); 54 | const totalEntries = get(response.headers, 'x-wp-total'); 55 | const { data = [] } = response; 56 | 57 | dispatch({ 58 | type: ENTRIES_FETCHED, 59 | payload: { 60 | data, 61 | totalPages, 62 | currentPage: currentPage === null ? currentPageInState : currentPage, 63 | totalEntries, 64 | filters, 65 | hash, 66 | }, 67 | }); 68 | 69 | callback(data); 70 | }) 71 | .catch((error) => { 72 | dispatch({ 73 | type: ENTRIES_FETCH_FAILED, 74 | }); 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /admin/redux/actions/entries/setRefetchingStatus.js: -------------------------------------------------------------------------------- 1 | import { SET_ENTRIES_REFETCHING_STATUS } from '../types'; 2 | 3 | export const setRefetchingStatus = (status) => (dispatch) => { 4 | dispatch({ 5 | type: SET_ENTRIES_REFETCHING_STATUS, 6 | payload: { 7 | status, 8 | }, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /admin/redux/actions/entries/updateEntry.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_ENTRY } from '../types'; 2 | import { get, findIndex, clone } from 'lodash'; 3 | import store from '../../store/store'; 4 | 5 | export const updateEntry = (updatedEntry) => (dispatch) => { 6 | const id = get(updatedEntry, 'id'); 7 | const currentEntries = clone(store.getState().entries.data); 8 | 9 | // current entry index 10 | const idx = findIndex(currentEntries, { id }); 11 | 12 | currentEntries.splice(idx, 1, updatedEntry); 13 | 14 | // now the entry is updated 15 | dispatch({ 16 | type: UPDATE_ENTRY, 17 | payload: { 18 | updatedEntries: currentEntries, 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /admin/redux/actions/generalSettings/saveSettings.js: -------------------------------------------------------------------------------- 1 | import { 2 | SAVE_SETTINGS, 3 | SETTINGS_SAVING, 4 | SETTINGS_SAVED, 5 | SETTINGS_DONE, 6 | } from '../types'; 7 | import store from '../../store/store'; 8 | 9 | export const saveSettings = () => (dispatch) => { 10 | const general_settings_key = 'cwp_gutenberg_forms_general_settings'; 11 | 12 | const { generalSettings } = store.getState(); 13 | 14 | const model = new wp.api.models.Settings({ 15 | [general_settings_key]: JSON.stringify(generalSettings), 16 | }); 17 | 18 | dispatch({ 19 | type: SAVE_SETTINGS, 20 | }); 21 | 22 | dispatch({ 23 | type: SETTINGS_SAVING, 24 | }); 25 | 26 | model.save().then((response) => { 27 | dispatch({ 28 | type: SETTINGS_SAVED, 29 | }); 30 | 31 | setTimeout(() => { 32 | dispatch({ 33 | type: SETTINGS_DONE, 34 | }); 35 | }, 1000); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /admin/redux/actions/generalSettings/updateSettings.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_SETTINGS } from '../types'; 2 | 3 | export const updateSettings = (newSettings) => (dispatch) => { 4 | dispatch({ 5 | type: UPDATE_SETTINGS, 6 | payload: { 7 | newSettings, 8 | }, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /admin/redux/actions/notice/addNotice.js: -------------------------------------------------------------------------------- 1 | import { ADD_NOTICE, REMOVE_NOTICE, REMOVE_SIMILAR_NOTICES } from '../types'; 2 | import { makeid } from '../../../functions'; 3 | import { set } from 'lodash'; 4 | 5 | export const addNotice = (notice) => (dispatch) => { 6 | set(notice, 'id', 'notice-' + makeid(10)); 7 | 8 | dispatch({ 9 | type: ADD_NOTICE, 10 | payload: { 11 | notice, 12 | }, 13 | }); 14 | 15 | dispatch({ 16 | type: REMOVE_SIMILAR_NOTICES, 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /admin/redux/actions/notice/removeNotice.js: -------------------------------------------------------------------------------- 1 | import { REMOVE_NOTICE } from '../types'; 2 | 3 | export const removeNotice = (id) => (dispatch) => { 4 | dispatch({ 5 | type: REMOVE_NOTICE, 6 | payload: { 7 | id, 8 | }, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /admin/redux/actions/settings/loading.js: -------------------------------------------------------------------------------- 1 | import { SETTINGS_SAVED, SETTINGS_SAVING } from '../types'; 2 | 3 | export const setSettingsStatus = (fetching) => (dispatch) => { 4 | const type = fetching ? SETTINGS_SAVING : SETTINGS_SAVED; 5 | 6 | dispatch({ 7 | type, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /admin/redux/actions/settings/setIntegration.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_INTEGRATION, 3 | SETTINGS_SAVING, 4 | SETTINGS_SAVED, 5 | SETTINGS_DONE, 6 | } from '../types'; 7 | 8 | export const setIntegration = (integrationName, query) => (dispatch) => { 9 | //where query is weather to enable or disable integration 10 | 11 | const integration_key = 'cwp__enable__'.concat(integrationName); 12 | const model = new wp.api.models.Settings({ 13 | [integration_key]: query, 14 | }); 15 | 16 | dispatch({ 17 | type: SETTINGS_SAVING, 18 | }); 19 | 20 | model.save().then((response) => { 21 | const payload = { 22 | [integration_key]: response[integration_key], 23 | integrationName, 24 | query, 25 | }; 26 | 27 | dispatch({ 28 | type: SET_INTEGRATION, 29 | payload, 30 | }); 31 | 32 | dispatch({ 33 | type: SETTINGS_SAVED, 34 | }); 35 | 36 | setTimeout(() => { 37 | dispatch({ 38 | type: SETTINGS_DONE, 39 | }); 40 | }, 1000); 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /admin/redux/actions/settings/setIntegrationDetails.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_INTEGRATION_DETAILS, 3 | SETTINGS_DONE, 4 | SETTINGS_SAVING, 5 | SETTINGS_SAVED, 6 | } from '../types'; 7 | import { each } from 'lodash'; 8 | 9 | export const setIntegrationDetails = 10 | (fields, integrationName) => (dispatch) => { 11 | let queries = {}; 12 | 13 | each(fields, (field, key) => { 14 | const query_key = 'cwp__' + integrationName + '__' + key; 15 | 16 | queries[query_key] = field.value; 17 | }); 18 | 19 | dispatch({ 20 | type: SETTINGS_SAVING, 21 | }); 22 | 23 | const model = new wp.api.models.Settings({ 24 | ...queries, 25 | }); 26 | 27 | model.save().then((response) => { 28 | const payload = { 29 | fields, 30 | integrationName, 31 | }; 32 | 33 | dispatch({ 34 | type: SET_INTEGRATION_DETAILS, 35 | payload, 36 | }); 37 | 38 | dispatch({ 39 | type: SETTINGS_SAVED, 40 | }); 41 | 42 | setTimeout(() => { 43 | dispatch({ 44 | type: SETTINGS_DONE, 45 | }); 46 | }, 1000); 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /admin/redux/actions/types.js: -------------------------------------------------------------------------------- 1 | //SETTINGS 2 | export const SET_OPTION = 'SET_OPTION'; 3 | export const SET_INTEGRATION = 'SET_INTEGRATION'; 4 | export const SETTINGS_SAVING = 'SETTINGS_SAVING'; 5 | export const SETTINGS_SAVED = 'SETTINGS_SAVED'; 6 | export const SETTINGS_DONE = 'SETTINGS_DONE'; 7 | export const SET_INTEGRATION_DETAILS = 'SET_INTEGRATION_DETAILS'; 8 | 9 | //GENERAL SETTINGS 10 | export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; 11 | export const SAVE_SETTINGS = 'SAVE_SETTINGS'; 12 | 13 | //Notices 14 | export const ADD_NOTICE = 'ADD_NOTICE'; 15 | export const REMOVE_NOTICE = 'REMOVE_NOTICE'; 16 | export const REMOVE_SIMILAR_NOTICES = 'REMOVE_SIMILAR_NOTICES'; 17 | 18 | //entries - PLURAL 19 | export const ENTRIES_FETCHING = 'ENTRIES_FETCHING'; 20 | export const ENTRIES_FETCHED = 'ENTRIES_FETCHED'; 21 | export const ENTRIES_FETCH_FAILED = 'ENTRIES_FETCH_FAILED'; 22 | export const SET_ENTRIES_REFETCHING_STATUS = 'SET_ENTRIES_REFETCHING_STATUS'; 23 | 24 | // entry - SINGULAR 25 | export const UPDATE_ENTRY = 'UPDATE_ENTRY'; 26 | -------------------------------------------------------------------------------- /admin/redux/reducers/entries.js: -------------------------------------------------------------------------------- 1 | import { 2 | ENTRIES_FETCHED, 3 | ENTRIES_FETCHING, 4 | ENTRIES_FETCH_FAILED, 5 | UPDATE_ENTRY, 6 | SET_ENTRIES_REFETCHING_STATUS, 7 | } from '../actions/types'; 8 | 9 | const initialState = { 10 | filters: {}, 11 | data: [], 12 | loading: false, 13 | error: false, 14 | totalPages: 0, 15 | currentPage: 0, 16 | perPage: 10, 17 | totalEntries: 0, 18 | refetchingStatus: true, 19 | entriesFetchedHash: '', 20 | }; 21 | 22 | export default function (state = initialState, action) { 23 | switch (action.type) { 24 | case ENTRIES_FETCHING: { 25 | return { 26 | ...state, 27 | loading: true, 28 | error: false, 29 | }; 30 | } 31 | 32 | case ENTRIES_FETCHED: { 33 | return { 34 | ...state, 35 | loading: false, 36 | error: false, 37 | data: action.payload.data, 38 | totalPages: action.payload.totalPages, 39 | currentPage: action.payload.currentPage, 40 | totalEntries: action.payload.totalEntries, 41 | filters: action.payload.filters, 42 | entriesFetchedHash: action.payload.hash, 43 | }; 44 | } 45 | 46 | case ENTRIES_FETCH_FAILED: { 47 | return { 48 | ...state, 49 | loading: false, 50 | error: true, 51 | }; 52 | } 53 | 54 | case UPDATE_ENTRY: { 55 | return { 56 | ...state, 57 | data: action.payload.updatedEntries, 58 | }; 59 | } 60 | 61 | case SET_ENTRIES_REFETCHING_STATUS: { 62 | return { 63 | ...state, 64 | refetchingStatus: action.payload.status, 65 | }; 66 | } 67 | 68 | default: 69 | return state; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /admin/redux/reducers/generalSettings.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_SETTINGS, SAVE_SETTINGS } from '../actions/types'; 2 | import { get } from 'lodash'; 3 | 4 | const generalSettings = get(cwp_global, 'general'); 5 | const initialState = { 6 | ...generalSettings, 7 | }; 8 | 9 | export default function (state = initialState, action) { 10 | switch (action.type) { 11 | case UPDATE_SETTINGS: 12 | return { 13 | ...state, 14 | ...action.payload.newSettings, 15 | }; 16 | 17 | case SAVE_SETTINGS: 18 | return { 19 | ...state, 20 | }; 21 | 22 | default: 23 | return { 24 | ...state, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /admin/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import SettingsReducer from './settings'; 3 | import InformationReducer from './information'; 4 | import GeneralSettingsReducer from './generalSettings'; 5 | import NoticeReducer from './notice'; 6 | import EntriesReducer from './entries'; 7 | 8 | export default combineReducers({ 9 | settings: SettingsReducer, 10 | information: InformationReducer, 11 | generalSettings: GeneralSettingsReducer, 12 | notice: NoticeReducer, // for showing / handling notices like integration error and warnings... 13 | entries: EntriesReducer, 14 | }); 15 | -------------------------------------------------------------------------------- /admin/redux/reducers/information.js: -------------------------------------------------------------------------------- 1 | const information = cwp_global.informations; 2 | 3 | const initialState = { 4 | cards: information.cards, 5 | }; 6 | 7 | export default function (state = initialState, action) { 8 | switch (action) { 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /admin/redux/reducers/notice.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_NOTICE, 3 | REMOVE_NOTICE, 4 | REMOVE_SIMILAR_NOTICES, 5 | } from '../actions/types'; 6 | import { isEqual, clone, uniqBy } from 'lodash'; 7 | 8 | const initialState = { 9 | data: [], 10 | }; 11 | 12 | export default function (state = initialState, action) { 13 | switch (action.type) { 14 | case ADD_NOTICE: { 15 | let newState = clone(state); 16 | newState.data.push(action.payload.notice); 17 | return newState; 18 | } 19 | case REMOVE_NOTICE: { 20 | let newState = clone(state); 21 | newState.data = newState.data.filter( 22 | (notice) => !isEqual(notice.id, action.payload.id) 23 | ); 24 | return newState; 25 | } 26 | 27 | case REMOVE_SIMILAR_NOTICES: { 28 | let newState = clone(state); 29 | 30 | newState.data = uniqBy(newState.data, 'uniqueKey'); // creating a unique notices data 31 | 32 | return newState; 33 | } 34 | 35 | default: 36 | return state; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /admin/redux/reducers/settings.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_INTEGRATION, 3 | SETTINGS_SAVING, 4 | SETTINGS_SAVED, 5 | SETTINGS_DONE, 6 | SET_INTEGRATION_DETAILS, 7 | } from '../actions/types'; 8 | 9 | import { set } from 'lodash'; 10 | 11 | const settings = cwp_global.settings; 12 | 13 | const initialState = { 14 | ...settings, 15 | loading: '', 16 | authenticating: false, 17 | }; 18 | 19 | export default function (state = initialState, action) { 20 | switch (action.type) { 21 | case SET_INTEGRATION: 22 | const { 23 | payload: { integrationName, query }, 24 | } = action; 25 | 26 | state.integrations[integrationName] = { 27 | ...state.integrations[integrationName], 28 | enable: query, 29 | }; 30 | 31 | return { 32 | ...state, 33 | }; 34 | 35 | case SET_INTEGRATION_DETAILS: 36 | // const { payload: { integrationName, fields } } = action; 37 | 38 | // state.integrations[integrationName].fields = fields; 39 | 40 | return { 41 | ...state, 42 | }; 43 | 44 | case SETTINGS_SAVING: 45 | return { 46 | ...state, 47 | loading: SETTINGS_SAVING, 48 | }; 49 | 50 | case SETTINGS_SAVED: 51 | return { 52 | ...state, 53 | loading: SETTINGS_SAVED, 54 | }; 55 | 56 | case SETTINGS_DONE: 57 | return { 58 | ...state, 59 | loading: '', 60 | }; 61 | 62 | default: 63 | return state; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /admin/redux/store/store.js: -------------------------------------------------------------------------------- 1 | import thunk from 'redux-thunk'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import combineReducers from '../reducers/index'; 4 | import { composeWithDevTools } from 'redux-devtools-extension'; 5 | const initialState = {}; 6 | 7 | const middlewares = [thunk]; 8 | 9 | const store = createStore( 10 | combineReducers, 11 | initialState, 12 | composeWithDevTools(applyMiddleware(...middlewares)) 13 | ); 14 | 15 | export default store; 16 | -------------------------------------------------------------------------------- /admin/style.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import "./styles/variables.scss"; 6 | @import "./styles/mixins"; 7 | @import "./styles/utils"; 8 | @import "./styles/integrations/integrations.scss"; 9 | @import "./styles/settings/settings.scss"; 10 | @import "./styles/breadcrumbs.scss"; 11 | 12 | .cwp-gf-modal { 13 | .footer { 14 | display: none; 15 | text-align: right; 16 | button:first-child { 17 | margin-right: 10px; 18 | } 19 | } 20 | } 21 | 22 | .cwp-notices-root { 23 | .cwp-notice { 24 | margin: 10px 0px !important; 25 | p { 26 | margin: 0 !important; 27 | } 28 | } 29 | } 30 | 31 | .components-guide__container { 32 | padding: 20px; 33 | .cwp_guide { 34 | img { 35 | width: 100%; 36 | } 37 | } 38 | 39 | .components-guide__footer { 40 | width: auto; 41 | } 42 | } 43 | 44 | .gutenberg-forms_page_cwp_gf_dashboard #wpcontent { 45 | padding-left: 0 !important; 46 | } 47 | 48 | .toplevel_page_gutenberg_forms #wpcontent { 49 | padding-left: 0 !important; 50 | } 51 | 52 | .cwp_load { 53 | position: fixed; 54 | right: 30px; 55 | top: 10px; 56 | } 57 | 58 | .cwp_guide { 59 | & img { 60 | width: 100%; 61 | } 62 | 63 | &.centered { 64 | & img { 65 | width: 70% !important; 66 | } 67 | } 68 | } 69 | 70 | .cwp-gf-skeleton { 71 | height: 30px; 72 | animation: pulse 1s infinite ease-in-out; 73 | margin-top: 10px; 74 | } 75 | -------------------------------------------------------------------------------- /admin/styles/_utils.scss: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Used to insert breakpoints while working directly 4 | 5 | **/ 6 | 7 | @mixin breakpoint($class) { 8 | @if $class == "xs" { 9 | @media (max-width: 767px) { 10 | @content; 11 | } 12 | } @else if $class == "xxs" { 13 | @media (max-width: 600px) { 14 | @content; 15 | } 16 | } @else if $class == "sm" { 17 | @media (max-width: 768px) { 18 | @content; 19 | } 20 | } @else if $class == "md" { 21 | @media (max-width: 992px) { 22 | @content; 23 | } 24 | } @else if $class == "lg" { 25 | @media (max-width: 1200px) { 26 | @content; 27 | } 28 | } @else { 29 | @warn "Breakpoint mixin supports: xs, sm, md, lg"; 30 | } 31 | } 32 | 33 | /** 34 | Will make the text un selectable 35 | **/ 36 | 37 | @mixin un-selectable() { 38 | user-select: none; 39 | -moz-user-select: none; 40 | -ms-user-select: none; 41 | } 42 | 43 | /** 44 | Apply given size to width and height 45 | **/ 46 | @mixin dimensions($size) { 47 | width: $size; 48 | height: $size; 49 | } 50 | -------------------------------------------------------------------------------- /admin/styles/breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | .cwp-breadcrumbs { 2 | display: flex; 3 | flex-direction: row; 4 | padding: 20px 0px; 5 | 6 | a { 7 | text-decoration: none !important; 8 | } 9 | 10 | &.end { 11 | justify-content: flex-end; 12 | } 13 | 14 | &.center { 15 | justify-content: flex-center; 16 | } 17 | 18 | &.start { 19 | justify-content: flex-start; 20 | } 21 | 22 | .breadcrumb { 23 | display: flex; 24 | align-items: center; 25 | 26 | .cwp-separator { 27 | margin: 0px 5px; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /admin/styles/settings/settings.scss: -------------------------------------------------------------------------------- 1 | @import "./sub/sidebar.scss"; 2 | @import "./sub/integrationSettings.scss"; 3 | @import "./sub/generalSettings.scss"; 4 | @import "./sub/importSettings.scss"; 5 | 6 | -------------------------------------------------------------------------------- /admin/styles/settings/sub/generalSettings.scss: -------------------------------------------------------------------------------- 1 | .cwp_general_settings_root { 2 | & .cwp_general_settings_messages { 3 | & .fields { 4 | display: grid; 5 | grid-template-columns: 1fr 1fr; 6 | grid-gap: 30px; 7 | & input { 8 | padding: 10px 10px; 9 | } 10 | 11 | @include breakpoint("sm") { 12 | grid-template-columns: 1fr; 13 | } 14 | } 15 | } 16 | 17 | & .cwp_general_settings_root_save { 18 | margin: 20px 0px; 19 | display: flex; 20 | justify-content: flex-end; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /admin/styles/settings/sub/importSettings.scss: -------------------------------------------------------------------------------- 1 | .cwp_plugin_import_root { 2 | .cwp_import_plugin_list { 3 | .cwp-plugin-import { 4 | h3 { 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | span, 9 | svg { 10 | margin-right: 10px; 11 | display: flex; 12 | flex-direction: row; 13 | align-items: center; 14 | } 15 | } 16 | } 17 | 18 | .cwp_import_plugin_list_prompt { 19 | padding: 50px 0px; 20 | text-align: center; 21 | background: #eee; 22 | span { 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | 27 | .components-spinner { 28 | margin: 0px 10px 0px 0px; 29 | } 30 | } 31 | } 32 | 33 | .cwp_empty { 34 | padding: 50px 0px; 35 | text-align: center; 36 | display: flex; 37 | background: #eee; 38 | flex-direction: column; 39 | justify-content: center; 40 | align-items: center; 41 | svg { 42 | width: 30px; 43 | height: 30px; 44 | margin-bottom: 10px; 45 | } 46 | } 47 | } 48 | 49 | .cwp_plugin_import_options { 50 | .cwp_plugin_import_settings { 51 | .cwp_plugin_import_settings_search { 52 | padding: 30px; 53 | border: 1px solid #eee; 54 | .cwp-sf-label { 55 | display: flex; 56 | flex-direction: row; 57 | padding: 6px 0px; 58 | .components-spinner { 59 | margin: 0px 0px -3px 10px; 60 | } 61 | } 62 | 63 | .cwp_plugin_import_settings_footer { 64 | display: flex; 65 | justify-content: flex-end; 66 | & button:first-child { 67 | margin-right: 10px; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /admin/styles/settings/sub/integrationSettings.scss: -------------------------------------------------------------------------------- 1 | .cwp_integ_fields { 2 | & .cwp_setting_options { 3 | display: flex; 4 | justify-content: flex-end; 5 | 6 | & button:nth-child(1) { 7 | margin: 0px 10px 0px 0px; 8 | } 9 | 10 | } 11 | 12 | & h3 { 13 | & span { 14 | vertical-align: text-top; 15 | } 16 | } 17 | 18 | & .cwp_api_field { 19 | margin: 10px 0px; 20 | display: flex; 21 | flex-direction: row; 22 | align-items: flex-end; 23 | 24 | & .api_input { 25 | flex:2; 26 | 27 | & input { 28 | padding: 5px 15px; 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /admin/styles/settings/sub/sidebar.scss: -------------------------------------------------------------------------------- 1 | .cwp_settings_root { 2 | & .cwp_sidebar { 3 | background: #fff; 4 | border-right: 1px solid #ccc; 5 | height: 100%; 6 | 7 | & button.is-primary { 8 | color: #fff; 9 | 10 | &:focus { 11 | color: #fff !important; 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /admin/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $secondaryColor: #ccc; -------------------------------------------------------------------------------- /admin/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | module.exports = { 4 | prefix: 'gufo-', 5 | content: [ 6 | "./*.php", 7 | "./**/*.php", 8 | "./components/*.js", 9 | "./layout/*.js", 10 | "./pages/*.js", 11 | "./pages/**/*.js", 12 | ], 13 | theme: { 14 | extend: {}, 15 | }, 16 | plugins: [ 17 | require('@tailwindcss/forms'), 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /assets/index.assets.php: -------------------------------------------------------------------------------- 1 | blocks = $blocks; 10 | } 11 | 12 | public function enqueue(): void { 13 | if ( has_block( "cwp/block-gutenberg-forms" ) ) { 14 | wp_enqueue_script( 15 | 'gutenberg-forms-google-recaptcha', 16 | plugins_url( '/', __FILE__ ) . 'scripts/google_recaptcha.js', 17 | array( 'jquery' ), 18 | filemtime( plugin_dir_path( __FILE__ ) . 'scripts/google_recaptcha.js' ), 19 | true 20 | ); 21 | 22 | $locale = substr( get_bloginfo ( 'language' ), 0, 2 ); 23 | 24 | wp_enqueue_script( 25 | 'google-recaptcha', 26 | "https://www.google.com/recaptcha/api.js?hl=" . $locale, 27 | array( 'gutenberg-forms-google-recaptcha' ), 28 | '', 29 | true 30 | ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/scripts/google_recaptcha.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | function gutenbergFormsRecaptchaLoad() { 3 | // recaptcha required form elements 4 | $(".cwp-form").each(function () { 5 | // checking if the current form has recaptcha enabled 6 | const is_recaptcha_enabled = $(this).data("recaptchaenable"); 7 | 8 | if (is_recaptcha_enabled) { 9 | const form = $(this)[0]; // current form 10 | const sitekey = $(this).data("siteKey"); 11 | 12 | grecaptcha.render(form, { sitekey }); 13 | } 14 | }); 15 | } 16 | 17 | window.gutenbergFormsRecaptchaLoad = gutenbergFormsRecaptchaLoad; // making it available globally 18 | }); 19 | -------------------------------------------------------------------------------- /config/externals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility methods for use when generating build configuration objects. 3 | */ 4 | const { join } = require( 'path' ); 5 | 6 | /** 7 | * Given a string, returns a new string with dash separators converted to 8 | * camel-case equivalent. This is not as aggressive as `_.camelCase`, which 9 | * which would also upper-case letters following numbers. 10 | * 11 | * @param {string} string Input dash-delimited string. 12 | * 13 | * @return {string} Camel-cased string. 14 | */ 15 | const camelCaseDash = string => string.replace( /-([a-z])/g, ( match, letter ) => letter.toUpperCase() ); 16 | 17 | /** 18 | * Define externals to load components through the wp global. 19 | */ 20 | const externals = [ 21 | 'components', 22 | 'api-fetch', 23 | 'edit-post', 24 | 'element', 25 | 'plugins', 26 | 'editor', 27 | 'block-editor', 28 | 'blocks', 29 | 'hooks', 30 | 'utils', 31 | 'date', 32 | 'data', 33 | 'i18n', 34 | ].reduce( 35 | ( externals, name ) => ( { 36 | ...externals, 37 | [ `@wordpress/${ name }` ]: `wp.${ camelCaseDash( name ) }`, 38 | } ), 39 | { 40 | wp: 'wp', 41 | ga: 'ga', // Old Google Analytics. 42 | gtag: 'gtag', // New Google Analytics. 43 | react: 'React', // React itself is there in Gutenberg. 44 | jquery: 'jQuery', // import $ from 'jquery'; // Use jQuery from WP after enqueuing it. 45 | 'react-dom': 'ReactDOM', 46 | lodash: 'lodash', // Lodash is there in Gutenberg. 47 | cgbGlobal: 'cgbGlobal', // import globals from 'cgbGlobal'; // Localized data. 48 | } 49 | ); 50 | 51 | module.exports = externals; 52 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Paths 3 | * 4 | * Project related paths. 5 | */ 6 | 7 | const path = require( 'path' ); 8 | const fs = require( 'fs' ); 9 | 10 | // Make sure any symlinks in the project folder are resolved: 11 | const pluginDir = fs.realpathSync( process.cwd() ); 12 | const resolvePlugin = relativePath => path.resolve( pluginDir, relativePath ); 13 | 14 | // Config after eject: we're in ./config/ 15 | module.exports = { 16 | dotenv: resolvePlugin( '.env' ), 17 | pluginSrc: resolvePlugin( 'src' ), // Plugin src folder path. 18 | pluginBlocksJs: resolvePlugin( 'src/blocks.js' ), 19 | yarnLockFile: resolvePlugin( 'yarn.lock' ), 20 | pluginDist: resolvePlugin( '.' ), // We are in ./dist folder already so the path '.' resolves to ./dist/. 21 | }; 22 | -------------------------------------------------------------------------------- /controllers/forms/forms.controller.php: -------------------------------------------------------------------------------- 1 | namespace = "/gutenberg-forms/forms/" . self::version; 18 | $this->resource_name = 'posts'; 19 | } 20 | 21 | /** 22 | * All rest route required for the current controller will be 23 | * registered in the function below 24 | */ 25 | 26 | public function register_routes() { 27 | $sub_controllers = [ 28 | new cwp_gf_Forms_Import_controller( $this->namespace ), 29 | ]; 30 | 31 | foreach ( $sub_controllers as $sub_controller ) { 32 | // registering routes for each sub controller 33 | $sub_controller->register_routes(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /controllers/index.php: -------------------------------------------------------------------------------- 1 | register_routes(); 14 | } ); 15 | 16 | add_filter( 'rest_query_vars', function ( $vars ) { 17 | $extra_filters = array( 'post', 'post__in', 'type', 'id' ); 18 | 19 | $vars[] = 'meta_query'; 20 | 21 | return array_merge( $vars, $extra_filters ); 22 | } ); 23 | -------------------------------------------------------------------------------- /dist/admin/index.asset.php: -------------------------------------------------------------------------------- 1 | array('lodash', 'react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => 'c7a2479844e25a67ae1625d073946f7f'); -------------------------------------------------------------------------------- /gutenberg-forms.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 |
7 |

Activate

8 |

Click on the enable toggle to activate Google ReCaptcha.

9 | 10 | 11 | 12 |
13 | 14 |

API Key(s)

15 |

16 | In order to get started and protect your form from spamming. You must provide the following keys 17 |

    18 |
  1. Site Key
  2. 19 |
  3. Client Secret
  4. 20 |
21 |

22 |
23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | 32 |

How to use?

33 |

34 | 35 | After completing all the steps, You can now navigate to the page where you want to use Gutenberg Forms 36 | . You can now see a ReCaptcha v2 toggle under the Spam Protection Panel. 37 |

38 | 39 |
-------------------------------------------------------------------------------- /lib/block/api/index.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash'; 2 | import { DATA_PROXY, LIBRARY_PROXY } from "../constants"; 3 | export function getCatagories() { 4 | return new Promise((resolve, reject) => { 5 | fetch(DATA_PROXY, { 6 | method: 'GET' 7 | }).then(res => res.json()).then(data => { 8 | let records = data.records, 9 | fields = get(records, '[0].fields'), 10 | Catagories = get(fields, 'Catagories'); 11 | resolve(Catagories); 12 | }).catch(err => { 13 | reject(err); 14 | }); 15 | }); 16 | } 17 | export function getTemplates() { 18 | return new Promise((resolve, reject) => { 19 | fetch(LIBRARY_PROXY, { 20 | method: 'GET' 21 | }).then(res => res.json()).then(data => { 22 | let records = get(data, 'records'); 23 | resolve(records); 24 | }).catch(err => { 25 | reject(err); 26 | }); 27 | }); 28 | } -------------------------------------------------------------------------------- /lib/block/block.js: -------------------------------------------------------------------------------- 1 | import "./editor.scss"; 2 | import "./style.scss"; 3 | import { applyFormStyles } from "./formStyles/index"; 4 | import { registerFieldStyles } from "./fieldStyles/index"; 5 | import { myAttrs } from "../constants"; 6 | applyFormStyles("cwp/block-gutenberg-forms"); //registering styles 7 | 8 | registerFieldStyles(myAttrs); //registering field styles 9 | -------------------------------------------------------------------------------- /src/_partials/_extend_fields.scss: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | * This file contains all the @mixins scss / css of elements 4 | * that will extend fields 5 | * Like Prefix, Suffix etc 6 | 7 | **/ 8 | 9 | @mixin InputElement() { 10 | /** 11 | 12 | This mixin will be applied to both input element ( prefix & suffix ) 13 | @params {$type} can either be prefix or suffix 14 | 15 | **/ 16 | display: table-cell; 17 | padding: 0px 15px; 18 | width: 1%; 19 | } 20 | 21 | @mixin Suffix() { 22 | @include InputElement(); 23 | 24 | &.outside { 25 | border-top: 1px solid; 26 | border-bottom: 1px solid; 27 | border-right: 1px solid; 28 | border-left: none !important; 29 | } 30 | 31 | &.inside { 32 | border: none !important; 33 | position: absolute; 34 | top: 0; 35 | right: 0; 36 | height: 100%; 37 | display: flex; 38 | align-items: center; 39 | width: auto !important; 40 | } 41 | } 42 | 43 | @mixin Prefix() { 44 | @include InputElement(); 45 | 46 | &.outside { 47 | border-top: 1px solid; 48 | border-bottom: 1px solid; 49 | border-left: 1px solid; 50 | border-right: none !important; 51 | } 52 | 53 | &.inside { 54 | border: none !important; 55 | position: absolute; 56 | top: 0; 57 | left: 0; 58 | height: 100%; 59 | display: flex; 60 | align-items: center; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/_partials/_keyframes.scss: -------------------------------------------------------------------------------- 1 | @keyframes slideInLeft { 2 | 0% { 3 | -webkit-transform: translate3d(-100%, 0, 0); 4 | transform: translate3d(-100%, 0, 0); 5 | visibility: visible; 6 | } 7 | to { 8 | -webkit-transform: translateZ(0); 9 | transform: translateZ(0); 10 | } 11 | } 12 | @keyframes slideInRight { 13 | 0% { 14 | -webkit-transform: translate3d(100%, 0, 0); 15 | transform: translate3d(100%, 0, 0); 16 | visibility: visible; 17 | } 18 | to { 19 | -webkit-transform: translateZ(0); 20 | transform: translateZ(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/_partials/_pre_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin ImagePreview() { 2 | & .cwp-img { 3 | position: relative; 4 | margin: 26px 0px; 5 | 6 | & img { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | & .cwp-close-image { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | 16 | & button { 17 | &:nth-child(1) { 18 | margin: 0px 3px 0px 0px; 19 | } 20 | 21 | &:nth-child(2) { 22 | margin: 0px 0px 0px 3px; 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | 30 | @mixin bulk_add() { 31 | & .cwp-bulk-add { 32 | & .cwp-save { 33 | display: flex; 34 | justify-content: flex-end; 35 | 36 | & button:nth-child(1) { 37 | margin: 0px 10px 0px 0px; 38 | } 39 | 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/_partials/_variables.scss: -------------------------------------------------------------------------------- 1 | $primaryColor: #007cba; 2 | $secondaryColor: #eee; 3 | $danger: red; 4 | -------------------------------------------------------------------------------- /src/block/api/index.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash' 2 | import { DATA_PROXY, LIBRARY_PROXY } from "../constants" 3 | 4 | export function getCatagories() { 5 | 6 | return new Promise((resolve, reject) => { 7 | 8 | 9 | fetch(DATA_PROXY, { 10 | method: 'GET' 11 | }) 12 | .then((res) => res.json()) 13 | .then((data) => { 14 | 15 | let records = data.records, 16 | fields = get(records, '[0].fields'), 17 | Catagories = get(fields, 'Catagories'); 18 | 19 | resolve(Catagories); 20 | 21 | }) 22 | .catch(err => { 23 | reject(err); 24 | }); 25 | 26 | }) 27 | 28 | } 29 | 30 | export function getTemplates() { 31 | 32 | return new Promise((resolve, reject) => { 33 | 34 | fetch(LIBRARY_PROXY, { 35 | method: 'GET' 36 | }) 37 | .then(res => res.json()) 38 | .then((data) => { 39 | 40 | let records = get(data, 'records'); 41 | 42 | resolve(records); 43 | 44 | }) 45 | .catch(err => { 46 | reject(err); 47 | }) 48 | 49 | }); 50 | 51 | } -------------------------------------------------------------------------------- /src/block/block.js: -------------------------------------------------------------------------------- 1 | import "./editor.scss"; 2 | import "./style.scss"; 3 | 4 | import { applyFormStyles } from "./formStyles/index"; 5 | import { registerFieldStyles } from "./fieldStyles/index"; 6 | import { myAttrs } from "../constants"; 7 | 8 | applyFormStyles("cwp/block-gutenberg-forms"); //registering styles 9 | registerFieldStyles(myAttrs); //registering field styles 10 | -------------------------------------------------------------------------------- /src/block/components/datepicker.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import Pikaday from "pikaday"; 3 | 4 | function Datepicker(props) { 5 | const inputField = React.useRef(); 6 | 7 | useEffect(() => { 8 | let datePicker = new Pikaday({ 9 | field: inputField.current, 10 | onSelect: date => { 11 | props.setAttributes({ 12 | placeholder: datePicker.toString() 13 | }); 14 | }, 15 | format: props.format, 16 | toString(date, format) { 17 | // you should do formatting based on the passed format, 18 | // but we will just return 'D/M/YYYY' for simplicity 19 | const day = date.getDate(); 20 | const month = date.getMonth() + 1; 21 | const year = date.getFullYear(); 22 | 23 | if (format === "DD/MM/YYYY") { 24 | return `${day}/${month}/${year}`; 25 | } else if (format === "MM/DD/YYYY") { 26 | return `${month}/${day}/${year}`; 27 | } else { 28 | return `${year}/${month}/${day}`; 29 | } 30 | } 31 | }); 32 | }, []); 33 | 34 | return ( 35 | 44 | ); 45 | } 46 | 47 | export default Datepicker; 48 | -------------------------------------------------------------------------------- /src/block/components/formulaBuilder.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import React, { useEffect, useRef, Fragment } from "react"; 3 | import { 4 | Button, 5 | DropdownMenu, 6 | MenuGroup, 7 | MenuItem, 8 | } from "@wordpress/components"; 9 | import { getSiblings } from "../functions"; 10 | import { getFieldIcon } from "../misc/helper"; 11 | import { map } from "lodash"; 12 | import $ from "jquery"; 13 | 14 | function FormulaBuilder(prop) { 15 | const area = useRef(); 16 | 17 | const props = prop.data; 18 | 19 | const { clientId } = props, 20 | { formula } = props.attributes; 21 | 22 | const addFieldId = (name) => { 23 | var $txt = $(area.current); 24 | var caretPos = $txt[0].selectionStart; 25 | var textAreaTxt = $txt.val(); 26 | var txtToAdd = `{{${name}}}`; 27 | 28 | const val = 29 | textAreaTxt.substring(0, caretPos) + 30 | txtToAdd + 31 | textAreaTxt.substring(caretPos); 32 | 33 | props.setAttributes({ formula: val }); 34 | }; 35 | 36 | return ( 37 |
38 |
39 |

{__('Available Number Fields:', 'forms-gutenberg')}

40 | 41 | {({ onClose }) => ( 42 | 43 | 44 | {map(getSiblings(clientId, "cwp/number"), (field) => { 45 | const { field_name, label } = field; 46 | 47 | return ( 48 | { 51 | onClose(); 52 | addFieldId(field_name); 53 | }} 54 | > 55 | 59 | 60 | ); 61 | })} 62 | 63 | 64 | )} 65 | 66 |
67 | 72 |
73 | ); 74 | } 75 | 76 | export default FormulaBuilder; 77 | -------------------------------------------------------------------------------- /src/block/components/imagePreview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon, Button } from "@wordpress/components"; 3 | import ImageUpload from "../components/imageUpload"; 4 | 5 | function ImagePreview(props) { 6 | const { url, height, width } = props.image, //with dimensions; 7 | dimensions = { 8 | height, 9 | width 10 | }; 11 | 12 | const { isSelected } = props; 13 | 14 | return ( 15 |
16 |
17 | {isSelected && ( 18 |
19 | 22 | props.onEdit(img)} 26 | /> 27 |
28 | )} 29 | 30 |
31 |
32 | ); 33 | } 34 | 35 | export default ImagePreview; 36 | -------------------------------------------------------------------------------- /src/block/components/imageUpload.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Icon } from "@wordpress/components"; 3 | import { has } from "lodash"; 4 | const { MediaUploadCheck, MediaUpload } = wp.blockEditor; 5 | 6 | function ImageUpload(props) { 7 | const { image } = props; 8 | 9 | let value = image ? image : ""; 10 | 11 | function getLeastOneSize(s, media) { 12 | let { sizes } = media; 13 | 14 | for (const size of s) { 15 | if (has(sizes, size)) { 16 | return sizes[size]; 17 | break; 18 | } else continue; 19 | } 20 | } 21 | 22 | return ( 23 | 24 | { 26 | let sizes = ["thumbnail", "medium", "large"], 27 | imageWithDimensions = getLeastOneSize(sizes, media); 28 | 29 | props.onSelect(imageWithDimensions); 30 | }} 31 | allowedTypes={["image"]} 32 | value={value} 33 | render={({ open }) => ( 34 | 37 | )} 38 | /> 39 | 40 | ); 41 | } 42 | export default ImageUpload; 43 | -------------------------------------------------------------------------------- /src/block/components/tagList.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import React, { useEffect } from "react"; 3 | import { MenuGroup, MenuItem } from "@wordpress/components"; 4 | import { 5 | getFieldsTags, 6 | getWordpressTags, 7 | getFormTags, 8 | getOtherTags, 9 | getMetaTags, 10 | } from "../functions"; 11 | import { get, isEmpty, has } from "lodash"; 12 | 13 | function TagList(props) { 14 | const { onSelect, data } = props; 15 | 16 | const mapList = (list) => { 17 | return !isEmpty(list) 18 | ? list.map((tag, index) => { 19 | const title = get(tag, "title"); 20 | const Tag = get(tag, "tag"); 21 | 22 | return ( 23 | onSelect(Tag)}> 24 | {title} 25 | {Tag} 26 | 27 | ); 28 | }) 29 | : null; 30 | }; 31 | 32 | const getList = () => { 33 | const requiredList = props.list; 34 | 35 | switch (requiredList) { 36 | case "fields": 37 | let fieldsTagList = isEmpty(data) 38 | ? getFieldsTags(props.clientId, false) 39 | : data; 40 | 41 | return fieldsTagList.map((field, index) => { 42 | const id = get(field, "field_id"); 43 | 44 | const label = get(field, "fieldName"); 45 | const tag = `{{${id}}}`; 46 | 47 | return ( 48 | onSelect(tag)}> 49 | {isEmpty(label) ? __("No Label", 'forms-gutenberg') : label} 50 | {tag} 51 | 52 | ); 53 | }); 54 | case "wordpress": 55 | const wpTags = isEmpty(data) ? getWordpressTags() : data; 56 | return mapList(wpTags); 57 | case "form": 58 | const formTags = isEmpty(data) ? getFormTags() : data; 59 | return mapList(formTags); 60 | case "meta": 61 | const metaTags = isEmpty(data) ? getMetaTags() : data; 62 | return mapList(metaTags); 63 | case "other": 64 | const otherTags = isEmpty(data) ? getOtherTags() : data; 65 | return mapList(otherTags); 66 | } 67 | }; 68 | 69 | const noStyling = has(props, "noStyling"); 70 | const classes = noStyling ? "no-styling" : ""; 71 | 72 | const listToMap = getList(); 73 | 74 | return ( 75 |
76 | {!isEmpty(listToMap) ? ( 77 | {listToMap} 78 | ) : ( 79 |
80 |

{__('No Tags Found !', 'forms-gutenberg')}

81 |
82 | )} 83 |
84 | ); 85 | } 86 | 87 | export default TagList; 88 | -------------------------------------------------------------------------------- /src/block/components/tagSelector.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import React, { useState, useEffect } from "react"; 3 | import { TextControl, TabPanel } from "@wordpress/components"; 4 | import { TEXT_DOMAIN } from "../constants"; 5 | import { isEmpty } from "lodash"; 6 | import TagList from "./tagList"; 7 | import SearchTags from "./searchTags"; 8 | 9 | function TagSelector(props) { 10 | const { setAttributes } = props; 11 | const [search, setSearch] = useState(""); 12 | 13 | const tabs = [ 14 | { 15 | name: "fields", 16 | title: __("Fields", "forms-gutenberg"), 17 | className: "cwp-tag-button", 18 | }, 19 | { 20 | name: "wordpress", 21 | title: __("Wordpress", "forms-gutenberg"), 22 | className: "cwp-tag-button", 23 | }, 24 | { 25 | name: "form", 26 | title: __("Form", "forms-gutenberg"), 27 | className: "cwp-tag-button", 28 | }, 29 | { 30 | name: "meta", 31 | title: __("Meta", "forms-gutenberg"), 32 | className: "cwp-tag-button", 33 | }, 34 | { 35 | name: "other", 36 | title: __("Other", "forms-gutenberg"), 37 | className: "cwp-tag-button", 38 | }, 39 | ]; 40 | 41 | return ( 42 |
43 |
44 | 49 |
50 |
51 | {isEmpty(search) ? ( 52 | 57 | {(tab) => { 58 | return ( 59 | 64 | ); 65 | }} 66 | 67 | ) : ( 68 | 73 | )} 74 |
75 |
76 | ); 77 | } 78 | 79 | export default TagSelector; 80 | -------------------------------------------------------------------------------- /src/block/constants/index.js: -------------------------------------------------------------------------------- 1 | export const TEXT_DOMAIN = "forms-gutenberg"; // text-domain 2 | 3 | 4 | 5 | //? some endpoints 6 | export const LIBRARY_PROXY = "https://raw.githubusercontent.com/ZafarKamal123/gutenberg-forms-templates/master/library.json"; 7 | export const DATA_PROXY = "https://raw.githubusercontent.com/ZafarKamal123/gutenberg-forms-templates/master/data.json"; 8 | -------------------------------------------------------------------------------- /src/block/fieldStyles/index.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { each, isEqual } from "lodash"; 3 | const { registerBlockStyle } = wp.blocks; 4 | 5 | let fieldStyles = [ 6 | { 7 | name: "inline", 8 | label: __("Inline", "forms-gutenberg"), 9 | } 10 | ]; 11 | 12 | const radioFieldStyling = [ 13 | { 14 | name: "button", 15 | label: __("Button", "forms-gutenberg"), 16 | } 17 | ]; 18 | 19 | export function registerFieldStyles(fields) { 20 | let prefix = "cwp/"; // our block prefix 21 | 22 | each(fields, field => { 23 | let slug = prefix.concat(field); // example => "cwp/name" 24 | 25 | if (isEqual(slug, "cwp/checkbox") || isEqual(slug, "cwp/radio")) 26 | registerBlockStyle(slug, radioFieldStyling); 27 | // styling only for radio and checkbox fields 28 | else registerBlockStyle(slug, fieldStyles); //registering style with the specified field slug 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/block/formStyles/index.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { each } from "lodash"; 3 | const { registerBlockStyle } = wp.blocks; 4 | 5 | const formStyles = [ 6 | { 7 | name: "paper", 8 | label: __("Paper", "forms-gutenberg"), 9 | } 10 | ]; 11 | 12 | export let applyFormStyles = slug => { 13 | each(formStyles, style => { 14 | registerBlockStyle(slug, style); //?iterating through each style & registering it 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/block/style.scss: -------------------------------------------------------------------------------- 1 | .cwp-hint { 2 | font-weight: 300; 3 | font-size: 1.5rem; 4 | } 5 | -------------------------------------------------------------------------------- /src/blocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gutenberg Blocks 3 | * 4 | * All blocks related JavaScript files should be imported here. 5 | * You can create a new block folder in this dir and include code 6 | * for that block here as well. 7 | * 8 | * All blocks should be included here since this is the file that 9 | * Webpack is compiling as the input file. 10 | */ 11 | 12 | import "./block/block.js"; 13 | 14 | /** 15 | * Main Gutenberg Forms block 16 | */ 17 | import "./blocks/gutenberg-forms/index.js"; 18 | 19 | /** 20 | * Inner blocks 21 | */ 22 | 23 | import "./blocks/reusable_forms/index.js"; 24 | import "./blocks/text/index.js"; 25 | import "./blocks/number/index.js"; 26 | import "./blocks/checkbox/index.js"; 27 | import "./blocks/select/index.js"; 28 | import "./blocks/datepicker/index.js"; 29 | import "./blocks/message/index.js"; 30 | import "./blocks/email/index.js"; 31 | import "./blocks/form-button/index.js"; 32 | import "./blocks/name/index.js"; 33 | import "./blocks/phone/index.js"; 34 | import "./blocks/radio/index.js"; 35 | import "./blocks/website/index.js"; 36 | import "./blocks/yes-no/index.js"; 37 | import "./blocks/hidden/index.js"; 38 | import "./blocks/file_upload/index.js"; 39 | import "./blocks/progress/index.js"; 40 | import "./blocks/form-calculation/index.js"; 41 | import "./blocks/column/index.js"; 42 | import "./blocks/form-column/index.js"; 43 | import "./blocks/form-group/index.js"; 44 | 45 | import "./blocks/form-steps/root/index.js"; 46 | import "./blocks/form-steps/childs/form-step/index.js"; 47 | 48 | /** 49 | * extended index 50 | */ 51 | 52 | import "./extend/index.js"; 53 | -------------------------------------------------------------------------------- /src/blocks/checkbox/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Checkbox", 3 | "attributes": { 4 | "isRequired": { 5 | "type": "boolean", 6 | "default": false 7 | }, 8 | "options": { 9 | "type": "array", 10 | "default": [ 11 | { 12 | "label": "Option 1" 13 | } 14 | ] 15 | }, 16 | "enableCondition": { 17 | "type": "boolean", 18 | "default": false 19 | }, 20 | "label": { 21 | "type": "string", 22 | "default": "Choose One" 23 | }, 24 | "id": { 25 | "type": "string", 26 | "default": "" 27 | }, 28 | "field_name": { 29 | "type": "string", 30 | "default": "" 31 | }, 32 | "messages": { 33 | "type": "object", 34 | "default": { 35 | "empty": "Please select atleast one checkbox!" 36 | } 37 | }, 38 | "condition": { 39 | "type": "object", 40 | "default": { 41 | "field": null, 42 | "condition": "===", 43 | "value": "" 44 | } 45 | }, 46 | "requiredLabel": { 47 | "type": "string", 48 | "default": "*" 49 | }, 50 | "fieldStyle": { 51 | "type": "string", 52 | "default": "block" 53 | }, 54 | "bulkAdd": { 55 | "type": "boolean", 56 | "default": false 57 | }, 58 | "adminId": { 59 | "type": "object", 60 | "default": { 61 | "default": "", 62 | "value": "" 63 | } 64 | }, 65 | "showHint" :{ 66 | "type": "boolean", 67 | "default": false 68 | }, 69 | "hint": { 70 | "type": "string", 71 | "default": "" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/blocks/checkbox/deprecated.js: -------------------------------------------------------------------------------- 1 | import blockData from "./block.json"; 2 | 3 | export const deprecated = [ 4 | { 5 | attributes: { 6 | ...blockData.attributes, 7 | }, 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /src/blocks/checkbox/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | import { fieldParents, myAttrs } from "../../constants.js"; 4 | import { getFieldTransform } from "../../block/functions"; 5 | import checkboxEdit from "./edit.js"; 6 | import checkboxSave from "./save.js"; 7 | 8 | import blockData from "./block.json"; 9 | 10 | const { title, attributes } = blockData; 11 | 12 | registerBlockType("cwp/checkbox", { 13 | title: __(title), 14 | icon: "yes", 15 | category: "gutenberg-forms", 16 | keywords: [__("gutenberg-forms"), __("forms"), __("checkbox")], 17 | edit: checkboxEdit, 18 | save: checkboxSave, 19 | attributes, 20 | transforms: { 21 | from: [ 22 | { 23 | type: "block", 24 | blocks: myAttrs.map((block) => "cwp/".concat(block)), 25 | transform: (a) => getFieldTransform(a, "checkbox"), 26 | }, 27 | ], 28 | }, 29 | parent: fieldParents, 30 | }); 31 | -------------------------------------------------------------------------------- /src/blocks/column/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Column", 3 | "attributes": { 4 | "width": { 5 | "type": "number", 6 | "default": 100 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/blocks/column/edit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RangeControl, PanelBody } from '@wordpress/components'; 3 | import { hasChildBlocks } from '../../block/functions'; 4 | 5 | const { InspectorControls, InnerBlocks } = wp.blockEditor; 6 | const { __ } = wp.i18n; 7 | const $ = jQuery; 8 | 9 | function edit( props ) { 10 | const { width } = props.attributes; 11 | const { setAttributes, clientId } = props; 12 | 13 | const updateAttribute = ( key, value ) => { 14 | const currentBlockId = '#block-'.concat( clientId ); 15 | const currentBlockElement = $( currentBlockId ); 16 | 17 | if ( key === 'width' && currentBlockElement.length ) { 18 | const widthInPercentage = String( value ).concat( '%' ); 19 | currentBlockElement.css( 'flex-basis', widthInPercentage ); // updating the dom width 20 | } 21 | 22 | setAttributes( { 23 | [ key ]: value, 24 | } ); 25 | }; 26 | 27 | return [ 28 | // eslint-disable-next-line react/jsx-key 29 |
30 | 37 | } 38 | /> 39 |
, 40 | null, 41 | !! props.isSelected && ( 42 | 43 | 47 | updateAttribute( 'width', newWidth ) } 51 | /> 52 | 53 | 54 | ), 55 | ]; 56 | } 57 | 58 | export default edit; 59 | -------------------------------------------------------------------------------- /src/blocks/column/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import columnEdit from "./edit.js"; 5 | import columnSave from "./save.js"; 6 | 7 | import blockData from "./block.json"; 8 | 9 | // Child block for the form-column block for creating layouts 10 | 11 | const { title, attributes } = blockData; 12 | 13 | registerBlockType("cwp/column", { 14 | title: __(title), 15 | icon: "editor-table", 16 | category: "gutenberg-forms", 17 | keywords: [ 18 | __("gutenberg-forms"), 19 | __("forms"), 20 | __("form-column"), 21 | __("column"), 22 | ], 23 | edit: columnEdit, 24 | save: columnSave, 25 | attributes, 26 | parent: ["cwp/form-column"], 27 | }); 28 | -------------------------------------------------------------------------------- /src/blocks/column/save.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { InnerBlocks } from '@wordpress/block-editor'; 3 | 4 | function save( props ) { 5 | const width = String( props.attributes.width ).concat( '%' ); 6 | 7 | const styling = { 8 | flexBasis: width, 9 | }; 10 | 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | 18 | export default save; 19 | -------------------------------------------------------------------------------- /src/blocks/components/bulk_add.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { TextareaControl, Button } from "@wordpress/components"; 3 | const { __ } = wp.i18n; 4 | 5 | function bulk_add(prop) { 6 | 7 | const props = prop.data; 8 | const { options } = props.attributes; 9 | 10 | const [bulkText, setBulkText] = useState(""); 11 | 12 | const getValue = () => { 13 | 14 | const labels = options.map(v => v.label), 15 | value = labels.join("\n"); 16 | 17 | return value; 18 | } 19 | 20 | const handleSave = () => { 21 | 22 | let values = bulkText.split("\n"); 23 | let newOption = values.map(o => { 24 | return { 25 | label: o, 26 | checked: false 27 | } 28 | }); 29 | 30 | props.setAttributes({ options: newOption, bulkAdd: false }); 31 | prop.onChange(newOption); 32 | } 33 | 34 | 35 | const handleCancel = () => { 36 | props.setAttributes({ bulkAdd: false }); 37 | } 38 | 39 | useEffect(() => { 40 | 41 | setBulkText(getValue()); 42 | 43 | }, []) 44 | 45 | return ( 46 |
47 | 52 |
53 | 54 | 55 |
56 |
57 | ) 58 | } 59 | 60 | 61 | export default bulk_add; 62 | -------------------------------------------------------------------------------- /src/blocks/components/prefix.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { get, isEmpty } from "lodash"; 3 | 4 | /** 5 | * This component will be used before any input field ( can either be inside the input or outside ) 6 | * for example Prefix [ Prefix Input_field Suffix ] Suffix 7 | */ 8 | 9 | function Prefix(props) { 10 | const position = get(props.prefix, "position"); 11 | 12 | const conditionalClass = isEmpty(position) ? "" : position; 13 | const className = "cwp-prefix cwp-field-prefix cwp-field-element ".concat( 14 | conditionalClass 15 | ); 16 | 17 | return
{props.children}
; 18 | } 19 | 20 | export default Prefix; 21 | -------------------------------------------------------------------------------- /src/blocks/components/suffix.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { get, isEmpty } from "lodash"; 3 | 4 | /** 5 | * This component will be used after any input field ( can either be inside the input or outside ) 6 | * for example Prefix [ Prefix Input_field Suffix ] Suffix 7 | */ 8 | 9 | function Suffix(props) { 10 | const position = get(props.suffix, "position"); 11 | const conditionalClass = isEmpty(position) ? "" : position; 12 | const className = "cwp-suffix cwp-field-suffix cwp-field-element ".concat( 13 | conditionalClass 14 | ); 15 | 16 | return
{props.children}
; 17 | } 18 | 19 | export default Suffix; 20 | -------------------------------------------------------------------------------- /src/blocks/datepicker/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Date Picker", 3 | "attributes": { 4 | "enableCondition": { 5 | "type": "boolean", 6 | "default": false 7 | }, 8 | "isRequired": { 9 | "type": "boolean", 10 | "default": false 11 | }, 12 | "label": { 13 | "type": "string", 14 | "default": "Pick Date" 15 | }, 16 | "placeholder": { 17 | "type": "string", 18 | "default": "" 19 | }, 20 | "id": { 21 | "type": "string", 22 | "default": "" 23 | }, 24 | "field_name": { 25 | "type": "string", 26 | "default": "" 27 | }, 28 | "requiredLabel": { 29 | "type": "string", 30 | "default": "*" 31 | }, 32 | "format": { 33 | "type": "string", 34 | "default": "DD/MM/YYYY" 35 | }, 36 | "messages": { 37 | "type": "object", 38 | "default": { 39 | "empty": "Please select date!" 40 | } 41 | }, 42 | "condition": { 43 | "type": "object", 44 | "default": { 45 | "field": null, 46 | "condition": "===", 47 | "value": "" 48 | } 49 | }, 50 | "requiredLabel": { 51 | "type": "string", 52 | "default": "*" 53 | }, 54 | "adminId": { 55 | "type": "object", 56 | "default": { 57 | "default": "", 58 | "value": "" 59 | } 60 | }, 61 | "prefix": { 62 | "type": "object", 63 | "default": { 64 | "enable": false, 65 | "content": "", 66 | "position": "outside" 67 | } 68 | }, 69 | "suffix": { 70 | "type": "object", 71 | "default": { 72 | "enable": false, 73 | "content": "", 74 | "position": "outside" 75 | } 76 | }, 77 | "showHint" :{ 78 | "type": "boolean", 79 | "default": false 80 | }, 81 | "hint": { 82 | "type": "string", 83 | "default": "" 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/blocks/datepicker/deprecated/deprecated.js: -------------------------------------------------------------------------------- 1 | import { attributes } from "../block.json"; 2 | 3 | // deprecated version 4 | 5 | import edit from "./edit"; 6 | import save from "./save"; 7 | 8 | export const deprecated = [ 9 | { 10 | attributes, 11 | edit, 12 | save, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/blocks/datepicker/deprecated/save.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * ! DEPRECATED SAVE VERSION 4 | * 5 | */ 6 | 7 | import React from "react"; 8 | import { isEmpty } from "lodash"; 9 | import { strip_tags } from "../../../block/misc/helper"; 10 | import { stringifyCondition } from "../../../block/functions"; 11 | 12 | function save(props) { 13 | const { 14 | placeholder, 15 | isRequired, 16 | label, 17 | id, 18 | requiredLabel, 19 | type, 20 | messages: { empty }, 21 | format, 22 | condition, 23 | enableCondition, 24 | } = props.attributes; 25 | 26 | const getLabel = () => { 27 | const { label, isRequired } = props.attributes; 28 | let required = !isEmpty(requiredLabel) 29 | ? `${requiredLabel}` 30 | : ""; 31 | let required_label = label + " " + required; 32 | 33 | if (isRequired) return required_label; 34 | 35 | return label; 36 | }; 37 | 38 | let getFieldType = () => { 39 | switch (type) { 40 | case "both": 41 | return "datetime-local"; 42 | case "time": 43 | return "time"; 44 | case "date": 45 | return "date"; 46 | } 47 | }; 48 | 49 | let errors = JSON.stringify({ 50 | empty, 51 | }); 52 | 53 | const getCondition = () => { 54 | if (!isEmpty(condition.field) && enableCondition) { 55 | //verifying the condition 56 | return { 57 | "data-condition": stringifyCondition(condition), 58 | }; 59 | } 60 | 61 | return {}; 62 | }; 63 | 64 | return ( 65 |
66 |
67 | {!isEmpty(label) && ( 68 | 72 | )} 73 | 89 |
90 |
91 | ); 92 | } 93 | 94 | export default save; 95 | -------------------------------------------------------------------------------- /src/blocks/datepicker/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import { getFieldTransform } from "../../block/functions"; 5 | import { fieldParents, myAttrs } from "../../constants"; 6 | import datePickerEdit from "./edit.js"; 7 | import datePickerSave from "./save.js"; 8 | import { deprecated } from "./deprecated/deprecated"; 9 | 10 | import blockData from "./block.json"; 11 | 12 | const { attributes, title } = blockData; 13 | 14 | registerBlockType("cwp/datepicker", { 15 | title: __(title), 16 | icon: "calendar-alt", 17 | category: "gutenberg-forms", 18 | keywords: [__("gutenberg-forms"), __("forms"), __("datepicker")], 19 | edit: datePickerEdit, 20 | save: datePickerSave, 21 | attributes, 22 | deprecated, 23 | transforms: { 24 | from: [ 25 | { 26 | type: "block", 27 | blocks: myAttrs.map((block) => "cwp/".concat(block)), 28 | transform: (a) => getFieldTransform(a, "datepicker"), 29 | }, 30 | ], 31 | }, 32 | parent: fieldParents, 33 | }); 34 | -------------------------------------------------------------------------------- /src/blocks/email/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Email", 3 | "attributes": { 4 | "enableCondition": { 5 | "type": "boolean", 6 | "default": false 7 | }, 8 | "email": { 9 | "type": "string", 10 | "default": "" 11 | }, 12 | "isRequired": { 13 | "type": "boolean", 14 | "default": false 15 | }, 16 | "label": { 17 | "type": "string", 18 | "default": "Email" 19 | }, 20 | "id": { 21 | "type": "string", 22 | "default": "" 23 | }, 24 | "field_name": { 25 | "type": "string", 26 | "default": "" 27 | }, 28 | "requiredLabel": { 29 | "type": "string", 30 | "default": "*" 31 | }, 32 | "messages": { 33 | "type": "object", 34 | "default": { 35 | "empty": "Please fill out this field!", 36 | "invalidEmail": "The email {{value}} is not valid!" 37 | } 38 | }, 39 | "condition": { 40 | "type": "object", 41 | "default": { 42 | "field": null, 43 | "condition": "===", 44 | "value": "" 45 | } 46 | }, 47 | "adminId": { 48 | "type": "object", 49 | "default": { 50 | "default": "", 51 | "value": "" 52 | } 53 | }, 54 | "prefix": { 55 | "type": "object", 56 | "default": { 57 | "enable": false, 58 | "content": "", 59 | "position": "outside" 60 | } 61 | }, 62 | "suffix": { 63 | "type": "object", 64 | "default": { 65 | "enable": false, 66 | "content": "", 67 | "position": "outside" 68 | } 69 | }, 70 | "showHint" :{ 71 | "type": "boolean", 72 | "default": false 73 | }, 74 | "hint": { 75 | "type": "string", 76 | "default": "" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/blocks/email/deprecated/deprecation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Handling the deprecation 4 | */ 5 | 6 | import blockData from "../block.json"; 7 | import edit from "./edit"; 8 | import save from "./save"; 9 | 10 | const { attributes } = blockData; 11 | 12 | export const deprecation = [ 13 | { 14 | attributes, 15 | save, 16 | edit, 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /src/blocks/email/deprecated/save.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ! Deprecated Save Version 3 | */ 4 | 5 | import React from "react"; 6 | import { isEmpty } from "lodash"; 7 | import { strip_tags } from "../../../block/misc/helper"; 8 | import { stringifyCondition } from "../../../block/functions"; 9 | 10 | function save(props) { 11 | const { 12 | email, 13 | isRequired, 14 | label, 15 | id, 16 | requiredLabel, 17 | messages, 18 | messages: { invalidEmail, empty }, 19 | condition, 20 | enableCondition, 21 | adminId, 22 | } = props.attributes; 23 | 24 | const getLabel = () => { 25 | const { label, isRequired } = props.attributes; 26 | 27 | const required = !isEmpty(requiredLabel) 28 | ? `${requiredLabel}` 29 | : ""; 30 | 31 | const required_label = label + " " + required; 32 | 33 | if (isRequired) { 34 | return required_label; 35 | } 36 | 37 | return label; 38 | }; 39 | 40 | const errors = JSON.stringify({ 41 | mismatch: invalidEmail, 42 | empty, 43 | }); 44 | 45 | const getCondition = () => { 46 | if (!isEmpty(condition.field) && enableCondition) { 47 | //verifying the condition 48 | return { 49 | "data-condition": stringifyCondition(condition), 50 | }; 51 | } 52 | 53 | return {}; 54 | }; 55 | 56 | return ( 57 |
58 |
59 | {!isEmpty(label) && ( 60 | 64 | )} 65 | 77 |
78 |
79 | ); 80 | } 81 | 82 | export default save; 83 | -------------------------------------------------------------------------------- /src/blocks/email/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import emailEdit from "./edit.js"; 5 | import emailSave from "./save.js"; 6 | import { getFieldTransform } from "../../block/functions"; 7 | import { fieldParents, myAttrs } from "../../constants"; 8 | 9 | import blockData from "./block.json"; 10 | 11 | const { title, attributes } = blockData; 12 | 13 | import { deprecation } from "./deprecated/deprecation"; 14 | 15 | registerBlockType("cwp/email", { 16 | title: __(title), 17 | icon: "email", 18 | category: "gutenberg-forms", 19 | keywords: [__("gutenberg-forms"), __("forms"), __("mail")], 20 | edit: emailEdit, 21 | save: emailSave, 22 | transforms: { 23 | from: [ 24 | { 25 | type: "block", 26 | blocks: myAttrs.map((block) => "cwp/".concat(block)), 27 | transform: (a) => getFieldTransform(a, "email"), 28 | }, 29 | ], 30 | }, 31 | deprecated: deprecation, 32 | attributes, 33 | parent: fieldParents, 34 | }); 35 | -------------------------------------------------------------------------------- /src/blocks/email/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isEmpty } from "lodash"; 3 | import { strip_tags } from "../../block/misc/helper"; 4 | import { stringifyCondition } from "../../block/functions"; 5 | import Prefix from "../components/prefix"; 6 | import Suffix from "../components/suffix"; 7 | 8 | function save(props) { 9 | const { 10 | email, 11 | isRequired, 12 | label, 13 | id, 14 | requiredLabel, 15 | messages, 16 | messages: { invalidEmail, empty }, 17 | condition, 18 | enableCondition, 19 | adminId, 20 | prefix, 21 | suffix, 22 | hint, 23 | showHint 24 | } = props.attributes; 25 | 26 | const getLabel = () => { 27 | const { label, isRequired } = props.attributes; 28 | 29 | const required = !isEmpty(requiredLabel) 30 | ? `${requiredLabel}` 31 | : ""; 32 | 33 | const required_label = label + " " + required; 34 | 35 | if (isRequired) { 36 | return required_label; 37 | } 38 | 39 | return label; 40 | }; 41 | 42 | const errors = JSON.stringify({ 43 | mismatch: invalidEmail, 44 | empty, 45 | }); 46 | 47 | const getCondition = () => { 48 | if (!isEmpty(condition.field) && enableCondition) { 49 | //verifying the condition 50 | return { 51 | "data-condition": stringifyCondition(condition), 52 | }; 53 | } 54 | 55 | return {}; 56 | }; 57 | 58 | return ( 59 |
60 |
61 | {!isEmpty(label) && ( 62 | 66 | )} 67 |
68 | {prefix.enable && ( 69 | 70 | 71 | 72 | )} 73 | 74 | 86 | {suffix.enable && ( 87 | 88 | 89 | 90 | )} 91 |
92 |
93 | {showHint && ( 94 |

{hint}

95 | )} 96 |
97 | ); 98 | } 99 | 100 | export default save; 101 | -------------------------------------------------------------------------------- /src/blocks/file_upload/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "File", 3 | "attributes": { 4 | "enableCondition": { 5 | "type": "boolean", 6 | "default": false 7 | }, 8 | "file": { 9 | "type": "string", 10 | "default": "" 11 | }, 12 | "isRequired": { 13 | "type": "boolean", 14 | "default": false 15 | }, 16 | "label": { 17 | "type": "string", 18 | "default": "Select File" 19 | }, 20 | "id": { 21 | "type": "string", 22 | "default": "" 23 | }, 24 | "field_name": { 25 | "type": "string", 26 | "default": "" 27 | }, 28 | "messages": { 29 | "type": "object", 30 | "default": { 31 | "empty": "Please select a file", 32 | "invalid": "The file {{value}} is not valid!" 33 | } 34 | }, 35 | "condition": { 36 | "type": "object", 37 | "default": { 38 | "field": null, 39 | "condition": "===", 40 | "value": "" 41 | } 42 | }, 43 | "requiredLabel": { 44 | "type": "string", 45 | "default": "*" 46 | }, 47 | "allowedFormats": { 48 | "type": "string", 49 | "default": [ 50 | "jpg", 51 | "jpeg", 52 | "png", 53 | "gif", 54 | "pdf", 55 | "doc", 56 | "docx", 57 | "ppt", 58 | "pptx", 59 | "odt", 60 | "avi", 61 | "ogg", 62 | "m4a", 63 | "mov", 64 | "mp3", 65 | "mp4", 66 | "mpg", 67 | "wav", 68 | "wmv" 69 | ] 70 | }, 71 | "adminId": { 72 | "type": "object", 73 | "default": { 74 | "default": "", 75 | "value": "" 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/blocks/file_upload/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import { fieldParents } from "../../constants"; 5 | 6 | import fileUploadEdit from "./edit.js"; 7 | import fileUploadSave from "./save.js"; 8 | 9 | import blockData from "./block.json"; 10 | 11 | const { title, attributes } = blockData; 12 | 13 | registerBlockType("cwp/file-upload", { 14 | title: __(title), 15 | icon: "media-document", 16 | category: "gutenberg-forms", 17 | keywords: [__("gutenberg-forms"), __("forms"), __("file"), __("file upload")], 18 | edit: fileUploadEdit, 19 | save: fileUploadSave, 20 | attributes, 21 | supports: { 22 | align: true, 23 | align: ["wide", "full", "center"], 24 | }, 25 | parent: fieldParents, 26 | }); 27 | -------------------------------------------------------------------------------- /src/blocks/file_upload/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isEmpty } from "lodash"; 3 | import { strip_tags } from "../../block/misc/helper"; 4 | import { stringifyCondition } from "../../block/functions"; 5 | 6 | function save(props) { 7 | const { 8 | file, 9 | isRequired, 10 | label, 11 | id, 12 | requiredLabel, 13 | messages: { empty, invalid }, 14 | condition, 15 | allowedFormats 16 | } = props.attributes; 17 | 18 | const getLabel = () => { 19 | const { label, isRequired } = props.attributes; 20 | 21 | let required = !isEmpty(requiredLabel) 22 | ? `${requiredLabel}` 23 | : ""; 24 | let required_label = label + " " + required; 25 | 26 | if (isRequired) return required_label; 27 | 28 | return label; 29 | }; 30 | 31 | let errors = JSON.stringify({ 32 | mismatch: invalid, 33 | empty 34 | }); 35 | 36 | const getCondition = () => { 37 | if (props.attributes.enableCondition && !isEmpty(condition.field)) { 38 | //verifying the condition 39 | return { 40 | "data-condition": stringifyCondition(condition) 41 | }; 42 | } 43 | 44 | return {}; 45 | }; 46 | const suggestions = [ 47 | "jpg", 48 | "jpeg", 49 | "png", 50 | "gif", 51 | "pdf", 52 | "doc", 53 | "docx", 54 | "ppt", 55 | "pptx", 56 | "odt", 57 | "avi", 58 | "ogg", 59 | "m4a", 60 | "mov", 61 | "mp3", 62 | "mp4", 63 | "mpg", 64 | "wav", 65 | "wmv" 66 | ] 67 | 68 | const acceptFiles = isEmpty(allowedFormats) ? suggestions.map(s => ".".concat(s)).join(",") : allowedFormats.map(s => ".".concat(s)).join(","); 69 | 70 | return ( 71 |
72 |
73 | {!isEmpty(label) && ( 74 | 78 | )} 79 | 91 |
92 |
93 | ); 94 | } 95 | 96 | export default save; 97 | -------------------------------------------------------------------------------- /src/blocks/form-button/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Button", 3 | "attributes": { 4 | "label": { 5 | "type": "string", 6 | "default": "Submit" 7 | }, 8 | "parentId": { 9 | "type": "string", 10 | "default": "" 11 | }, 12 | "action": { 13 | "default": "submit", 14 | "type": "string" 15 | }, 16 | "styling": { 17 | "type": "object", 18 | "default": { 19 | "backgroundColor": "rgb(238, 238, 238)", 20 | "color": "rgb(49, 49, 49)", 21 | "padding": 25, 22 | "borderRadius": 0 23 | } 24 | } 25 | }, 26 | "supports": { 27 | "align": true, 28 | "align": ["wide", "full", "center", "left", "right"] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/blocks/form-button/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import { fieldParents } from "../../constants"; 5 | import formButtonEdit from "./edit.js"; 6 | import formButtonSave from "./save.js"; 7 | 8 | import blockData from "./block.json"; 9 | const { title, attributes } = blockData; 10 | 11 | registerBlockType("cwp/form-button", { 12 | title: __(title), 13 | icon: __( 14 | 25 | ), 26 | supports: { 27 | align: true, 28 | align: ["left", "center", "right"], 29 | }, 30 | category: "gutenberg-forms", 31 | keywords: [__("gutenberg-forms"), __("forms"), __("button")], 32 | edit: formButtonEdit, 33 | save: formButtonSave, 34 | attributes, 35 | parent: fieldParents, 36 | }); 37 | -------------------------------------------------------------------------------- /src/blocks/form-button/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function save(props) { 4 | const { 5 | styling, 6 | label, 7 | action, 8 | parentId, 9 | styling: { padding }, 10 | } = props.attributes; 11 | 12 | const buttonStyling = { 13 | ...styling, 14 | padding: `${Math.floor(padding / 3)}px ${padding}px`, 15 | }; 16 | 17 | switch (action) { 18 | case "submit": 19 | return ( 20 | 27 | ); 28 | case "reset": 29 | return ( 30 | 35 | ); 36 | default: 37 | return ( 38 | 44 | ); 45 | } 46 | } 47 | 48 | export default save; 49 | -------------------------------------------------------------------------------- /src/blocks/form-calculation/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Calculation", 3 | "attributes": { 4 | "formulaBuilder": { 5 | "type": "boolean", 6 | "default": true 7 | }, 8 | "calculation": { 9 | "type": "string", 10 | "default": "" 11 | }, 12 | "label": { 13 | "type": "string", 14 | "default": "Total" 15 | }, 16 | "id": { 17 | "type": "string", 18 | "default": "" 19 | }, 20 | "field_name": { 21 | "type": "string", 22 | "default": "" 23 | }, 24 | "formula": { 25 | "type": "string", 26 | "default": "" 27 | }, 28 | "condition": { 29 | "type": "object", 30 | "default": { 31 | "field": null, 32 | "condition": "===", 33 | "value": "" 34 | } 35 | }, 36 | "styling": { 37 | "type": "object", 38 | "default": { 39 | "fontSize": 40 40 | } 41 | }, 42 | "enableCondition": { 43 | "type": "boolean", 44 | "default": false 45 | }, 46 | "postfix": { 47 | "type": "string", 48 | "default": "" 49 | }, 50 | "prefix": { 51 | "type": "string", 52 | "default": "" 53 | }, 54 | "adminId": { 55 | "type": "object", 56 | "default": { 57 | "default": "", 58 | "value": "" 59 | } 60 | }, 61 | "decimalPlaces": { 62 | "type": "number", 63 | "default": 0 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/blocks/form-calculation/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import calculationEdit from "./edit.js"; 5 | import calculationSave from "./save.js"; 6 | import { fieldParents } from "../../constants"; 7 | import Icon from "../../block/Icon.js"; 8 | 9 | import blockData from "./block.json"; 10 | const { attributes, title } = blockData; 11 | 12 | registerBlockType("cwp/form-calculation", { 13 | title: __(title), 14 | icon: __(), 15 | category: "gutenberg-forms", 16 | keywords: [__("gutenberg-forms"), __("forms"), __("calculation")], 17 | edit: calculationEdit, 18 | save: calculationSave, 19 | attributes, 20 | supports: { 21 | align: ["wide", "full", "center"], 22 | }, 23 | parent: fieldParents, 24 | }); 25 | -------------------------------------------------------------------------------- /src/blocks/form-calculation/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isEmpty } from "lodash"; 3 | import { strip_tags } from "../../block/misc/helper"; 4 | import { stringifyCondition } from "../../block/functions"; 5 | 6 | function save(props) { 7 | const { 8 | calculation, 9 | label, 10 | id, 11 | formula, 12 | condition, 13 | enableCondition, 14 | prefix, 15 | postfix, 16 | styling, 17 | decimalPlaces 18 | } = props.attributes; 19 | 20 | const getLabel = () => { 21 | const { label } = props.attributes; 22 | 23 | return label; 24 | }; 25 | 26 | const getCondition = () => { 27 | if (props.attributes.enableCondition) { 28 | //verifying the condition 29 | return { 30 | "data-condition": stringifyCondition(condition) 31 | }; 32 | } 33 | 34 | return {}; 35 | }; 36 | 37 | const getCalculation = () => { 38 | if (!isEmpty(formula)) { 39 | return { 40 | "data-cwp-calculation": formula 41 | }; 42 | } 43 | 44 | return {}; 45 | }; 46 | 47 | return ( 48 |
54 |
55 | {!isEmpty(label) && ( 56 | 60 | )} 61 |
62 | {!isEmpty(prefix) && {prefix}} 63 | 64 | 0 65 | 66 | {!isEmpty(postfix) && {postfix}} 67 |
68 | 78 |
79 |
80 | ); 81 | } 82 | 83 | export default save; 84 | -------------------------------------------------------------------------------- /src/blocks/form-column/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Column", 3 | "attributes": { 4 | "columns": { 5 | "type": "number", 6 | "default": 3 7 | }, 8 | "intro": { 9 | "type": "boolean", 10 | "default": false 11 | }, 12 | "stack": { 13 | "type": "boolean", 14 | "default": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/blocks/form-column/components/introduction.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Icon from "../../../block/Icon"; 3 | 4 | function Introduction(props) { 5 | const selections = [ 6 | { 7 | label: , 8 | value: 2 9 | }, 10 | { 11 | label: , 12 | value: 3 13 | }, 14 | { 15 | label: , 16 | value: 4 17 | } 18 | ]; 19 | 20 | return ( 21 |
22 |

Columns

23 |

Select your column for the form!

24 | {selections.map(selection => ( 25 | 31 | ))} 32 |
33 | ); 34 | } 35 | 36 | export default Introduction; 37 | -------------------------------------------------------------------------------- /src/blocks/form-column/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType, createBlock } = wp.blocks; 3 | 4 | import formColumnEdit from "./edit.js"; 5 | import formColumnSave from "./save.js"; 6 | import { fieldParents } from "../../constants"; 7 | 8 | import blockData from "./block.json"; 9 | const { attributes, title } = blockData; 10 | 11 | registerBlockType("cwp/form-column", { 12 | title: __(title), 13 | icon: "editor-table", 14 | category: "gutenberg-forms", 15 | keywords: [ 16 | __("gutenberg-forms"), 17 | __("forms"), 18 | __("form-column"), 19 | __("column"), 20 | ], 21 | edit: formColumnEdit, 22 | save: formColumnSave, 23 | attributes, 24 | supports: { 25 | align: true, 26 | align: ["wide", "full", "center"], 27 | }, 28 | parent: fieldParents, 29 | }); 30 | -------------------------------------------------------------------------------- /src/blocks/form-column/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { InnerBlocks } from "@wordpress/block-editor"; 3 | 4 | function save(props) { 5 | const { columns, stack } = props.attributes, 6 | stackClass = stack ? "cwp_stack_columns" : ""; 7 | 8 | return ( 9 |
13 | 14 |
15 | ); 16 | } 17 | 18 | export default save; 19 | -------------------------------------------------------------------------------- /src/blocks/form-group/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Group", 3 | "attributes": { 4 | "styling": { 5 | "type": "object", 6 | "default": { 7 | "backgroundColor": "rgb(238, 238, 238)", 8 | "color": "rgb(49, 49, 49)", 9 | "padding": 25, 10 | "borderColor": "rgb(220, 215, 202)", 11 | "borderWidth": 2, 12 | "borderRadius": 0 13 | } 14 | }, 15 | "label": { 16 | "type": "string", 17 | "default": "My Group" 18 | }, 19 | "content": { 20 | "type": "string", 21 | "default": "" 22 | }, 23 | "condition": { 24 | "type": "object", 25 | "default": { 26 | "field": null, 27 | "condition": "===", 28 | "value": "" 29 | } 30 | }, 31 | "enableCondition": { 32 | "type": "boolean", 33 | "default": false 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/blocks/form-group/edit.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import Inspector from "./Inspector"; 3 | import { Notice } from "@wordpress/components"; 4 | import { isChildFieldsRequired } from "../../block/functions"; 5 | const { InnerBlocks, RichText } = wp.blockEditor; 6 | const { __ } = wp.i18n; 7 | 8 | 9 | function edit(props) { 10 | const { styling, label, enableCondition } = props.attributes; 11 | 12 | const handleLabel = label => { 13 | props.setAttributes({ label }); 14 | }; 15 | 16 | const groupStyling = { 17 | border: `${styling.borderWidth}px solid ${styling.borderColor}`, 18 | ...styling 19 | } 20 | 21 | return [ 22 | !!props.isSelected && , 23 | null, 24 | 25 | {isChildFieldsRequired(props.clientId) && enableCondition && ( 26 | 27 | { 28 | __("Do not have a required fields inside a conditional group.", "forms-gutenberg") 29 | } 30 | 31 | )} 32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 | ]; 40 | } 41 | 42 | export default edit; 43 | -------------------------------------------------------------------------------- /src/blocks/form-group/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import { fieldParents } from "../../constants"; 5 | import formGroupEdit from "./edit.js"; 6 | import formGroupSave from "./save.js"; 7 | 8 | import blockData from "./block.json"; 9 | const { attributes, title } = blockData; 10 | 11 | registerBlockType("cwp/form-group", { 12 | title: __(title), 13 | icon: "forms", 14 | category: "gutenberg-forms", 15 | keywords: [ 16 | __("gutenberg-forms"), 17 | __("forms"), 18 | __("form group"), 19 | __("column"), 20 | ], 21 | edit: formGroupEdit, 22 | save: formGroupSave, 23 | attributes, 24 | supports: { 25 | align: true, 26 | align: ["wide", "full", "center"], 27 | }, 28 | parent: fieldParents, 29 | }); 30 | -------------------------------------------------------------------------------- /src/blocks/form-group/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isEmpty } from "lodash"; 3 | import { stringifyCondition } from "../../block/functions"; 4 | const { InnerBlocks } = wp.blockEditor; 5 | 6 | function save(props) { 7 | const { styling, label, condition, enableCondition } = props.attributes; 8 | 9 | const groupStyling = { 10 | border: `${styling.borderWidth}px solid ${styling.borderColor}`, 11 | ...styling 12 | } 13 | 14 | 15 | const getCondition = () => { 16 | if (enableCondition) { 17 | //verifying the condition 18 | return { 19 | "data-condition": stringifyCondition(condition) 20 | }; 21 | } 22 | 23 | return {}; 24 | }; 25 | return ( 26 |
27 | {!isEmpty(label) && ( 28 | 29 | )} 30 |
31 | 32 |
33 |
34 | ); 35 | } 36 | 37 | export default save; 38 | -------------------------------------------------------------------------------- /src/blocks/form-steps/childs/form-step/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Step", 3 | "attributes": { 4 | "label": { 5 | "type": "string", 6 | "default": "Form Step" 7 | }, 8 | "hideStep": { 9 | "type": "boolean", 10 | "default": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/blocks/form-steps/childs/form-step/edit.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, Fragment } from "react"; 2 | import { InnerBlocks } from "@wordpress/block-editor"; 3 | import { getRootFormBlock } from "../../../../block/functions/index"; 4 | import { isEmpty, get } from "lodash"; 5 | import { Notice, TextControl, PanelBody } from "@wordpress/components"; 6 | 7 | const { InspectorControls } = wp.blockEditor; 8 | const { __ } = wp.i18n; 9 | 10 | function edit(props) { 11 | const [disabled, setDisabled] = useState(false); 12 | 13 | const { setAttributes } = props; 14 | const { label, hideStep } = props.attributes; 15 | 16 | useEffect(() => { 17 | const root = getRootFormBlock(props.clientId); 18 | 19 | if (!isEmpty(root)) { 20 | let root_type = get(root, "attributes.formType"); 21 | 22 | root_type === "standard" ? setDisabled(true) : null; // checking if the root form is a multistep 23 | } 24 | }, []); 25 | 26 | return [ 27 | props.isSelected && !disabled && ( 28 | 29 | 30 | setAttributes({ label })} 35 | /> 36 | 37 | 38 | ), 39 | null, 40 |
41 | {disabled ? ( 42 | 43 | This is to be used only within the Multi-Step Form. 44 | 45 | ) : ( 46 |
50 | 51 |
52 | )} 53 |
, 54 | ]; 55 | } 56 | 57 | export default edit; 58 | -------------------------------------------------------------------------------- /src/blocks/form-steps/childs/form-step/index.js: -------------------------------------------------------------------------------- 1 | const { registerBlockType } = wp.blocks; 2 | const { __ } = wp.i18n; 3 | 4 | import stepFormEdit from "./edit"; 5 | import stepFormSave from "./save"; 6 | 7 | import blockData from "./block.json"; 8 | const { attributes, title } = blockData; 9 | 10 | registerBlockType("cwp/form-step", { 11 | title: __(title), 12 | icon: "editor-ol-rtl", 13 | category: "gutenberg-forms", 14 | keywords: [ 15 | __("gutenberg-forms"), 16 | __("forms"), 17 | __("form-step"), 18 | __("step"), 19 | __("multistep"), 20 | ], 21 | edit: stepFormEdit, 22 | save: stepFormSave, 23 | attributes, 24 | parent: ["cwp/form-steps"], 25 | }); 26 | -------------------------------------------------------------------------------- /src/blocks/form-steps/childs/form-step/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { InnerBlocks } from "@wordpress/block-editor"; 3 | import { isEmpty } from "lodash"; 4 | 5 | function save(props) { 6 | const { label } = props.attributes; 7 | 8 | const stepLabel = isEmpty(label) ? "Form Step" : label; 9 | 10 | return ( 11 |
12 | 13 |
14 | ); 15 | } 16 | 17 | export default save; 18 | -------------------------------------------------------------------------------- /src/blocks/form-steps/root/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Form Steps", 3 | "attributes": { 4 | "currentStep": { 5 | "type": "number", 6 | "default": 0 7 | }, 8 | "multiStepEffect": { 9 | "type": "string", 10 | "default": "cwp-noEffect-step" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/blocks/form-steps/root/index.js: -------------------------------------------------------------------------------- 1 | const { registerBlockType } = wp.blocks; 2 | const { __ } = wp.i18n; 3 | 4 | import stepsEdit from "./edit"; 5 | import stepsSave from "./save"; 6 | 7 | import blockData from "./block.json"; 8 | const { title, attributes } = blockData; 9 | 10 | registerBlockType("cwp/form-steps", { 11 | title: __(title), 12 | icon: "editor-ol-rtl", 13 | category: "gutenberg-forms", 14 | supports: { 15 | inserter: false, 16 | }, 17 | keywords: [ 18 | __("gutenberg-forms"), 19 | __("forms"), 20 | __("form-step"), 21 | __("step"), 22 | __("multistep"), 23 | ], 24 | edit: stepsEdit, 25 | save: stepsSave, 26 | attributes, 27 | parent: ["cwp/block-gutenberg-forms"], 28 | }); 29 | -------------------------------------------------------------------------------- /src/blocks/form-steps/root/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const { InnerBlocks } = wp.editor; 4 | 5 | function save(props) { 6 | const { multiStepEffect } = props.attributes; 7 | 8 | return ( 9 |
10 | 11 |
12 | ); 13 | } 14 | 15 | export default save; 16 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/components/introduction.js: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment } from "react"; 2 | import { Button, Placeholder, TextControl } from "@wordpress/components"; 3 | import SettingsModal from "../settings_modal/modal"; 4 | const { __ } = wp.i18n; 5 | 6 | function Introduction(props) { 7 | const [modal, setModal] = useState(false); 8 | const { cpt, formLabel, id } = props.data.attributes; // weather it is a cpt or not 9 | const { setAttributes } = props.data; 10 | 11 | const handleType = (type) => { 12 | props.onSelect(type); 13 | if (formLabel === "") { 14 | const formId = id && "form-".concat(id.split("-")[1]); 15 | 16 | setAttributes({ formLabel: "Gutenberg Form - ".concat(formId) }); 17 | } 18 | }; 19 | 20 | return ( 21 |
22 | setModal(false)} 27 | /> 28 | 36 |
37 | setAttributes({ formLabel })} 41 | /> 42 | 43 |
44 | 47 | 48 | {__('or', 'forms-gutenberg')} 49 | 50 | 53 | {/**/} 56 |
57 |
58 |
59 |
60 | ); 61 | } 62 | 63 | export default Introduction; 64 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/components/messages.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { TextControl, Icon } from "@wordpress/components"; 3 | import { firstCapital, getFieldIcon } from "../../../block/misc/helper.js"; 4 | import { has } from "lodash"; 5 | const { __ } = wp.i18n; 6 | 7 | export default function CustomMessages(props) { 8 | const { val } = props; 9 | 10 | const handleChange = (t, v, i, fieldName) => { 11 | props.onChange(t, v, i, fieldName); 12 | }; 13 | 14 | return ( 15 | 16 | {val.map((v, i) => { 17 | let fieldName = "cwp/".concat(v.fieldName); 18 | 19 | return ( 20 | 21 |
22 | {/* handleChange("empty", value, i, fieldName)} 24 | label="Required" 25 | value={v.empty} 26 | /> */} 27 | {has(v, "invalid") && ( 28 | 29 |

30 | 31 | {"Invalid ".concat(firstCapital(v.fieldName))} 32 | 33 | 34 |

35 | 37 | handleChange("invalid", value, i, fieldName) 38 | } 39 | value={v.invalid} 40 | /> 41 |
42 | )} 43 | {has(v, "invalidEmail") && ( 44 | 45 |

46 | 47 | {"Invalid ".concat(firstCapital(v.fieldName))} 48 | 49 | 50 |

51 | 53 | handleChange("invalidEmail", value, i, fieldName) 54 | } 55 | value={v.invalidEmail} 56 | /> 57 |
58 | )} 59 | {has(v, "invalidName") && ( 60 | 61 |

62 | 63 | {"Invalid ".concat(firstCapital(v.fieldName))} 64 | 65 | 66 |

67 | 69 | handleChange("invalidName", value, i, fieldName) 70 | } 71 | value={v.invalidName} 72 | /> 73 |
74 | )} 75 |
76 |
77 | ); 78 | })} 79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/deprecated/deprecated.js: -------------------------------------------------------------------------------- 1 | import blockData from "../block.json"; 2 | import { getGlobalMessages } from "../../../block/functions"; 3 | import save from "./save"; 4 | import edit from "./edit"; 5 | 6 | const blockAttributes = { 7 | ...blockData.attributes, 8 | messages: { 9 | type: "array", 10 | default: getGlobalMessages(), 11 | }, 12 | template: { 13 | type: "string", 14 | default: JSON.stringify({ 15 | subject: "", 16 | body: "", 17 | }), 18 | }, 19 | }; 20 | 21 | export const deprecation = [ 22 | { 23 | attributes: blockAttributes, 24 | edit, 25 | save, 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import mainEdit from "./edit"; 5 | import mainSave from "./save"; 6 | import { 7 | getGlobalMessages, 8 | get_submission_message, 9 | } from "../../block/functions"; 10 | import { fieldSupport } from "../../constants"; 11 | import { deprecation } from "./deprecated/deprecated"; 12 | import { get } from "lodash"; 13 | 14 | import blockData from "./block.json"; 15 | 16 | const { error, spam } = get_submission_message(); 17 | 18 | const blockAttributes = { 19 | ...blockData.attributes, 20 | messages: { 21 | type: "array", 22 | default: getGlobalMessages(), 23 | }, 24 | template: { 25 | type: "string", 26 | default: JSON.stringify({ 27 | subject: "", 28 | body: "", 29 | }), 30 | }, 31 | spamMessage: { 32 | type: "string", 33 | default: get(spam, "value"), 34 | }, 35 | errorMessage: { 36 | type: "string", 37 | default: get(error, "value"), 38 | }, 39 | }; 40 | 41 | registerBlockType("cwp/block-gutenberg-forms", { 42 | supports: { 43 | ...fieldSupport, 44 | reusable: false, 45 | }, 46 | title: __(blockData.title), 47 | icon: __("feedback"), 48 | category: "gutenberg-forms", 49 | keywords: [__("gutenberg-forms"), __("forms")], 50 | example: blockData.example, 51 | attributes: blockAttributes, 52 | deprecated: deprecation, 53 | edit: mainEdit, 54 | save: mainSave, 55 | }); 56 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/settings_modal/components/Empty.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from "@wordpress/components" 3 | 4 | const new_form_url = cwpGlobal.new_form_url; 5 | 6 | function Empty({ message }) { 7 | 8 | 9 | return ( 10 |
11 |
12 |

{message}

13 | 14 | 17 |
18 |
19 | ) 20 | } 21 | 22 | export default Empty; -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/settings_modal/components/PostTypeBlock.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button, Card, CardBody, CardHeader, CardFooter, CardMedia, Icon } from '@wordpress/components' 3 | const { createBlock, parse } = wp.blocks; 4 | const { replaceBlock } = wp.data.dispatch("core/block-editor"); 5 | 6 | 7 | function PostTypeBlock(props) { 8 | 9 | const { form: { post_title, ID } } = props; 10 | 11 | const apply_template = () => { 12 | replaceBlock( 13 | props.clientId, 14 | createBlock( 15 | 'cwp/reusable-form', 16 | { 17 | formId: ID.toString() 18 | } 19 | ) 20 | ); 21 | 22 | props.onSelect(); 23 | } 24 | 25 | return ( 26 | 27 | 28 |
29 | 30 |

{post_title}

31 |
32 | 33 |
34 |
35 | ) 36 | } 37 | 38 | 39 | export default PostTypeBlock; -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/settings_modal/components/header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TextControl, Icon } from "@wordpress/components"; 3 | 4 | function Header(props) { 5 | return ( 6 |
7 |
8 |

{props.currentCatagory}

9 |
10 |
11 | 12 |
13 |
14 | ); 15 | } 16 | 17 | export default Header; 18 | -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/settings_modal/components/preview_block.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button, Card, CardBody, CardHeader, CardFooter, CardMedia, Icon } from '@wordpress/components' 3 | import { get } from 'lodash' 4 | const { createBlock, parse } = wp.blocks; 5 | const { replaceBlock } = wp.data.dispatch("core/block-editor"); 6 | 7 | function PreviewBlock(props) { 8 | 9 | const { data } = props; 10 | 11 | const { fields } = data; 12 | 13 | const name = get(fields, 'Name'), 14 | screenshot = get(fields, 'Screenshot[0].thumbnails.large.url'), 15 | code = get(fields, 'Code'); 16 | 17 | 18 | 19 | const apply_template = () => { 20 | 21 | 22 | const [template] = parse(code); 23 | 24 | 25 | const { name, attributes, innerBlocks } = template; 26 | 27 | replaceBlock( 28 | props.clientId, 29 | createBlock( 30 | name, 31 | attributes, 32 | innerBlocks 33 | ) 34 | ); 35 | props.onSelect(); 36 | } 37 | 38 | return ( 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 |
48 | ) 49 | } 50 | 51 | export default PreviewBlock; -------------------------------------------------------------------------------- /src/blocks/gutenberg-forms/settings_modal/components/templates.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | 4 | Panel, 5 | MenuGroup, 6 | MenuItem 7 | 8 | } from "@wordpress/components" 9 | import { get, isEqual } from "lodash" 10 | 11 | const { __ } = wp.i18n; 12 | 13 | function Templates(props) { 14 | 15 | 16 | const { templates, currentTemplate } = props; 17 | 18 | const currentTemplateName = get(currentTemplate, 'fields.Name'); 19 | 20 | return ( 21 |
22 | Available Templates, "forms-gutenberg")}> 23 | 24 | { 25 | templates.map((template, index) => { 26 | 27 | const name = get(template, 'fields.Name'); 28 | 29 | return props.onSelect(template)} isDefault={isEqual(currentTemplateName, name)} key={index}>{name} 30 | 31 | }) 32 | } 33 | 34 | 35 |
36 | ) 37 | } 38 | 39 | export default Templates; 40 | -------------------------------------------------------------------------------- /src/blocks/hidden/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Hidden", 3 | "attributes": { 4 | "value": { 5 | "type": "string", 6 | "default": "" 7 | }, 8 | "label": { 9 | "type": "string", 10 | "default": "Hidden" 11 | }, 12 | "id": { 13 | "type": "string", 14 | "default": "" 15 | }, 16 | "field_name": { 17 | "type": "string", 18 | "default": "" 19 | }, 20 | "adminId": { 21 | "type": "object", 22 | "default": { 23 | "default": "", 24 | "value": "" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/blocks/hidden/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import hiddenEdit from "./edit.js"; 5 | import hiddenSave from "./save.js"; 6 | import { fieldParents } from "../../constants"; 7 | 8 | import blockData from "./block.json"; 9 | const { attributes,title } = blockData; 10 | 11 | registerBlockType("cwp/hidden", { 12 | title: __(title), 13 | icon: "hidden", 14 | category: "gutenberg-forms", 15 | keywords: [__("gutenberg-forms"), __("forms"), __("hidden"), __("field")], 16 | edit: hiddenEdit, 17 | save: hiddenSave, 18 | attributes, 19 | parent: fieldParents, 20 | }); 21 | -------------------------------------------------------------------------------- /src/blocks/hidden/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function save(props) { 4 | const { id, value } = props.attributes; 5 | 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | export default save; 14 | -------------------------------------------------------------------------------- /src/blocks/message/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Textarea", 3 | "attributes": { 4 | "enableCondition": { 5 | "type": "boolean", 6 | "default": false 7 | }, 8 | "message": { 9 | "type": "string", 10 | "default": "" 11 | }, 12 | "isRequired": { 13 | "type": "boolean", 14 | "default": false 15 | }, 16 | "label": { 17 | "type": "string", 18 | "default": "Message" 19 | }, 20 | "id": { 21 | "type": "string", 22 | "default": "" 23 | }, 24 | "height": { 25 | "type": "number", 26 | "default": 200 27 | }, 28 | "field_name": { 29 | "type": "string", 30 | "default": "" 31 | }, 32 | "messages": { 33 | "type": "object", 34 | "default": { 35 | "empty": "Please fill out this field!", 36 | "invalid": "The message {{value}} is not valid!" 37 | } 38 | }, 39 | "pattern": { 40 | "type": "string", 41 | "default": "" 42 | }, 43 | "condition": { 44 | "type": "object", 45 | "default": { 46 | "field": null, 47 | "condition": "===", 48 | "value": "" 49 | } 50 | }, 51 | "requiredLabel": { 52 | "type": "string", 53 | "default": "*" 54 | }, 55 | "minimumLength": { 56 | "type": "number", 57 | "default": 0 58 | }, 59 | "maximumLength": { 60 | "type": "number", 61 | "default": 1000 62 | }, 63 | "adminId": { 64 | "type": "object", 65 | "default": { 66 | "default": "", 67 | "value": "" 68 | } 69 | }, 70 | "showHint" :{ 71 | "type": "boolean", 72 | "default": false 73 | }, 74 | "hint": { 75 | "type": "string", 76 | "default": "" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/blocks/message/index.js: -------------------------------------------------------------------------------- 1 | const { __ } = wp.i18n; 2 | const { registerBlockType } = wp.blocks; 3 | 4 | import { getFieldTransform } from "../../block/functions"; 5 | import { fieldParents, myAttrs } from "../../constants"; 6 | import messageEdit from "./edit.js"; 7 | import messageSave from "./save.js"; 8 | 9 | import blockData from "./block.json"; 10 | const { attributes, title } = blockData; 11 | 12 | registerBlockType("cwp/message", { 13 | title: __(title), 14 | icon: "testimonial", 15 | category: "gutenberg-forms", 16 | keywords: [__("gutenberg-forms"), __("forms"), __("message")], 17 | edit: messageEdit, 18 | save: messageSave, 19 | attributes, 20 | transforms: { 21 | from: [ 22 | { 23 | type: "block", 24 | blocks: myAttrs.map((block) => "cwp/".concat(block)), 25 | transform: (a) => getFieldTransform(a, "message"), 26 | }, 27 | ], 28 | }, 29 | parent: fieldParents, 30 | }); 31 | -------------------------------------------------------------------------------- /src/blocks/message/save.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isEmpty } from "lodash"; 3 | import { strip_tags } from "../../block/misc/helper"; 4 | import { stringifyCondition } from "../../block/functions"; 5 | 6 | function save(props) { 7 | const { 8 | message, 9 | isRequired, 10 | label, 11 | id, 12 | height, 13 | requiredLabel, 14 | messages: { empty, invalid }, 15 | pattern, 16 | condition, 17 | enableCondition, 18 | minimumLength, 19 | maximumLength, 20 | hint, 21 | showHint 22 | } = props.attributes; 23 | 24 | const getLabel = () => { 25 | const { label, isRequired } = props.attributes; 26 | 27 | let required = !isEmpty(requiredLabel) 28 | ? `${requiredLabel}` 29 | : ""; 30 | let required_label = label + " " + required; 31 | 32 | if (isRequired) return required_label; 33 | 34 | return label; 35 | }; 36 | let errors = JSON.stringify({ 37 | mismatch: invalid, 38 | empty 39 | }); 40 | let getPattern = () => { 41 | return isEmpty(pattern) ? {} : { pattern }; 42 | }; 43 | 44 | const getCondition = () => { 45 | if (props.attributes.enableCondition && !isEmpty(condition.field)) { 46 | //verifying the condition 47 | return { 48 | "data-condition": stringifyCondition(condition) 49 | }; 50 | } 51 | 52 | return {}; 53 | }; 54 | 55 | return ( 56 |
57 |
58 | {!isEmpty(label) && ( 59 | 63 | )} 64 |