diff --git a/backend/Actions/UserRegistrationMembership/RecordApiHelper.php b/backend/Actions/UserRegistrationMembership/RecordApiHelper.php new file mode 100644 index 00000000..db74686b --- /dev/null +++ b/backend/Actions/UserRegistrationMembership/RecordApiHelper.php @@ -0,0 +1,82 @@ +_integrationDetails = $integrationDetails; + $this->_integrationID = $integId; + } + + public function execute($fieldValues, $fieldMap) + { + if (!class_exists('UserRegistration')) { + $response = [ + 'success' => false, + 'message' => __('User Registration and Membership is not installed or activated', 'bit-integrations') + ]; + + LogHandler::save($this->_integrationID, ['type' => 'User Registration', 'type_name' => 'check'], 'error', $response); + + return $response; + } + + $fieldData = $this->generateReqDataFromFieldMap($fieldMap, $fieldValues); + $mainAction = $this->_integrationDetails->mainAction ?? 'create_user'; + $response = $this->processAction($mainAction, $fieldData); + + $responseType = !empty($response['success']) ? 'success' : 'error'; + LogHandler::save($this->_integrationID, ['type' => 'User Registration', 'type_name' => $mainAction], $responseType, $response); + + return $response; + } + + private function processAction($mainAction, $fieldData) + { + $defaultResponse = [ + 'success' => false, + 'message' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro') + ]; + + switch ($mainAction) { + case 'create_user': + return apply_filters(Config::withPrefix('user_registration_create_user'), $defaultResponse, $fieldData, $this->_integrationDetails); + + default: + return [ + 'success' => false, + 'message' => __('Invalid action', 'bit-integrations') + ]; + } + } + + private function generateReqDataFromFieldMap($fieldMap, $fieldValues) + { + $dataFinal = []; + + foreach ($fieldMap as $item) { + if (empty($item->userRegistrationField)) { + continue; + } + + $dataFinal[$item->userRegistrationField] = $item->formField === 'custom' && isset($item->customValue) + ? Common::replaceFieldWithValue($item->customValue, $fieldValues) + : ($fieldValues[$item->formField] ?? ''); + } + + return $dataFinal; + } +} diff --git a/backend/Actions/UserRegistrationMembership/Routes.php b/backend/Actions/UserRegistrationMembership/Routes.php new file mode 100644 index 00000000..0c166eca --- /dev/null +++ b/backend/Actions/UserRegistrationMembership/Routes.php @@ -0,0 +1,12 @@ + 'user_registration', + 'post_status' => 'publish', + 'orderby' => 'ID', + 'order' => 'DESC', + 'no_found_rows' => true, + 'nopaging' => true, + ]); + + $forms = array_map( + fn ($form) => (object) [ + 'value' => $form->ID, + 'label' => $form->post_title, + ], + $allForms + ); + + wp_send_json_success(['forms' => $forms], 200); + } + + public static function refreshFormFields($request) + { + self::checkPluginExists(); + + $formId = absint($request->form_id ?? 0); + + if (empty($formId)) { + wp_send_json_error(__('Form ID is required', 'bit-integrations'), 400); + } + + $fields = self::getFormFields($formId); + + wp_send_json_success(['fields' => array_values($fields)], 200); + } + + public function execute($integrationData, $fieldValues) + { + $integrationDetails = $integrationData->flow_details; + $fieldMap = $integrationDetails->field_map ?? []; + + if (empty($fieldMap)) { + return new WP_Error('field_map_empty', __('Field map is empty', 'bit-integrations')); + } + + $recordApiHelper = new RecordApiHelper($integrationDetails, $integrationData->id); + + return $recordApiHelper->execute($fieldValues, $fieldMap); + } + + private static function getFormFields($formId) + { + if (!\function_exists('ur_get_form_fields')) { + return []; + } + + $formFields = ur_get_form_fields($formId); + + return array_map( + fn ($field) => (object) [ + 'label' => $field->general_setting->label ?? $field->field_key, + 'value' => $field->field_key, + 'required' => !empty($field->general_setting->required) + ], + $formFields + ); + } + + private static function checkPluginExists() + { + if (!class_exists('UserRegistration')) { + wp_send_json_error( + __('User Registration and Membership is not activated or not installed', 'bit-integrations'), + 400 + ); + } + } +} diff --git a/backend/Core/Util/AllTriggersName.php b/backend/Core/Util/AllTriggersName.php index ef68dfc8..abbc27d7 100644 --- a/backend/Core/Util/AllTriggersName.php +++ b/backend/Core/Util/AllTriggersName.php @@ -132,6 +132,7 @@ public static function allTriggersName() 'Tripetto' => ['name' => 'Tripetto', 'isPro' => true, 'is_active' => false], 'TutorLms' => ['name' => 'Tutor LMS', 'isPro' => true, 'is_active' => false], 'TeamsForWooCommerceMemberships' => ['name' => 'Teams For WooCommerce Memberships', 'isPro' => true, 'is_active' => false], + 'UserRegistrationMembership' => ['name' => 'User Registration & Membership', 'isPro' => true, 'is_active' => false], 'UltimateMember' => ['name' => 'UltimateMember', 'isPro' => true, 'is_active' => false], 'UserFeedback' => ['name' => 'UserFeedback', 'isPro' => true, 'is_active' => false], 'Voxel' => ['name' => 'Voxel', 'isPro' => true, 'is_active' => false], diff --git a/backend/Core/Util/Common.php b/backend/Core/Util/Common.php index a6e1ac03..f48aa34e 100644 --- a/backend/Core/Util/Common.php +++ b/backend/Core/Util/Common.php @@ -294,7 +294,7 @@ private static function replaceFieldWithValueHelper($stringToReplaceField, $fiel $fieldName = substr($field, 2, \strlen($field) - 3); $smartTagValue = SmartTags::getSmartTagValue($fieldName, true); if (isset($fieldValues[$fieldName]) && !self::isEmpty($fieldValues[$fieldName])) { - $stringToReplaceField = !\is_array($fieldValues[$fieldName]) ? str_replace($field, $fieldValues[$fieldName], $stringToReplaceField) + $stringToReplaceField = !\is_array($fieldValues[$fieldName]) && !\is_object($fieldValues[$fieldName]) ? str_replace($field, $fieldValues[$fieldName], $stringToReplaceField) : str_replace(['"' . $field . '"', $field], wp_json_encode($fieldValues[$fieldName], JSON_UNESCAPED_UNICODE), $stringToReplaceField); } elseif (!empty($smartTagValue)) { $stringToReplaceField = str_replace($field, $smartTagValue, $stringToReplaceField); diff --git a/backend/Flow/Flow.php b/backend/Flow/Flow.php index 3b16f0dd..3450a1d7 100644 --- a/backend/Flow/Flow.php +++ b/backend/Flow/Flow.php @@ -493,6 +493,11 @@ public static function execute($triggered_entity, $triggered_entity_id, $data, $ break; + case 'UserRegistration&Membership': + $integrationName = 'UserRegistrationMembership'; + + break; + default: $integrationName = $integrationName; diff --git a/frontend/src/Utils/StaticData/tutorialLinks.js b/frontend/src/Utils/StaticData/tutorialLinks.js index ef86b252..8c5666cd 100644 --- a/frontend/src/Utils/StaticData/tutorialLinks.js +++ b/frontend/src/Utils/StaticData/tutorialLinks.js @@ -658,6 +658,10 @@ const tutorialLinks = { seoPress: { youTubeLink: '', docLink: 'https://bit-integrations.com/wp-docs/actions/seopress-integrations-as-action/' + }, + userRegistrationMembership: { + youTubeLink: '', + docLink: 'https://bit-integrations.com/wp-docs/actions/user-registration-and-membership-as-action/' } } export default tutorialLinks diff --git a/frontend/src/Utils/StaticData/webhookIntegrations.js b/frontend/src/Utils/StaticData/webhookIntegrations.js index 40988a86..8a4591ec 100644 --- a/frontend/src/Utils/StaticData/webhookIntegrations.js +++ b/frontend/src/Utils/StaticData/webhookIntegrations.js @@ -87,7 +87,8 @@ export const customFormIntegrations = [ 'TeamsForWooCommerceMemberships', 'WPCafe', 'SeoPress', - 'ThriveLeads' + 'ThriveLeads', + 'UserRegistrationMembership' ] export const actionHookIntegrations = ['ActionHook'] diff --git a/frontend/src/components/AllIntegrations/EditInteg.jsx b/frontend/src/components/AllIntegrations/EditInteg.jsx index 9878ef72..34c06feb 100644 --- a/frontend/src/components/AllIntegrations/EditInteg.jsx +++ b/frontend/src/components/AllIntegrations/EditInteg.jsx @@ -175,6 +175,9 @@ const EditTeamsForWooCommerceMemberships = lazy(() => import('./TeamsForWooCommerceMemberships/EditTeamsForWooCommerceMemberships') ) const EditSeoPress = lazy(() => import('./SeoPress/EditSeoPress')) +const EditUserRegistrationMembership = lazy( + () => import('./UserRegistrationMembership/EditUserRegistrationMembership') +) const loaderStyle = { display: 'flex', @@ -352,6 +355,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'Keap': return + case 'User Registration & Membership': + return case 'Freshdesk': return case 'Zoom': diff --git a/frontend/src/components/AllIntegrations/IntegInfo.jsx b/frontend/src/components/AllIntegrations/IntegInfo.jsx index e9136eac..e546be47 100644 --- a/frontend/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend/src/components/AllIntegrations/IntegInfo.jsx @@ -176,6 +176,9 @@ const TeamsForWooCommerceMembershipsAuthorization = lazy( () => import('./TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization') ) const SeoPressAuthorization = lazy(() => import('./SeoPress/SeoPressAuthorization')) +const UserRegistrationMembershipAuthorization = lazy( + () => import('./UserRegistrationMembership/UserRegistrationMembershipAuthorization') +) export default function IntegInfo() { const { id, type } = useParams() @@ -278,6 +281,14 @@ export default function IntegInfo() { isInfo /> ) + case 'User Registration & Membership': + return ( + + ) case 'Zoho Bigin': return ( import('./TeamsForWooCommerceMemberships/TeamsForWooCommerceMemberships') ) const SeoPress = lazy(() => import('./SeoPress/SeoPress')) +const UserRegistrationMembership = lazy( + () => import('./UserRegistrationMembership/UserRegistrationMembership') +) export default function NewInteg({ allIntegURL }) { const { integUrlName } = useParams() @@ -810,6 +813,15 @@ export default function NewInteg({ allIntegURL }) { setFlow={setFlow} /> ) + case 'User Registration & Membership': + return ( + + ) case 'Mailercloud': return ( nextPage(3)} - disabled={seoPressConf.field_map.length < 1} className="btn f-right btcd-btn-lg purple sh-sm flx" type="button" disabled={!checkMappedFields(seoPressConf)}> diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/EditUserRegistrationMembership.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/EditUserRegistrationMembership.jsx new file mode 100644 index 00000000..fd47b3e6 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/EditUserRegistrationMembership.jsx @@ -0,0 +1,75 @@ +import { useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useRecoilState, useRecoilValue } from 'recoil' +import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents' +import { checkMappedFields, handleInput } from './UserRegistrationMembershipCommonFunc' +import UserRegistrationMembershipIntegLayout from './UserRegistrationMembershipIntegLayout' + +export default function EditUserRegistrationMembership({ allIntegURL }) { + const navigate = useNavigate() + const { id } = useParams() + + const [userRegistrationConf, setUserRegistrationConf] = useRecoilState($actionConf) + const [flow, setFlow] = useRecoilState($newFlow) + const formFields = useRecoilValue($formFields) + const [isLoading, setIsLoading] = useState(false) + const [snack, setSnackbar] = useState({ show: false }) + + return ( + + + + + {__('Integration Name:', 'bit-integrations')} + handleInput(e, userRegistrationConf, setUserRegistrationConf)} + name="name" + value={userRegistrationConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> + + + + + + + + + saveActionConf({ + flow, + setFlow, + allIntegURL, + conf: userRegistrationConf, + navigate, + id, + edit: 1, + setIsLoading, + setSnackbar + }) + } + disabled={!checkMappedFields(userRegistrationConf)} + isLoading={isLoading} + dataConf={userRegistrationConf} + setDataConf={setUserRegistrationConf} + formFields={formFields} + /> + + + ) +} diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembership.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembership.jsx new file mode 100644 index 00000000..d425b701 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembership.jsx @@ -0,0 +1,115 @@ +import { useState } from 'react' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useNavigate } from 'react-router-dom' +import BackIcn from '../../../Icons/BackIcn' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import UserRegistrationMembershipAuthorization from './UserRegistrationMembershipAuthorization' +import { checkMappedFields } from './UserRegistrationMembershipCommonFunc' +import UserRegistrationMembershipIntegLayout from './UserRegistrationMembershipIntegLayout' + +export default function UserRegistrationMembership({ formFields, setFlow, flow, allIntegURL }) { + const navigate = useNavigate() + const [isLoading, setIsLoading] = useState(false) + const [step, setStep] = useState(1) + const [snack, setSnackbar] = useState({ show: false }) + const [userRegistrationConf, setUserRegistrationConf] = useState({ + name: 'User Registration & Membership', + type: 'User Registration & Membership', + field_map: [{ formField: '', userRegistrationField: '' }], + actions: {}, + mainAction: '' + }) + + const nextPage = val => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + + if (val === 3) { + if (!checkMappedFields(userRegistrationConf)) { + setSnackbar({ + show: true, + msg: __('Please map all required fields to continue.', 'bit-integrations') + }) + return + } + + if (userRegistrationConf.name !== '' && userRegistrationConf.field_map.length > 0) { + setStep(val) + } + } else { + setStep(val) + } + } + + return ( + + + {/* */} + + {/* STEP 1 */} + + + {/* STEP 2 */} + + + + + + nextPage(3)} + disabled={!checkMappedFields(userRegistrationConf)} + className="btn f-right btcd-btn-lg purple sh-sm flx" + type="button"> + {__('Next', 'bit-integrations')} + + + + + {/* STEP 3 */} + + saveIntegConfig( + flow, + setFlow, + allIntegURL, + userRegistrationConf, + navigate, + '', + '', + setIsLoading + ) + } + isLoading={isLoading} + dataConf={userRegistrationConf} + setDataConf={setUserRegistrationConf} + formFields={formFields} + /> + + ) +} diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx new file mode 100644 index 00000000..267170f5 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx @@ -0,0 +1,84 @@ +import { useState } from 'react' +import { __ } from '../../../Utils/i18nwrap' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import LoaderSm from '../../Loaders/LoaderSm' +import Note from '../../Utilities/Note' +import TutorialLink from '../../Utilities/TutorialLink' +import { userRegistrationAuthorize } from './UserRegistrationMembershipCommonFunc' + +export default function UserRegistrationMembershipAuthorization({ + userRegistrationConf, + setUserRegistrationConf, + step, + nextPage, + isLoading, + setIsLoading, + setSnackbar +}) { + const [isAuthorized, setIsAuthorized] = useState(false) + const [showAuthMsg, setShowAuthMsg] = useState(false) + const { userRegistrationMembership } = tutorialLinks + + const handleAuthorize = () => { + userRegistrationAuthorize(setIsAuthorized, setShowAuthMsg, setIsLoading, setSnackbar, nextPage) + } + + return ( + + {userRegistrationMembership?.youTubeLink && ( + + )} + {userRegistrationMembership?.docLink && ( + + )} + + + {__('Integration Name:', 'bit-integrations')} + + setUserRegistrationConf({ ...userRegistrationConf, name: e.target.value })} + name="name" + value={userRegistrationConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + disabled={isAuthorized} + /> + + + {isAuthorized ? __('Connected ✔', 'bit-integrations') : __('Connect', 'bit-integrations')} + {isLoading && } + + + {showAuthMsg && isAuthorized && ( + + nextPage(2)} + className="btn f-right btcd-btn-lg purple sh-sm flx" + type="button"> + {__('Next', 'bit-integrations')} + + + + )} + + + ) +} diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js new file mode 100644 index 00000000..b5e07530 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js @@ -0,0 +1,161 @@ +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' + +export const handleInput = (e, userRegistrationConf, setUserRegistrationConf) => { + const { name, value } = e.target + const newConf = { ...userRegistrationConf } + newConf[name] = value + setUserRegistrationConf(newConf) +} + +export const userRegistrationAuthorize = ( + setIsAuthorized, + setShowAuthMsg, + setIsLoading, + setSnackbar, + nextPage +) => { + setIsLoading(true) + bitsFetch({}, 'user_registration_authorize') + .then(result => { + if (result?.success) { + setIsAuthorized(true) + setShowAuthMsg(true) + setSnackbar({ + show: true, + msg: __('Connected Successfully', 'bit-integrations') + }) + nextPage(2) + } else { + setSnackbar({ + show: true, + msg: __( + result?.data || + 'Connection failed. Please make sure User Registration and Membership plugin is installed and activated.', + 'bit-integrations' + ) + }) + } + setIsLoading(false) + }) + .catch(error => { + setSnackbar({ + show: true, + msg: __('Connection failed', 'bit-integrations') + }) + setIsLoading(false) + }) +} + +export const refreshForms = ( + userRegistrationConf, + setUserRegistrationConf, + setIsLoading, + setSnackbar +) => { + setIsLoading(true) + bitsFetch({}, 'refresh_user_registration_forms') + .then(result => { + if (result?.success && result?.data) { + const newConf = { ...userRegistrationConf } + if (!newConf.default) { + newConf.default = {} + } + newConf.default.forms = result.data.forms || [] + setUserRegistrationConf(newConf) + setSnackbar({ + show: true, + msg: __('Forms refreshed successfully', 'bit-integrations') + }) + } else { + setSnackbar({ + show: true, + msg: __('Failed to refresh forms', 'bit-integrations') + }) + } + setIsLoading(false) + }) + .catch(() => { + setSnackbar({ + show: true, + msg: __('Failed to refresh forms', 'bit-integrations') + }) + setIsLoading(false) + }) +} + +export const refreshFormFields = ( + formId, + userRegistrationConf, + setUserRegistrationConf, + setIsLoading, + setSnackbar +) => { + if (!formId) { + return + } + + setIsLoading(true) + bitsFetch({ form_id: formId }, 'refresh_user_registration_form_fields') + .then(result => { + if (result?.success && result?.data) { + const newConf = { ...userRegistrationConf } + if (!newConf.default) { + newConf.default = {} + } + newConf.default.formFields = result.data.fields || [] + newConf.field_map = generateMappedField(newConf.default.formFields) + setUserRegistrationConf(newConf) + setSnackbar({ + show: true, + msg: __('Form fields refreshed successfully', 'bit-integrations') + }) + } else { + setSnackbar({ + show: true, + msg: __('Failed to refresh form fields', 'bit-integrations') + }) + } + setIsLoading(false) + }) + .catch(() => { + setSnackbar({ + show: true, + msg: __('Failed to refresh form fields', 'bit-integrations') + }) + setIsLoading(false) + }) +} + +export const generateMappedField = fields => { + const requiredFlds = fields.filter(fld => fld.required === true) + return requiredFlds.length > 0 + ? requiredFlds.map(field => ({ + formField: '', + userRegistrationField: field.value || field.key + })) + : [{ formField: '', userRegistrationField: '' }] +} + +export const checkMappedFields = userRegistrationConf => { + const { mainAction, field_map, selectedForm } = userRegistrationConf + + if (!mainAction || mainAction !== 'create_user') { + return false + } + + if (!selectedForm) { + return false + } + + const formFields = userRegistrationConf?.default?.formFields || [] + const requiredFields = formFields.filter(fld => fld.required === true || fld.required === 'true') + + if (requiredFields.length === 0) { + return field_map.length > 0 + } + + const mappedFields = field_map.map(field => field.userRegistrationField).filter(f => f) + + return requiredFields.every(reqField => mappedFields.includes(reqField.value)) +} diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipFieldMap.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipFieldMap.jsx new file mode 100644 index 00000000..548aabb2 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipFieldMap.jsx @@ -0,0 +1,114 @@ +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { __, sprintf } from '../../../Utils/i18nwrap' +import { SmartTagField } from '../../../Utils/StaticData/SmartTagField' +import TagifyInput from '../../Utilities/TagifyInput' +import { + addFieldMap, + delFieldMap, + handleCustomValue, + handleFieldMapping +} from '../GlobalIntegrationHelper' + +export default function UserRegistrationMembershipFieldMap({ + i, + formFields, + field, + userRegistrationConf, + setUserRegistrationConf +}) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const requiredFlds = + userRegistrationConf?.default?.formFields?.filter(fld => fld.required === true) || [] + const nonRequiredFlds = + userRegistrationConf?.default?.formFields?.filter(fld => fld.required === false) || [] + + return ( + + + + handleFieldMapping(ev, i, userRegistrationConf, setUserRegistrationConf)}> + {__('Select Field', 'bit-integrations')} + + {formFields?.map(f => ( + + {f.label} + + ))} + + {__('Custom...', 'bit-integrations')} + + {isPro && + SmartTagField?.map(f => ( + + {f.label} + + ))} + + + + {field.formField === 'custom' && ( + handleCustomValue(e, i, userRegistrationConf, setUserRegistrationConf)} + label={__('Custom Value', 'bit-integrations')} + className="mr-2" + type="text" + value={field.customValue} + placeholder={__('Custom Value', 'bit-integrations')} + formFields={formFields} + /> + )} + + handleFieldMapping(ev, i, userRegistrationConf, setUserRegistrationConf)}> + {__('Select Field', 'bit-integrations')} + {i < requiredFlds.length ? ( + + {requiredFlds[i].label} + + ) : ( + nonRequiredFlds.map(fld => ( + + {fld.label} + + )) + )} + + + {i >= requiredFlds.length && ( + <> + addFieldMap(i, userRegistrationConf, setUserRegistrationConf)} + className="icn-btn sh-sm ml-2 mr-1" + type="button"> + + + + delFieldMap(i, userRegistrationConf, setUserRegistrationConf)} + className="icn-btn sh-sm ml-1" + type="button" + aria-label="btn"> + + + > + )} + + + ) +} diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipIntegLayout.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipIntegLayout.jsx new file mode 100644 index 00000000..13c5c165 --- /dev/null +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipIntegLayout.jsx @@ -0,0 +1,173 @@ +import MultiSelect from 'react-multiple-select-dropdown-lite' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import Loader from '../../Loaders/Loader' +import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' +import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers' +import { refreshFormFields, refreshForms } from './UserRegistrationMembershipCommonFunc' +import UserRegistrationMembershipFieldMap from './UserRegistrationMembershipFieldMap' + +export const modules = [ + { name: 'create_user', label: __('Create User', 'bit-integrations'), is_pro: true } +] + +export default function UserRegistrationMembershipIntegLayout({ + formFields, + userRegistrationConf, + setUserRegistrationConf, + setIsLoading, + isLoading, + setSnackbar +}) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const changeHandler = (val, name) => { + const newConf = { ...userRegistrationConf } + + if (name === 'mainAction') { + newConf.mainAction = val + newConf.field_map = [{ formField: '', userRegistrationField: '' }] + + refreshForms(newConf, setUserRegistrationConf, setIsLoading, setSnackbar) + } else if (name === 'selectedForm') { + newConf.selectedForm = val + refreshFormFields(val, newConf, setUserRegistrationConf, setIsLoading, setSnackbar) + } else { + newConf[name] = val + } + + setUserRegistrationConf(newConf) + } + return ( + <> + + + + {__('Select Action:', 'bit-integrations')} + changeHandler(value, 'mainAction')} + options={modules?.map(module => ({ + label: checkIsPro(isPro, module.is_pro) ? module.label : getProLabel(module.label), + value: module.name, + disabled: checkIsPro(isPro, module.is_pro) ? false : true + }))} + singleSelect + closeOnSelect + /> + + + {userRegistrationConf.mainAction === 'create_user' && ( + <> + + + {__('Select Form:', 'bit-integrations')} + changeHandler(e.target.value, 'selectedForm')} + name="selectedForm" + value={userRegistrationConf.selectedForm} + className="btcd-paper-inp w-5"> + {__('Select Form', 'bit-integrations')} + {userRegistrationConf?.default?.forms && + userRegistrationConf.default.forms.map(form => ( + + {form.label} + + ))} + + + refreshForms(userRegistrationConf, setUserRegistrationConf, setIsLoading, setSnackbar) + } + className="icn-btn sh-sm ml-2 mr-2 tooltip" + style={{ '--tooltip-txt': `'${__('Refresh forms', 'bit-integrations')}'` }} + type="button" + disabled={isLoading}> + ↻ + + > + )} + + {isLoading && ( + + )} + + {userRegistrationConf.mainAction && userRegistrationConf.selectedForm && ( + + {__('Map Fields', 'bit-integrations')} + + refreshFormFields( + userRegistrationConf.selectedForm, + userRegistrationConf, + setUserRegistrationConf, + setIsLoading, + setSnackbar + ) + } + className="icn-btn sh-sm ml-2 mr-2 tooltip" + style={{ '--tooltip-txt': `'${__('Refresh fields', 'bit-integrations')}'` }} + type="button" + disabled={isLoading}> + ↻ + + + )} + + {userRegistrationConf.mainAction && userRegistrationConf.selectedForm && ( + <> + + + + {__('Form Fields', 'bit-integrations')} + + + {__('User Registration Fields', 'bit-integrations')} + + + + {userRegistrationConf.field_map.map((itm, i) => ( + + ))} + + + + addFieldMap( + userRegistrationConf.field_map.length, + userRegistrationConf, + setUserRegistrationConf + ) + } + className="icn-btn sh-sm" + type="button"> + + + + + + + > + )} + > + ) +} diff --git a/frontend/src/components/Flow/New/SelectAction.jsx b/frontend/src/components/Flow/New/SelectAction.jsx index 7c6505c9..dd0f1509 100644 --- a/frontend/src/components/Flow/New/SelectAction.jsx +++ b/frontend/src/components/Flow/New/SelectAction.jsx @@ -176,7 +176,8 @@ export default function SelectAction() { { type: 'FluentCart' }, { type: 'WPCafe' }, { type: 'Teams For WooCommerce Memberships' }, - { type: 'SeoPress' } + { type: 'SeoPress' }, + { type: 'User Registration & Membership', logo: 'userRegistrationMembership' } ] const [availableIntegs, setAvailableIntegs] = useState(sortByField(integs, 'type', 'ASC') || integs) diff --git a/frontend/src/resource/img/integ/userRegistrationMembership.webp b/frontend/src/resource/img/integ/userRegistrationMembership.webp new file mode 100644 index 00000000..5e002de5 Binary files /dev/null and b/frontend/src/resource/img/integ/userRegistrationMembership.webp differ