From c1cdff8929de7f410412ef2d191c3bcc8dfa8fe1 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Sun, 7 Sep 2025 12:43:05 +0600 Subject: [PATCH 01/13] feat: fabman actions --- .../components/AllIntegrations/EditInteg.jsx | 3 + .../AllIntegrations/Fabman/EditFabman.jsx | 94 +++++++ .../AllIntegrations/Fabman/Fabman.jsx | 215 +++++++++++++++ .../AllIntegrations/Fabman/FabmanActions.jsx | 13 + .../Fabman/FabmanAuthorization.jsx | 120 ++++++++ .../Fabman/FabmanCommonFunc.js | 161 +++++++++++ .../AllIntegrations/Fabman/FabmanFieldMap.jsx | 105 +++++++ .../Fabman/FabmanIntegLayout.jsx | 261 ++++++++++++++++++ .../Fabman/IntegrationHelpers.jsx | 26 ++ .../components/AllIntegrations/IntegInfo.jsx | 3 + .../components/AllIntegrations/NewInteg.jsx | 10 + .../src/components/Flow/New/SelectAction.jsx | 1 + .../src/resource/img/integ/fabman.webp | 3 + includes/Actions/Fabman/FabmanController.php | 126 +++++++++ includes/Actions/Fabman/RecordApiHelper.php | 144 ++++++++++ includes/Actions/Fabman/Routes.php | 12 + 16 files changed, 1297 insertions(+) create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx create mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx create mode 100644 frontend-dev/src/resource/img/integ/fabman.webp create mode 100644 includes/Actions/Fabman/FabmanController.php create mode 100644 includes/Actions/Fabman/RecordApiHelper.php create mode 100644 includes/Actions/Fabman/Routes.php diff --git a/frontend-dev/src/components/AllIntegrations/EditInteg.jsx b/frontend-dev/src/components/AllIntegrations/EditInteg.jsx index bf5c821c2..95080d9bb 100644 --- a/frontend-dev/src/components/AllIntegrations/EditInteg.jsx +++ b/frontend-dev/src/components/AllIntegrations/EditInteg.jsx @@ -92,6 +92,7 @@ const EditMailup = lazy(() => import('./Mailup/EditMailup')) const EditNotion = lazy(() => import('./Notion/EditNotion')) const EditMailjet = lazy(() => import('./Mailjet/EditMailjet')) const EditSendGrid = lazy(() => import('./SendGrid/EditSendGrid')) +const EditFabman = lazy(() => import('./Fabman/EditFabman')) const EditPCloud = lazy(() => import('./PCloud/EditPCloud')) const EditEmailOctopus = lazy(() => import('./EmailOctopus/EditEmailOctopus')) const EditCustomAction = lazy(() => import('./CustomAction/EditCustomAction')) @@ -410,6 +411,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'SendGrid': return + case 'Fabman': + return case 'PCloud': return case 'EmailOctopus': diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx new file mode 100644 index 000000000..26aca3dda --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -0,0 +1,94 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-param-reassign */ + +import { useState } from 'react' +import { useNavigate } 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 EditFormInteg from '../EditFormInteg' +import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents' +import EditWebhookInteg from '../EditWebhookInteg' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import { checkMappedFields, handleInput } from './FabmanCommonFunc' +import FabmanIntegLayout from './FabmanIntegLayout' + +function EditFabman({ allIntegURL }) { + const navigate = useNavigate() + const [flow, setFlow] = useRecoilState($newFlow) + const [fabmanConf, setFabmanConf] = useRecoilState($actionConf) + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({ + list: false, + field: false, + auth: false + }) + const [snack, setSnackbar] = useState({ show: false }) + const formField = useRecoilValue($formFields) + + const saveConfig = () => { + if (!fabmanConf.actionName) { + setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) + return + } + if (!checkMappedFields(fabmanConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + saveActionConf({ + flow, + allIntegURL, + conf: fabmanConf, + navigate, + edit: 1, + setLoading, + setSnackbar + }) + } + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, fabmanConf, setFabmanConf)} + name="name" + value={fabmanConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + handleInput(e, fabmanConf, setFabmanConf, setLoading, setSnackbar)} + fabmanConf={fabmanConf} + setFabmanConf={setFabmanConf} + loading={loading} + setLoading={setLoading} + setSnackbar={setSnackbar} + /> + + +
+
+ ) +} + +export default EditFabman diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx new file mode 100644 index 000000000..5226142d2 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx @@ -0,0 +1,215 @@ +/* eslint-disable no-console */ +/* eslint-disable no-unused-expressions */ +import { useState } from 'react' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useNavigate } from 'react-router-dom' +import toast from 'react-hot-toast' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import Steps from '../../Utilities/Steps' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import FabmanAuthorization from './FabmanAuthorization' +import { checkMappedFields, fetchFabmanMembers } from './FabmanCommonFunc' +import FabmanIntegLayout from './FabmanIntegLayout' + +export default function Fabman({ formFields, formID, setSnackbar }) { + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({ auth: false, workspaces: false, members: false }) + const [step, setStep] = useState(1) + const [snack, setSnack] = useState({ show: false }) + const [fabmanConf, setFabmanConf] = useState({ + name: 'Fabman', + type: 'Fabman', + field_map: [{ formField: '', fabmanFormField: '' }], + staticFields: [ + { key: 'emailAddress', label: 'Email Address', required: 1 }, + { key: 'firstName', label: 'First Name', required: '' }, + { key: 'lastName', label: 'Last Name', required: '' }, + { key: 'memberNumber', label: 'Member Number', required: '' }, + { key: 'gender', label: 'Gender', required: '' }, + { key: 'dateOfBirth', label: 'Date of Birth', required: '' }, + { key: 'company', label: 'Company', required: '' }, + { key: 'phone', label: 'Phone', required: '' }, + { key: 'address', label: 'Address', required: '' }, + { key: 'address2', label: 'Address 2', required: '' }, + { key: 'city', label: 'City', required: '' }, + { key: 'zip', label: 'ZIP Code', required: '' }, + { key: 'countryCode', label: 'Country Code', required: '' }, + { key: 'region', label: 'Region', required: '' }, + { key: 'notes', label: 'Notes', required: '' }, + { key: 'billingFirstName', label: 'Billing First Name', required: '' }, + { key: 'billingLastName', label: 'Billing Last Name', required: '' }, + { key: 'billingCompany', label: 'Billing Company', required: '' }, + { key: 'billingAddress', label: 'Billing Address', required: '' }, + { key: 'billingAddress2', label: 'Billing Address 2', required: '' }, + { key: 'billingCity', label: 'Billing City', required: '' }, + { key: 'billingZip', label: 'Billing ZIP Code', required: '' }, + { key: 'billingCountryCode', label: 'Billing Country Code', required: '' }, + { key: 'billingRegion', label: 'Billing Region', required: '' }, + { key: 'billingInvoiceText', label: 'Billing Invoice Text', required: '' }, + { key: 'billingEmailAddress', label: 'Billing Email Address', required: '' }, + { key: 'language', label: 'Language', required: '' }, + { key: 'state', label: 'State', required: '' }, + { key: 'taxExempt', label: 'Tax Exempt', required: '' }, + { key: 'hasBillingAddress', label: 'Has Billing Address', required: '' }, + { key: 'requireUpfrontPayment', label: 'Require Upfront Payment', required: '' }, + { key: 'upfrontMinimumBalance', label: 'Upfront Minimum Balance', required: '' } + ], + customFields: [], + actions: {}, + condition: { + action_behavior: '', + actions: [{ field: '', action: 'value' }], + logics: [{ field: '', logic: '', val: '' }, 'or', { field: '', logic: '', val: '' }] + }, + trigger_type: '', + pro_integ_v: '2.5.4', + fields: [] + }) + + const saveConfig = () => { + if (!checkMappedFields(fabmanConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + + if (!fabmanConf.actionName) { + setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) + return + } + + if (!fabmanConf.selectedWorkspace) { + setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + return + } + + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + if (requiresMemberSelection && !fabmanConf.selectedMember) { + setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + return + } + + setIsLoading(true) + const integConfig = { + apiKey: fabmanConf.apiKey, + selectedWorkspace: fabmanConf.selectedWorkspace, + selectedMember: fabmanConf.selectedMember, + actionName: fabmanConf.actionName, + field_map: fabmanConf.field_map, + staticFields: fabmanConf.staticFields, + customFields: fabmanConf.customFields, + actions: fabmanConf.actions, + condition: fabmanConf.condition, + trigger_type: fabmanConf.trigger_type, + pro_integ_v: fabmanConf.pro_integ_v, + fields: fabmanConf.fields + } + + saveIntegConfig(integConfig, formID) + .then(() => { + setSnackbar({ show: true, msg: __('Fabman integration saved successfully', 'bit-integrations') }) + setStep(3) + }) + .catch(() => { + setSnackbar({ show: true, msg: __('Something went wrong', 'bit-integrations') }) + }) + .finally(() => { + setIsLoading(false) + }) + } + + const nextPage = () => { + if (!checkMappedFields(fabmanConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + + if (!fabmanConf.actionName) { + setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) + return + } + + if (!fabmanConf.selectedWorkspace) { + setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + return + } + + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + if (requiresMemberSelection && !fabmanConf.selectedMember) { + setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + return + } + + setStep(3) + } + + const handleWorkspaceChange = e => { + const newConf = { ...fabmanConf } + newConf.selectedWorkspace = e.target.value + setFabmanConf(newConf) + + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + if (requiresMemberSelection && e.target.value) { + fetchFabmanMembers(newConf, setFabmanConf, loading, setLoading, 'fetch') + } + } + + const handleActionChange = e => { + const newConf = { ...fabmanConf } + newConf.actionName = e.target.value + setFabmanConf(newConf) + + const requiresMemberSelection = + e.target.value === 'update_member' || e.target.value === 'delete_member' + if (requiresMemberSelection && fabmanConf.selectedWorkspace) { + fetchFabmanMembers(newConf, setFabmanConf, loading, setLoading, 'fetch') + } + } + + return ( +
+ +
+ +
+ + {/* STEP 1 */} + + + {/* STEP 2 */} + {step === 2 && ( + + )} + + {/* STEP 3 */} + {step === 3 && ( + + )} +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx new file mode 100644 index 000000000..8e6706a44 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx @@ -0,0 +1,13 @@ +/* eslint-disable no-param-reassign */ + +import { __ } from '../../../Utils/i18nwrap' + +export default function FabmanActions({ fabmanConf, setFabmanConf, loading, setLoading }) { + return ( +
+
+ {__('No additional utilities available for this action.', 'bit-integrations')} +
+
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx new file mode 100644 index 000000000..9a584e895 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -0,0 +1,120 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable no-unused-expressions */ +import { useState } from 'react' +import { __ } from '../../../Utils/i18nwrap' +import LoaderSm from '../../Loaders/LoaderSm' +import { fabmanAuthentication } from './FabmanCommonFunc' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import TutorialLink from '../../Utilities/TutorialLink' + +export default function FabmanAuthorization({ + fabmanConf, + setFabmanConf, + step, + setStep, + loading, + setLoading, + isInfo +}) { + const [isAuthorized, setIsAuthorized] = useState(false) + const [error, setError] = useState({ name: '', apiKey: '' }) + const { fabman } = tutorialLinks + + const nextPage = () => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + + setStep(2) + } + + const handleInput = e => { + const newConf = { ...fabmanConf } + const rmError = { ...error } + rmError[e.target.name] = '' + newConf[e.target.name] = e.target.value + setError(rmError) + setFabmanConf(newConf) + } + + return ( +
+ {fabman?.youTubeLink && } + {fabman?.docLink && } + +
+ {__('Integration Name:', 'bit-integrations')} +
+ + +
+ {__('API Key:', 'bit-integrations')} +
+ +
+ {error.apiKey} +
+ + {__('To Get API Key, Please Visit', 'bit-integrations')} +   + + {__('Fabman API Documentation', 'bit-integrations')} + + +
+
+ + {!isInfo && ( +
+ +
+ +
+ )} +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js new file mode 100644 index 000000000..32cb41912 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -0,0 +1,161 @@ +/* eslint-disable no-console */ +/* eslint-disable no-else-return */ +import toast from 'react-hot-toast' +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' + +export const handleInput = (e, fabmanConf, setFabmanConf) => { + const newConf = { ...fabmanConf } + const { name } = e.target + if (e.target.value !== '') { + newConf[name] = e.target.value + } else { + delete newConf[name] + } + setFabmanConf({ ...newConf }) +} + +export const generateMappedField = fabmanConf => { + const requiredFlds = fabmanConf?.staticFields.filter(fld => !!fld.required) + return requiredFlds.length > 0 + ? requiredFlds.map(field => ({ formField: '', fabmanFormField: field.key })) + : [{ formField: '', fabmanFormField: '' }] +} + +export const checkMappedFields = fabmanConf => { + const mappedFields = fabmanConf?.field_map + ? fabmanConf.field_map.filter( + mappedField => + !mappedField.formField || + !mappedField.fabmanFormField || + (mappedField.formField === 'custom' && !mappedField.customValue) + ) + : [] + if (mappedFields.length > 0) { + return false + } + return true +} + +export const fabmanAuthentication = ( + confTmp, + setConf, + setError, + setIsAuthorized, + loading, + setLoading, + type +) => { + if (!confTmp.apiKey) { + setError({ apiKey: !confTmp.apiKey ? __("API key can't be empty", 'bit-integrations') : '' }) + return + } + + setError({}) + + if (type === 'authentication') { + setLoading({ ...loading, auth: true }) + } + + const requestParams = { apiKey: confTmp.apiKey } + + bitsFetch(requestParams, 'fabman_authorization').then(result => { + if (result && result.success) { + const newConf = { ...confTmp } + setIsAuthorized(true) + if (type === 'authentication') { + if (result.data) { + newConf.customFields = result.data + + if (result.data && result.data.length > 0) { + newConf.accountId = result.data[0].id + } + } + setConf(newConf) + setLoading({ ...loading, auth: false }) + toast.success(__('Authorized Successfully', 'bit-integrations')) + + fetchFabmanWorkspaces(newConf, setConf, loading, setLoading, 'fetch') + } + return + } + setLoading({ ...loading, auth: false }) + toast.error(__('Authorization Failed', 'bit-integrations')) + }) +} + +export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, type = 'fetch') => { + if (!confTmp.apiKey) { + toast.error(__('API key is required to fetch workspaces', 'bit-integrations')) + return + } + + if (type === 'fetch') { + setLoading({ ...loading, workspaces: true }) + } else if (type === 'refresh') { + setLoading({ ...loading, workspaces: true }) + } + + const requestParams = { apiKey: confTmp.apiKey } + + bitsFetch(requestParams, 'fabman_fetch_workspaces').then(result => { + if (result && result.success) { + const newConf = { ...confTmp } + // Fix: Check for result.data.workspaces instead of result.data + if (result.data && result.data.workspaces && Array.isArray(result.data.workspaces)) { + newConf.workspaces = result.data.workspaces + // Auto-select if only one workspace + if (result.data.workspaces.length === 1) { + newConf.selectedWorkspace = result.data.workspaces[0].id + } + } + setConf(newConf) + setLoading({ ...loading, workspaces: false }) + if (type === 'refresh') { + toast.success(__('Workspaces refreshed successfully', 'bit-integrations')) + } else { + toast.success(__('Workspaces fetched successfully', 'bit-integrations')) + } + return + } + setLoading({ ...loading, workspaces: false }) + toast.error(__('Failed to fetch workspaces', 'bit-integrations')) + }) +} + +export const fetchFabmanMembers = (confTmp, setConf, loading, setLoading, type = 'fetch') => { + if (!confTmp.apiKey || !confTmp.selectedWorkspace) { + toast.error(__('API key and workspace are required to fetch members', 'bit-integrations')) + return + } + + if (type === 'fetch') { + setLoading({ ...loading, members: true }) + } else if (type === 'refresh') { + setLoading({ ...loading, members: true }) + } + + const requestParams = { + apiKey: confTmp.apiKey, + workspaceId: confTmp.selectedWorkspace + } + + bitsFetch(requestParams, 'fabman_fetch_members').then(result => { + if (result && result.success) { + const newConf = { ...confTmp } + if (result.data && result.data.members && Array.isArray(result.data.members)) { + newConf.members = result.data.members + } + setConf(newConf) + setLoading({ ...loading, members: false }) + if (type === 'refresh') { + toast.success(__('Members refreshed successfully', 'bit-integrations')) + } else { + toast.success(__('Members fetched successfully', 'bit-integrations')) + } + return + } + setLoading({ ...loading, members: false }) + toast.error(__('Failed to fetch members', 'bit-integrations')) + }) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx new file mode 100644 index 000000000..de3511ec1 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx @@ -0,0 +1,105 @@ +/* eslint-disable no-console */ +import { useRecoilValue } from 'recoil' +import { __, sprintf } from '../../../Utils/i18nwrap' +import { addFieldMap, delFieldMap, handleFieldMapping } from './IntegrationHelpers' +import { SmartTagField } from '../../../Utils/StaticData/SmartTagField' +import { $btcbi } from '../../../GlobalStates' +import { generateMappedField } from './FabmanCommonFunc' +import TagifyInput from '../../Utilities/TagifyInput' +import { handleCustomValue } from '../IntegrationHelpers/IntegrationHelpers' + +export default function FabmanFieldMap({ i, formFields, field, fabmanConf, setFabmanConf }) { + const requiredFields = fabmanConf?.staticFields.filter(fld => !!fld.required) || [] + const nonRequriedFields = fabmanConf?.staticFields?.filter(fld => !fld.required) || [] + // Fix: Handle case when customFields is not an array + const customFields = Array.isArray(fabmanConf.customFields) ? fabmanConf.customFields : [] + const allNonrequriedFields = [...nonRequriedFields, ...customFields] + + const btcbi = useRecoilValue($btcbi) + const { isPro } = btcbi + + return ( +
+
+
+ + + {field.formField === 'custom' && ( + handleCustomValue(e, i, fabmanConf, setFabmanConf)} + label={__('Custom Value', 'bit-integrations')} + className="mr-2" + type="text" + value={field.customValue} + placeholder={__('Custom Value', 'bit-integrations')} + formFields={formFields} + /> + )} + + +
+ {i >= requiredFields.length && ( + <> + + + + )} +
+
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx new file mode 100644 index 000000000..2329e5457 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -0,0 +1,261 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable react-hooks/exhaustive-deps */ +import { __ } from '../../../Utils/i18nwrap' +import FabmanFieldMap from './FabmanFieldMap' +import { addFieldMap } from './IntegrationHelpers' +import FabmanActions from './FabmanActions' +import { fetchFabmanWorkspaces, fetchFabmanMembers, generateMappedField } from './FabmanCommonFunc' +import Loader from '../../Loaders/Loader' +import { useEffect } from 'react' + +export default function FabmanIntegLayout({ + formFields, + fabmanConf, + setFabmanConf, + loading, + setLoading, + setSnackbar +}) { + useEffect(() => { + if ( + fabmanConf?.actionName && + fabmanConf?.selectedWorkspace && + Array.isArray(fabmanConf.field_map) && + fabmanConf.field_map.length === 1 && + (fabmanConf.field_map[0].fabmanFormField === '' || + fabmanConf.field_map[0].fabmanFormField === undefined) + ) { + const newConf = { ...fabmanConf } + newConf.field_map = generateMappedField(newConf) + setFabmanConf(newConf) + } + }, [fabmanConf.actionName, fabmanConf.selectedWorkspace]) + + const actions = [ + { label: __('Create Member', 'bit-integrations'), value: 'create_member' }, + { label: __('Update Member', 'bit-integrations'), value: 'update_member' }, + { label: __('Delete Member', 'bit-integrations'), value: 'delete_member' } + ] + + const handleActionChange = e => { + const newConf = { ...fabmanConf } + const value = e.target.value + newConf.actionName = value + setFabmanConf(newConf) + + const requiresMemberSelection = value === 'update_member' || value === 'delete_member' + if (requiresMemberSelection && newConf.selectedWorkspace) { + fetchFabmanMembers(newConf, setFabmanConf, loading, setLoading, 'fetch') + } + } + + const handleWorkspaceChange = e => { + const newConf = { ...fabmanConf } + newConf.selectedWorkspace = e.target.value + setFabmanConf(newConf) + + const requiresMemberSelection = + newConf.actionName === 'update_member' || newConf.actionName === 'delete_member' + if (requiresMemberSelection && newConf.selectedWorkspace) { + fetchFabmanMembers(newConf, setFabmanConf, loading, setLoading, 'fetch') + } + } + + const handleMemberChange = e => { + const newConf = { ...fabmanConf } + const selectedValue = e.target.value + + if (selectedValue) { + const [memberId, lockVersion] = selectedValue.split('|') + newConf.selectedMember = memberId + newConf.selectedLockVersion = lockVersion + } else { + delete newConf.selectedMember + delete newConf.selectedLockVersion + } + + setFabmanConf(newConf) + } + + const handleRefreshWorkspaces = () => { + fetchFabmanWorkspaces(fabmanConf, setFabmanConf, loading, setLoading, 'refresh') + } + + const handleRefreshMembers = () => { + fetchFabmanMembers(fabmanConf, setFabmanConf, loading, setLoading, 'refresh') + } + + // Check if action requires member selection + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + + return ( +
+
+ + {/* Action Selector */} +
+ {__('Action:', 'bit-integrations')} + +
+
+ + {/* Workspace Selector - Only visible after action is selected */} + {fabmanConf.actionName && ( + <> +
+ {__('Select Workspace:', 'bit-integrations')} + + +
+ + {loading.workspaces && ( + + )} +
+ + )} + + {/* Member Selector - Only visible for update/delete actions after workspace is selected */} + {requiresMemberSelection && fabmanConf.selectedWorkspace && ( + <> +
+ {__('Select Member:', 'bit-integrations')} + + +
+ + {loading.members && ( + + )} +
+ + )} + + {/* Field Map - Only visible after all required selections are made */} + {fabmanConf.actionName && fabmanConf.selectedWorkspace && ( + <> +
+ {__('Field Map', 'bit-integrations')} +
+
+
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Fabman Fields', 'bit-integrations')} +
+
+ + {fabmanConf?.field_map.map((itm, i) => ( + + ))} +
+
+ +
+
+
+
+ {__('Utilities', 'bit-integrations')} +
+
+ +
+ + )} +
+ ) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx new file mode 100644 index 000000000..ea74bd386 --- /dev/null +++ b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx @@ -0,0 +1,26 @@ +/* eslint-disable no-unused-expressions */ + +export const addFieldMap = (i, confTmp, setConf) => { + const newConf = { ...confTmp } + newConf.field_map.splice(i, 0, {}) + setConf({ ...newConf }) +} + +export const delFieldMap = (i, confTmp, setConf) => { + const newConf = { ...confTmp } + if (newConf.field_map.length > 1) { + newConf.field_map.splice(i, 1) + } + + setConf({ ...newConf }) +} + +export const handleFieldMapping = (event, index, conftTmp, setConf) => { + const newConf = { ...conftTmp } + newConf.field_map[index][event.target.name] = event.target.value + + if (event.target.value === 'custom') { + newConf.field_map[index].customValue = '' + } + setConf({ ...newConf }) +} diff --git a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx index f7a52f40d..a045b6efa 100644 --- a/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend-dev/src/components/AllIntegrations/IntegInfo.jsx @@ -85,6 +85,7 @@ const MailupAuthentication = lazy(() => import('./Mailup/MailupAuthorization')) const NotionAuthorization = lazy(() => import('./Notion/NotionAuthorization')) const MailjetAuthorization = lazy(() => import('./Mailjet/MailjetAuthorization')) const SendGridAuthorization = lazy(() => import('./SendGrid/SendGridAuthorization')) +const FabmanAuthorization = lazy(() => import('./Fabman/FabmanAuthorization')) const PCloudAuthorization = lazy(() => import('./PCloud/PCloudAuthorization')) const EmailOctopusAuthorization = lazy(() => import('./EmailOctopus/EmailOctopusAuthorization')) const CustomAction = lazy(() => import('./CustomAction/CustomFuncEditor')) @@ -393,6 +394,8 @@ export default function IntegInfo() { return case 'SendGrid': return + case 'Fabman': + return case 'PCloud': return case 'EmailOctopus': diff --git a/frontend-dev/src/components/AllIntegrations/NewInteg.jsx b/frontend-dev/src/components/AllIntegrations/NewInteg.jsx index 1ef5ca984..6f76e1d2d 100644 --- a/frontend-dev/src/components/AllIntegrations/NewInteg.jsx +++ b/frontend-dev/src/components/AllIntegrations/NewInteg.jsx @@ -93,6 +93,7 @@ const Mailup = lazy(() => import('./Mailup/Mailup')) const Notion = lazy(() => import('./Notion/Notion')) const Mailjet = lazy(() => import('./Mailjet/Mailjet')) const SendGrid = lazy(() => import('./SendGrid/SendGrid')) +const Fabman = lazy(() => import('./Fabman/Fabman')) const PCloud = lazy(() => import('./PCloud/PCloud')) const EmailOctopus = lazy(() => import('./EmailOctopus/EmailOctopus')) const Smaily = lazy(() => import('./Smaily/Smaily')) @@ -903,6 +904,15 @@ export default function NewInteg({ allIntegURL }) { setFlow={setFlow} /> ) + case 'Fabman': + return ( + + ) case 'PCloud': return ( 'Fabman', + 'title' => __('Fabman', 'bit-integrations'), + 'type' => 'action', + 'integrationID' => 0 + ]; + } + + public static function authorization($requestParams) + { + if (empty($requestParams->apiKey)) { + wp_send_json_error(__('API Key is required', 'bit-integrations'), 400); + } + + $header = [ + 'Authorization' => 'Bearer ' . $requestParams->apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiEndpoint = 'https://fabman.io/api/v1/accounts'; + $apiResponse = HttpHelper::get($apiEndpoint, null, $header); + + if (is_wp_error($apiResponse)) { + wp_send_json_error($apiResponse->get_error_message(), 400); + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Invalid API credentials', 'bit-integrations'), 400); + } + + $accountId = $apiResponse[0]->id; + wp_send_json_success([ + 'accountId' => $accountId + ], 200); + } + + public static function fetchWorkspaces($requestParams) + { + $header = [ + 'Authorization' => 'Bearer ' . $requestParams->apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiEndpoint = 'https://fabman.io/api/v1/spaces'; + $apiResponse = HttpHelper::get($apiEndpoint, null, $header); + + if (is_wp_error($apiResponse)) { + wp_send_json_error($apiResponse->get_error_message(), 400); + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Failed to fetch workspaces', 'bit-integrations'), 400); + } + + wp_send_json_success([ + 'workspaces' => $apiResponse + ], 200); + } + + public static function fetchMembers($requestParams) + { + if (empty($requestParams->apiKey) || empty($requestParams->workspaceId)) { + wp_send_json_error(__('API Key and Workspace ID are required', 'bit-integrations'), 400); + } + + $header = [ + 'Authorization' => 'Bearer ' . $requestParams->apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiEndpoint = 'https://fabman.io/api/v1/members'; + $apiResponse = HttpHelper::get($apiEndpoint, null, $header); + + if (is_wp_error($apiResponse)) { + wp_send_json_error($apiResponse->get_error_message(), 400); + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Failed to fetch members', 'bit-integrations'), 400); + } + + wp_send_json_success([ + 'members' => $apiResponse + ], 200); + } + + public static function execute($integrationData, $fieldValues) + { + $integrationDetails = $integrationData->flow_details; + $apiKey = $integrationDetails->apiKey; + $selectedWorkspace = $integrationDetails->selectedWorkspace; + $accountId = $integrationDetails->accountId; + $memberId = $integrationDetails->selectedMember; + $integId = $integrationData->id; + $actionName = $integrationDetails->actionName; + $lockVersion = $integrationDetails->selectedLockVersion; + + if (empty($apiKey) || empty($selectedWorkspace) || empty($accountId) || empty($actionName)) { + return new WP_Error('REQ_FIELD_EMPTY', __('API Key, Account ID, Workspace ID, and Action are required for Fabman', 'bit-integrations')); + } + + $recordApiHelper = new RecordApiHelper($apiKey, $selectedWorkspace, $accountId, $integId, $memberId, $lockVersion); + $fabmanApiResponse = $recordApiHelper->execute($actionName, $fieldValues, $integrationDetails); + + if (is_wp_error($fabmanApiResponse)) { + return $fabmanApiResponse; + } + + return $fabmanApiResponse; + } +} diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php new file mode 100644 index 000000000..0be0a428d --- /dev/null +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -0,0 +1,144 @@ +_integrationID = $integrationID; + $this->_apiKey = $apiKey; + $this->_workspaceId = $workspaceId; + $this->_accountId = $accountId; + $this->_memberId = $memberId; + $this->_lockVersion = $lockVersion; + $this->_apiEndpoint = 'https://fabman.io/api/v1'; + } + + public function execute($actionName, $fieldValues, $integrationDetails) + { + $finalData = []; + $finalData['space'] = $this->_workspaceId; + $finalData['account'] = $this->_accountId; + + if ($this->_memberId) { + $finalData['memberId'] = $this->_memberId; + } + + if (!empty($integrationDetails->field_map)) { + foreach ($integrationDetails->field_map as $fieldMap) { + if (!empty($fieldMap->formField) && !empty($fieldMap->fabmanFormField)) { + $finalData[$fieldMap->fabmanFormField] = $fieldMap->formField === 'custom' + ? $fieldMap->customValue + : $fieldValues[$fieldMap->formField]; + } + } + } + + switch ($actionName) { + case 'create_member': + return $this->createMember($finalData); + case 'update_member': + return $this->updateMember($finalData); + case 'delete_member': + return $this->deleteMember($finalData); + default: + return new WP_Error('INVALID_ACTION', __('Invalid action name', 'bit-integrations')); + } + } + + private function createMember($data) + { + unset($data['memberId']); + $apiEndpoint = $this->_apiEndpoint . '/members'; + $header = [ + 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiResponse = HttpHelper::post($apiEndpoint, json_encode($data), $header); + error_log(print_r($apiResponse, true)); + + if (is_wp_error($apiResponse)) { + return $apiResponse; + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : __('Failed to create member', 'bit-integrations')); + } + + return $apiResponse; + } + + private function updateMember($data) + { + $data['lockVersion'] = $this->_lockVersion; + if (empty($this->_memberId)) { + return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for update operation', 'bit-integrations')); + } + + error_log(print_r($data, true)); + + $apiEndpoint = $this->_apiEndpoint . '/members/' . $this->_memberId; + $header = [ + 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Content-Type' => 'application/json' + ]; + + error_log(print_r($apiEndpoint, true)); + + $apiResponse = HttpHelper::put($apiEndpoint, json_encode($data), $header); + error_log(print_r($apiResponse, true)); + + if (is_wp_error($apiResponse)) { + return $apiResponse; + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : __('Failed to update member', 'bit-integrations')); + } + + return $apiResponse; + } + + private function deleteMember() + { + if (empty($this->_memberId)) { + return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for delete operation', 'bit-integrations')); + } + + $apiEndpoint = $this->_apiEndpoint . '/members/' . $this->_memberId; + $header = [ + 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiResponse = HttpHelper::request($apiEndpoint, 'DELETE', null, $header); + if (is_wp_error($apiResponse)) { + return $apiResponse; + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : __('Failed to delete member', 'bit-integrations')); + } + + return $apiResponse; + } +} diff --git a/includes/Actions/Fabman/Routes.php b/includes/Actions/Fabman/Routes.php new file mode 100644 index 000000000..ca06065ca --- /dev/null +++ b/includes/Actions/Fabman/Routes.php @@ -0,0 +1,12 @@ + Date: Mon, 8 Sep 2025 12:02:41 +0600 Subject: [PATCH 02/13] chore: update logo and update functionality --- .../AllIntegrations/Fabman/EditFabman.jsx | 32 +- .../AllIntegrations/Fabman/Fabman.jsx | 286 ++++++++++++------ .../AllIntegrations/Fabman/FabmanActions.jsx | 8 +- .../Fabman/FabmanAuthorization.jsx | 21 +- .../Fabman/FabmanCommonFunc.js | 32 +- .../Fabman/FabmanIntegLayout.jsx | 170 +++++++---- .../src/resource/img/integ/fabman.webp | Bin 145 -> 4374 bytes includes/Actions/Fabman/FabmanController.php | 12 +- includes/Actions/Fabman/RecordApiHelper.php | 65 +++- 9 files changed, 425 insertions(+), 201 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index 26aca3dda..2ed3b1dd6 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -33,10 +33,29 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - if (!checkMappedFields(fabmanConf)) { + const isDeleteAction = fabmanConf.actionName === 'delete_member' + if (!isDeleteAction && !checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } + + const requiresWorkspaceSelection = + fabmanConf.actionName === 'update_member' || + fabmanConf.actionName === 'delete_member' || + fabmanConf.actionName === 'update_spaces' + + if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { + setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + return + } + + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + if (requiresMemberSelection && !fabmanConf.selectedMember) { + setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + return + } + saveActionConf({ flow, allIntegURL, @@ -80,7 +99,16 @@ function EditFabman({ allIntegURL }) { { if (!checkMappedFields(fabmanConf)) { - setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } if (!fabmanConf.actionName) { - setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - if (!fabmanConf.selectedWorkspace) { - setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + const requiresWorkspaceSelection = + fabmanConf.actionName === 'create_member' || + fabmanConf.actionName === 'update_member' || + fabmanConf.actionName === 'delete_member' || + fabmanConf.actionName === 'update_spaces' + + if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { + setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } const requiresMemberSelection = fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) return } - setIsLoading(true) - const integConfig = { - apiKey: fabmanConf.apiKey, - selectedWorkspace: fabmanConf.selectedWorkspace, - selectedMember: fabmanConf.selectedMember, - actionName: fabmanConf.actionName, - field_map: fabmanConf.field_map, - staticFields: fabmanConf.staticFields, - customFields: fabmanConf.customFields, - actions: fabmanConf.actions, - condition: fabmanConf.condition, - trigger_type: fabmanConf.trigger_type, - pro_integ_v: fabmanConf.pro_integ_v, - fields: fabmanConf.fields - } - - saveIntegConfig(integConfig, formID) - .then(() => { - setSnackbar({ show: true, msg: __('Fabman integration saved successfully', 'bit-integrations') }) - setStep(3) - }) - .catch(() => { - setSnackbar({ show: true, msg: __('Something went wrong', 'bit-integrations') }) - }) - .finally(() => { - setIsLoading(false) - }) + saveIntegConfig(flow, setFlow, allIntegURL, fabmanConf, navigate, '', '', setIsLoading) } const nextPage = () => { if (!checkMappedFields(fabmanConf)) { - setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } if (!fabmanConf.actionName) { - setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - if (!fabmanConf.selectedWorkspace) { - setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + const requiresWorkspaceSelection = + fabmanConf.actionName === 'create_member' || + fabmanConf.actionName === 'update_member' || + fabmanConf.actionName === 'delete_member' || + fabmanConf.actionName === 'update_spaces' + + if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { + setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } const requiresMemberSelection = fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) return } @@ -171,9 +246,9 @@ export default function Fabman({ formFields, formID, setSnackbar }) { } return ( -
+
-
+
@@ -189,27 +264,54 @@ export default function Fabman({ formFields, formID, setSnackbar }) { {/* STEP 2 */} {step === 2 && ( - +
+ + + +
)} {/* STEP 3 */} - {step === 3 && ( - - )} + + saveIntegConfig(flow, setFlow, allIntegURL, fabmanConf, navigate, '', '', setIsLoading) + } + isLoading={isLoading} + dataConf={fabmanConf} + setDataConf={setFabmanConf} + formFields={formFields} + />
) } diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx index 8e6706a44..73cfa0f35 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx @@ -3,11 +3,5 @@ import { __ } from '../../../Utils/i18nwrap' export default function FabmanActions({ fabmanConf, setFabmanConf, loading, setLoading }) { - return ( -
-
- {__('No additional utilities available for this action.', 'bit-integrations')} -
-
- ) + return
} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 9a584e895..b9670c62c 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -4,6 +4,7 @@ import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import LoaderSm from '../../Loaders/LoaderSm' import { fabmanAuthentication } from './FabmanCommonFunc' +import Note from '../../Utilities/Note' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import TutorialLink from '../../Utilities/TutorialLink' @@ -72,15 +73,6 @@ export default function FabmanAuthorization({
{error.apiKey}
- - {__('To Get API Key, Please Visit', 'bit-integrations')} -   - - {__('Fabman API Documentation', 'bit-integrations')} - - -
-
{!isInfo && (
@@ -104,7 +96,7 @@ export default function FabmanAuthorization({ : __('Authorize', 'bit-integrations')} {loading.auth && } -
+
)} +
) } + +const fabmanApiKeyNote = `

${__('To get your Fabman API key:', 'bit-integrations')}

+
    +
  • ${__('Log in to your Fabman account.', 'bit-integrations')}
  • +
  • ${__('Go to "Configure" → "Integrations (API & Webhooks)".', 'bit-integrations')}
  • +
  • ${__('Click "Create API key", add a title, and choose a member.', 'bit-integrations')}
  • +
  • ${__('Save, then click "Reveal" to copy your API key.', 'bit-integrations')}
  • +
` diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js index 32cb41912..1c0653722 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -23,18 +23,16 @@ export const generateMappedField = fabmanConf => { } export const checkMappedFields = fabmanConf => { - const mappedFields = fabmanConf?.field_map - ? fabmanConf.field_map.filter( - mappedField => - !mappedField.formField || - !mappedField.fabmanFormField || - (mappedField.formField === 'custom' && !mappedField.customValue) - ) - : [] - if (mappedFields.length > 0) { + const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] + const invalid = rows.filter(r => { + const hasAnySide = + (r?.formField && r.formField !== '') || (r?.fabmanFormField && r.fabmanFormField !== '') + if (!hasAnySide) return false + if (!r.formField || !r.fabmanFormField) return true + if (r.formField === 'custom' && !r.customValue) return true return false - } - return true + }) + return invalid.length === 0 } export const fabmanAuthentication = ( @@ -64,12 +62,8 @@ export const fabmanAuthentication = ( const newConf = { ...confTmp } setIsAuthorized(true) if (type === 'authentication') { - if (result.data) { - newConf.customFields = result.data - - if (result.data && result.data.length > 0) { - newConf.accountId = result.data[0].id - } + if (result.data && result.data.accountId) { + newConf.accountId = result.data.accountId } setConf(newConf) setLoading({ ...loading, auth: false }) @@ -101,10 +95,10 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ bitsFetch(requestParams, 'fabman_fetch_workspaces').then(result => { if (result && result.success) { const newConf = { ...confTmp } - // Fix: Check for result.data.workspaces instead of result.data + if (result.data && result.data.workspaces && Array.isArray(result.data.workspaces)) { newConf.workspaces = result.data.workspaces - // Auto-select if only one workspace + if (result.data.workspaces.length === 1) { newConf.selectedWorkspace = result.data.workspaces[0].id } diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx index 2329e5457..af2ed2403 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -19,14 +19,14 @@ export default function FabmanIntegLayout({ useEffect(() => { if ( fabmanConf?.actionName && - fabmanConf?.selectedWorkspace && + (fabmanConf?.selectedWorkspace || fabmanConf?.actionName === 'create_spaces') && Array.isArray(fabmanConf.field_map) && fabmanConf.field_map.length === 1 && (fabmanConf.field_map[0].fabmanFormField === '' || fabmanConf.field_map[0].fabmanFormField === undefined) ) { const newConf = { ...fabmanConf } - newConf.field_map = generateMappedField(newConf) + newConf.field_map = generateMappedField({ staticFields: getActiveStaticFields(fabmanConf) }) setFabmanConf(newConf) } }, [fabmanConf.actionName, fabmanConf.selectedWorkspace]) @@ -34,13 +34,56 @@ export default function FabmanIntegLayout({ const actions = [ { label: __('Create Member', 'bit-integrations'), value: 'create_member' }, { label: __('Update Member', 'bit-integrations'), value: 'update_member' }, - { label: __('Delete Member', 'bit-integrations'), value: 'delete_member' } + { label: __('Delete Member', 'bit-integrations'), value: 'delete_member' }, + { label: __('Create Spaces', 'bit-integrations'), value: 'create_spaces' }, + { label: __('Update Spaces', 'bit-integrations'), value: 'update_spaces' } ] + const getActiveStaticFields = conf => + conf.actionName === 'create_spaces' || conf.actionName === 'update_spaces' + ? conf.spacesStaticFields + : conf.memberStaticFields + + // Ensure required targets are present in field_map for validation (especially in edit/update) + useEffect(() => { + const staticFields = getActiveStaticFields(fabmanConf) || [] + const requiredFields = staticFields.filter(f => !!f.required) + if (!Array.isArray(fabmanConf.field_map)) return + + let changed = false + const newFieldMap = [...fabmanConf.field_map] + + // Ensure length covers required fields + for (let i = 0; i < requiredFields.length; i += 1) { + if (!newFieldMap[i]) { + newFieldMap[i] = { formField: '', fabmanFormField: requiredFields[i].key } + changed = true + } else if (newFieldMap[i].fabmanFormField !== requiredFields[i].key) { + newFieldMap[i] = { ...newFieldMap[i], fabmanFormField: requiredFields[i].key } + changed = true + } + } + + if (changed) { + const newConf = { ...fabmanConf, field_map: newFieldMap } + setFabmanConf(newConf) + } + }, [fabmanConf.actionName, fabmanConf.selectedWorkspace, fabmanConf.field_map]) + const handleActionChange = e => { const newConf = { ...fabmanConf } const value = e.target.value newConf.actionName = value + + // Reset member related selects for space actions + if (value === 'create_spaces' || value === 'update_spaces') { + delete newConf.selectedMember + delete newConf.selectedLockVersion + } + + // Reset field map when switching groups + newConf.field_map = [{ formField: '', fabmanFormField: '' }] + setFabmanConf(newConf) const requiresMemberSelection = value === 'update_member' || value === 'delete_member' @@ -52,6 +95,12 @@ export default function FabmanIntegLayout({ const handleWorkspaceChange = e => { const newConf = { ...fabmanConf } newConf.selectedWorkspace = e.target.value + + const ws = (fabmanConf?.workspaces || []).find(w => String(w.id) === String(e.target.value)) + if (ws && typeof ws.lockVersion !== 'undefined') { + newConf.selectedLockVersion = ws.lockVersion + } + setFabmanConf(newConf) const requiresMemberSelection = @@ -85,10 +134,14 @@ export default function FabmanIntegLayout({ fetchFabmanMembers(fabmanConf, setFabmanConf, loading, setLoading, 'refresh') } - // Check if action requires member selection const requiresMemberSelection = fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + const isSpaceAction = + fabmanConf.actionName === 'create_spaces' || fabmanConf.actionName === 'update_spaces' + + const activeStaticFields = getActiveStaticFields(fabmanConf) + return (

@@ -111,8 +164,8 @@ export default function FabmanIntegLayout({

- {/* Workspace Selector - Only visible after action is selected */} - {fabmanConf.actionName && ( + {/* Workspace Selector - visible after action selected (required for all except create_spaces) */} + {fabmanConf.actionName && (!isSpaceAction || fabmanConf.actionName === 'update_spaces') && ( <>
{__('Select Workspace:', 'bit-integrations')} @@ -154,7 +207,7 @@ export default function FabmanIntegLayout({ )} - {/* Member Selector - Only visible for update/delete actions after workspace is selected */} + {/* Member Selector - Only for update/delete member actions */} {requiresMemberSelection && fabmanConf.selectedWorkspace && ( <>
@@ -202,60 +255,63 @@ export default function FabmanIntegLayout({ )} - {/* Field Map - Only visible after all required selections are made */} - {fabmanConf.actionName && fabmanConf.selectedWorkspace && ( - <> -
- {__('Field Map', 'bit-integrations')} -
-
-
-
-
- {__('Form Fields', 'bit-integrations')} + {/* Field Map - Visible after required selections are made */} + {fabmanConf.actionName && + (!isSpaceAction || + (isSpaceAction && + (fabmanConf.actionName !== 'update_spaces' || fabmanConf.selectedWorkspace))) && ( + <> +
+ {__('Field Map', 'bit-integrations')}
-
- {__('Fabman Fields', 'bit-integrations')} +
+
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Fabman Fields', 'bit-integrations')} +
-
- {fabmanConf?.field_map.map((itm, i) => ( - - ))} -
-
- + {fabmanConf?.field_map.map((itm, i) => ( + + ))} +
+
+ +
+
+
+
+ {__('Utilities', 'bit-integrations')} +
+
+
-
-
-
- {__('Utilities', 'bit-integrations')} -
-
- -
- - )} + + )}
) } diff --git a/frontend-dev/src/resource/img/integ/fabman.webp b/frontend-dev/src/resource/img/integ/fabman.webp index 0fea847d924f834dffd9f480ec71468594231de0..410c01372ca44041f74b01c9f6747e930b669401 100644 GIT binary patch literal 4374 zcmaKq1z42Z*2iC9sG%f9Kp7eoK@b5^iJ>H=OB|Y^yX(;1Idn^dG#rqwA(Rf0?v@6r zJDzi&d+t5o{qEXpt^M0;|DSi)t0W~MK}ZY$>f%rZH3fbRFaQA0P>~1#WdI;0Dyqc0L}`|D0@^z8Kh_#6n8(T$A(;HUrq2s8nJbQl1@@Bf%X zrT=Gd5Y!Y6YG2l<1_vwvBLD(O0akzkz=0xe)T43%yr>tJr6mFY&P3rS_k=ng*I6=M zE#2)8x8jqZNP}SnJ(kojkJde5A?N+iXQhu|^=RSrQc5b-L5YdZ3CDLdL&S!&%NdJd z_BZAO6IjdZ}GrcPhP=!=HJ+~wIkRC?pg(O7s;L(g4z%cf?fjb7AP2K z5uCTF&?DFz$=RXo(g_N9_TGVpvIByKrAaDL{ly@Q0bRnogUWBvl*o9l7y1B=3yF`R z2NFXM=f(r>Ar^XBfw9jW!f+m>G#s=u$txMVEF1Ij2O~!!&9{AHDbYr$x*0f$S)E-% z6I=Jqf}*jQMwduua&Mb&%ey_5RFLv)^T$ z!~U#HzSCLv1aht%_BnF3C z2Um_JcFBj%S6P`a$9+7GJT%AZO=XEi=YM{irsnHx2*SS4r+JLzYDnt5mDo(2bB?0E z;ow{4n;?!#+*RAB!1eNXs}X(E4_nQx4R(%F{~kk~s2WjuW7b>$Y3b32Z*NjK49y-@ zI4ub|Ao9VqE-hE?@#6_Yv=9xr&VVneUa+6?)c+?!NU9I3G)aJldx?f^YAmP0sB{YZ^A0d14z@6J` zXUZ!vamn3sh_N>CwtWY;cGJ_rTMqLu-F~xMcqyQMD=6D~&eA&T+wzE_Xg?|LSwKXn z)~G~OUFWd1T3v>@AA1jbrbFNijQ@Ors$Pv%CR7~`skMpBs2O%NnF2K9*45TH{{Eg ziv~_dCTpDbM;_xX7<~^(NGgdIm3_b0#Go7O^3*q(F^7P23c^Zw^>vKWLyjN!qvzTo zdc?i;z9I5HCg7ni`4)mYFshw`%H@>nOM&<6Dtd~|Z=&yFtRIH97^KM)6K?{DmOx3& zaddMA|C}zDI61VKAv9j1%gn$WxH6)vzych)2KU|UaFTi6fZpA;6z~4X|9)l%l8n5t z0&{)^zo&~tc1b%ljKSNXHNCTljI?to&GVfkdufMVwxm6O9CR6{3vP%!7UcsMzg-p! z4dEKF0gM+FM*|CNA7OS?`Z5U~h>p9K7aX=4fF>ezv_FL#%BTb z>oP<9kzDu}hcw?Y%R}aHOG$ACFix03{HqR}AH|2tQT55 zoEE&O-Zs5Vy18aCLa+^EzjGlTH;uIZes|FruO!X3c6?40(aLHi3{@v=F1JY&Xy>f9 z$A*f@ZyRc_@FX9WqC_M2Z=Jao0EXJwnAb6Vct>6!4NQg7zUU5!*rxi)E1^WC(9_kq z4^T803qjwjwunxR)F6^a1UEnH4cG?PyPHgkyN~3s@{Az>Ar9MZpJFQ=Od^z-hrzR2 z?K9CTURWllVwj!L5fcz^VmIs8T+-+pXbZFT!WRT& zpMSe=dKZy^RE|7&%25;i;={%1x8it`HRQ7r7nW&K?ZoUkc|cpF^+c*`>bY4btY_9%77d#4Tds>f2k?o(u)nWjq)!9yvjVgxc$?dtaHeVt5sb7=TfB;oRs3XROs zEEzOzl;%}2Or9Mc9hf34UgF(!a5D+?%nLDj3}xk!(4J+V=4z5WcXKGOe!HugmZ8d4 zWLPnGe(~5O%O>y0kuCq%U-7qOq!o!vdbiTOTbv@F9l5R;p*?MNG%fN5w&%-0QI!Tg zW0|R&p59~D#pi!Vpu@3_#N$qfk`I|1K}t5zy)usM9X>hAu4h%4si`K9ctC0E` zG2R-+Ry_8@Rx$I2)D?bwfxo&VCO=Hk#zxj^E`Cg7+g)-Zwphk_lOdP=wht`vM$i6r z2YI0gru+}P($KIjlXVLaXWuBneE8K?Ol@Tq%(BWV2V2G2P;9?j>6eXCeI z;1+wV9x0uNqr!)O`$d#;Iz>5(r?5EJ<-t(y5cVi;!|w0pJOAnCs>RYjVsS2TsE!dr zeaVOZCa@{RO?v9&F(<`zXC@%xnw1Nn#~;)!IkRIqkIy642<}}ubb0+K>R%nL^}(!e zj`kV!ZpmWMQaDTQR?P_WGg$|-pF*NAY@%by$lBD*{c-L#q01}P6cdA%!uDaNjcKAy zA!=1Qc65cJ4LJ=%7GeRu z&?4aF-F+|y*u%h@Xe`aD4rk=;L`I+7sS&%G$;Hc@d0?yHU}x_ZSv^`x``jzIZ6QvZ zdgcmi4w~8Ps6(%6*Q~7?iq1$pbrNy zS_w(TS2Ib4k5YcP*^w52(XwD_~sH4i0=po94?4;wy; zl4q^e3O+nnFmp_=I8ED zJ2bVIxPOmZTsQVyX=9S~ULPx<(us&=kXO6P?hvnTKwwQG93FBoSAxeEn^%lzARanS zE5ql0{Qa_%`WtIQ>Y5u0gOTjoy%wt&pqg}y@oVO%*5y@b5BOBXh=u*PB1u?ehoB4} zv|^Ob@MOYsB}4$^n2+4D0#u!25tOZYw&E39{wWMy>jt zEt;I`pFu^dI*VTN>92M_)`a3^CiQyeD}Ki4Tb;S*1oPhr?x|M@p+cm`ITguDRYUjt z7cr7NHqt3&7Tvxdw0_Q**s{YlKY0J*EJ;};T_t4^#N|9OtAuptO@~PjpU4el*iURo z&gU@HY?%{&49K-LHq7jCe8vmN8EGng4J%^~E4YwP+_C+B&}&`MgtfG&pX4g1H5?Kr z=;0-hT2B8Iu{PtNgYUav)!(z^XHFrR5c!ARECx^DdO4YerMcXG_(UT@~olr!IT%tYa!Qe`J?TE9^}P z@x@LgW~mLJUHQc&Y%7WrWr}hraMR2h)zI_{sKyY>{s?Tm&sE21Rirhy&F1o{s;Otc z%AW2s={snE?hfMl20Psf({IB>BWYgP3e#;Zl0%F4dQfMRq=(OA7c0 zPMU4CDH@R>p2-@Q?m5RsA5zVIWo2Sgrju@InPgW7csk(6=g-g!BNwM(M;Vz`^J`6c ycU;-rXKUY^`YIr+h5 z4|-y36bdQCZu>0mGAQ;z@)ERULrw&zm>eo{p__4KVYfGLsLjflow_details; $apiKey = $integrationDetails->apiKey; - $selectedWorkspace = $integrationDetails->selectedWorkspace; - $accountId = $integrationDetails->accountId; - $memberId = $integrationDetails->selectedMember; + $selectedWorkspace = $integrationDetails->selectedWorkspace ?? null; + $accountId = $integrationDetails->accountId ?? null; + $memberId = $integrationDetails->selectedMember ?? null; $integId = $integrationData->id; $actionName = $integrationDetails->actionName; - $lockVersion = $integrationDetails->selectedLockVersion; + $lockVersion = $integrationDetails->selectedLockVersion ?? null; - if (empty($apiKey) || empty($selectedWorkspace) || empty($accountId) || empty($actionName)) { - return new WP_Error('REQ_FIELD_EMPTY', __('API Key, Account ID, Workspace ID, and Action are required for Fabman', 'bit-integrations')); - } + // error_log('the lockversion' . print_r($lockVersion, true)); $recordApiHelper = new RecordApiHelper($apiKey, $selectedWorkspace, $accountId, $integId, $memberId, $lockVersion); $fabmanApiResponse = $recordApiHelper->execute($actionName, $fieldValues, $integrationDetails); diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php index 0be0a428d..7960a0b92 100644 --- a/includes/Actions/Fabman/RecordApiHelper.php +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -35,9 +35,12 @@ public function __construct($apiKey, $workspaceId, $accountId, $integrationID = public function execute($actionName, $fieldValues, $integrationDetails) { $finalData = []; - $finalData['space'] = $this->_workspaceId; $finalData['account'] = $this->_accountId; + if ($this->_workspaceId) { + $finalData['space'] = $this->_workspaceId; + } + if ($this->_memberId) { $finalData['memberId'] = $this->_memberId; } @@ -47,7 +50,7 @@ public function execute($actionName, $fieldValues, $integrationDetails) if (!empty($fieldMap->formField) && !empty($fieldMap->fabmanFormField)) { $finalData[$fieldMap->fabmanFormField] = $fieldMap->formField === 'custom' ? $fieldMap->customValue - : $fieldValues[$fieldMap->formField]; + : ($fieldValues[$fieldMap->formField] ?? null); } } } @@ -59,6 +62,10 @@ public function execute($actionName, $fieldValues, $integrationDetails) return $this->updateMember($finalData); case 'delete_member': return $this->deleteMember($finalData); + case 'create_spaces': + return $this->createSpace($finalData); + case 'update_spaces': + return $this->updateSpace($finalData); default: return new WP_Error('INVALID_ACTION', __('Invalid action name', 'bit-integrations')); } @@ -94,18 +101,13 @@ private function updateMember($data) return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for update operation', 'bit-integrations')); } - error_log(print_r($data, true)); - $apiEndpoint = $this->_apiEndpoint . '/members/' . $this->_memberId; $header = [ 'Authorization' => 'Bearer ' . $this->_apiKey, 'Content-Type' => 'application/json' ]; - error_log(print_r($apiEndpoint, true)); - $apiResponse = HttpHelper::put($apiEndpoint, json_encode($data), $header); - error_log(print_r($apiResponse, true)); if (is_wp_error($apiResponse)) { return $apiResponse; @@ -141,4 +143,53 @@ private function deleteMember() return $apiResponse; } + + private function createSpace($data) + { + $apiEndpoint = $this->_apiEndpoint . '/spaces'; + $header = [ + 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiResponse = HttpHelper::post($apiEndpoint, json_encode($data), $header); + error_log(print_r($apiResponse, true)); + + if (is_wp_error($apiResponse)) { + return $apiResponse; + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : __('Failed to create space', 'bit-integrations')); + } + + return $apiResponse; + } + + private function updateSpace($data) + { + $data['lockVersion'] = $this->_lockVersion; + if (empty($this->_workspaceId)) { + return new WP_Error('MISSING_SPACE_ID', __('Space ID is required for update operation', 'bit-integrations')); + } + + $apiEndpoint = $this->_apiEndpoint . '/spaces/' . $this->_workspaceId; + $header = [ + 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiResponse = HttpHelper::put($apiEndpoint, json_encode($data), $header); + error_log(print_r($apiResponse, true)); + + if (is_wp_error($apiResponse)) { + return $apiResponse; + } + + if (empty($apiResponse) || isset($apiResponse->error)) { + return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : __('Failed to update space', 'bit-integrations')); + } + + return $apiResponse; + } } From 8cbe98707428eb5c8487e3744b20b944ec4fdfb6 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:20:54 +0600 Subject: [PATCH 03/13] chore: update logics --- .../Fabman/FabmanIntegLayout.jsx | 27 ++-- includes/Actions/Fabman/FabmanController.php | 10 +- includes/Actions/Fabman/RecordApiHelper.php | 124 +++++++----------- 3 files changed, 67 insertions(+), 94 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx index af2ed2403..593f89d3a 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -39,12 +39,21 @@ export default function FabmanIntegLayout({ { label: __('Update Spaces', 'bit-integrations'), value: 'update_spaces' } ] - const getActiveStaticFields = conf => - conf.actionName === 'create_spaces' || conf.actionName === 'update_spaces' - ? conf.spacesStaticFields - : conf.memberStaticFields + const getActiveStaticFields = conf => { + if (conf.actionName === 'create_spaces' || conf.actionName === 'update_spaces') { + const fields = Array.isArray(conf.spacesStaticFields) + ? conf.spacesStaticFields.map(f => ({ ...f })) + : [] + const isCreate = conf.actionName === 'create_spaces' + const nameIdx = fields.findIndex(f => String(f.key) === 'name') + if (nameIdx > -1) fields[nameIdx].required = true + const tzIdx = fields.findIndex(f => String(f.key) === 'timezone') + if (tzIdx > -1) fields[tzIdx].required = isCreate + return fields + } + return conf.memberStaticFields + } - // Ensure required targets are present in field_map for validation (especially in edit/update) useEffect(() => { const staticFields = getActiveStaticFields(fabmanConf) || [] const requiredFields = staticFields.filter(f => !!f.required) @@ -53,7 +62,6 @@ export default function FabmanIntegLayout({ let changed = false const newFieldMap = [...fabmanConf.field_map] - // Ensure length covers required fields for (let i = 0; i < requiredFields.length; i += 1) { if (!newFieldMap[i]) { newFieldMap[i] = { formField: '', fabmanFormField: requiredFields[i].key } @@ -75,13 +83,11 @@ export default function FabmanIntegLayout({ const value = e.target.value newConf.actionName = value - // Reset member related selects for space actions if (value === 'create_spaces' || value === 'update_spaces') { delete newConf.selectedMember delete newConf.selectedLockVersion } - // Reset field map when switching groups newConf.field_map = [{ formField: '', fabmanFormField: '' }] setFabmanConf(newConf) @@ -146,7 +152,6 @@ export default function FabmanIntegLayout({

- {/* Action Selector */}
{__('Action:', 'bit-integrations')} handleInput(e, fabmanConf, setFabmanConf)} + onChange={handleNameChange} name="name" - value={fabmanConf.name} + value={localName} type="text" placeholder={__('Integration Name...', 'bit-integrations')} /> From 0d56df3bf0aba51a74f6589c2b4ff324477621ae Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:13:00 +0600 Subject: [PATCH 05/13] chore: update functionality --- .../AllIntegrations/Fabman/EditFabman.jsx | 52 +++--- .../AllIntegrations/Fabman/Fabman.jsx | 130 ++++++++------- .../Fabman/FabmanAuthorization.jsx | 48 ++++-- .../Fabman/FabmanCommonFunc.js | 40 ++--- .../AllIntegrations/Fabman/FabmanFieldMap.jsx | 52 ++++-- .../Fabman/FabmanIntegLayout.jsx | 152 ++++++++++-------- .../Fabman/IntegrationHelpers.jsx | 19 ++- includes/Actions/Fabman/FabmanController.php | 10 +- includes/Actions/Fabman/RecordApiHelper.php | 35 ++-- 9 files changed, 301 insertions(+), 237 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index cc291ab1e..a955d0c4a 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ /* eslint-disable no-param-reassign */ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useRecoilState, useRecoilValue } from 'recoil' import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates' @@ -19,7 +19,6 @@ function EditFabman({ allIntegURL }) { const navigate = useNavigate() const [flow, setFlow] = useRecoilState($newFlow) const [fabmanConf, setFabmanConf] = useRecoilState($actionConf) - const [isLoading, setIsLoading] = useState(false) const [loading, setLoading] = useState({ list: false, field: false, @@ -29,6 +28,25 @@ function EditFabman({ allIntegURL }) { const formField = useRecoilValue($formFields) const [localName, setLocalName] = useState(fabmanConf.name || '') + useEffect(() => { + setLocalName(fabmanConf.name || '') + }, [fabmanConf.name]) + + const isConfigInvalid = () => { + if (!fabmanConf.actionName) return true + const isDeleteAction = fabmanConf.actionName === 'delete_member' + if (!isDeleteAction && !checkMappedFields(fabmanConf)) return true + if ( + ['update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( + fabmanConf.actionName + ) && + !fabmanConf.selectedWorkspace + ) + return true + if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && !fabmanConf.selectedMember) + return true + return false + } const saveConfig = () => { if (!fabmanConf.actionName) { @@ -40,24 +58,21 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } - const requiresWorkspaceSelection = fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' || - fabmanConf.actionName === 'update_spaces' - + fabmanConf.actionName === 'update_spaces' || + fabmanConf.actionName === 'delete_spaces' if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } - const requiresMemberSelection = fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' if (requiresMemberSelection && !fabmanConf.selectedMember) { setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) return } - saveActionConf({ flow, allIntegURL, @@ -70,9 +85,12 @@ function EditFabman({ allIntegURL }) { } const handleNameChange = e => { - const value = e.target.value - setLocalName(value) - handleInput(e, fabmanConf, setFabmanConf) + setLocalName(e.target.value) + } + const handleNameBlur = () => { + if (localName !== fabmanConf.name) { + setFabmanConf(prev => ({ ...prev, name: localName })) + } } return ( @@ -84,6 +102,7 @@ function EditFabman({ allIntegURL }) { { - if (!checkMappedFields(fabmanConf)) { - setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) - return - } - - if (!fabmanConf.actionName) { - setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) - return - } - - const requiresWorkspaceSelection = - fabmanConf.actionName === 'create_member' || - fabmanConf.actionName === 'update_member' || - fabmanConf.actionName === 'delete_member' || - fabmanConf.actionName === 'update_spaces' - - if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { - setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) - return - } + // Extracted validation logic + const isConfigInvalid = () => { + if (!fabmanConf.actionName) return true + if ( + !['delete_member', 'delete_spaces'].includes(fabmanConf.actionName) && + !checkMappedFields(fabmanConf) + ) + return true + if ( + ['create_member', 'update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( + fabmanConf.actionName + ) && + !fabmanConf.selectedWorkspace + ) + return true + if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && !fabmanConf.selectedMember) + return true + return false + } - const requiresMemberSelection = - fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' - if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) + const saveConfig = () => { + if (isConfigInvalid()) { + if (!fabmanConf.actionName) { + setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) + } else if ( + !['delete_member', 'delete_spaces'].includes(fabmanConf.actionName) && + !checkMappedFields(fabmanConf) + ) { + setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + } else if ( + ['create_member', 'update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( + fabmanConf.actionName + ) && + !fabmanConf.selectedWorkspace + ) { + setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + } else if ( + ['update_member', 'delete_member'].includes(fabmanConf.actionName) && + !fabmanConf.selectedMember + ) { + setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) + } return } - saveIntegConfig(flow, setFlow, allIntegURL, fabmanConf, navigate, '', '', setIsLoading) } const nextPage = () => { - if (!checkMappedFields(fabmanConf)) { - setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) - return - } - - if (!fabmanConf.actionName) { - setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) - return - } - - const requiresWorkspaceSelection = - fabmanConf.actionName === 'create_member' || - fabmanConf.actionName === 'update_member' || - fabmanConf.actionName === 'delete_member' || - fabmanConf.actionName === 'update_spaces' - - if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { - setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) - return - } - - const requiresMemberSelection = - fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' - if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) + if (isConfigInvalid()) { + if (!fabmanConf.actionName) { + setSnack({ show: true, msg: __('Please select an action', 'bit-integrations') }) + } else if ( + !['delete_member', 'delete_spaces'].includes(fabmanConf.actionName) && + !checkMappedFields(fabmanConf) + ) { + setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + } else if ( + ['create_member', 'update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( + fabmanConf.actionName + ) && + !fabmanConf.selectedWorkspace + ) { + setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) + } else if ( + ['update_member', 'delete_member'].includes(fabmanConf.actionName) && + !fabmanConf.selectedMember + ) { + setSnack({ show: true, msg: __('Please select a member', 'bit-integrations') }) + } return } - setStep(3) } @@ -278,21 +288,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { +
+ {loading.workspaces && ( + + )} +
+ + )} + {/* Remove the entire member selector section for delete_member */} + {/* Field map for delete_member: only one required email field, no + button */} + {isDeleteMember && ( <> -
- {__('Select Workspace:', 'bit-integrations')} - - +
+ {__('Field Map', 'bit-integrations')}
- {loading.workspaces && ( - - )}
- - )} - {requiresMemberSelection && fabmanConf.selectedWorkspace && ( - <> -
- {__('Select Member:', 'bit-integrations')} - - +
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Fabman Fields', 'bit-integrations')} +
- {loading.members && ( - - )} -
+ )} + {/* Field map for other actions */} {fabmanConf.actionName && fabmanConf.actionName !== 'delete_member' && (!isSpaceAction || diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx index face3c082..9f6d60322 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-expressions */ - export const addFieldMap = (i, confTmp, setConf) => { const newConf = { ...confTmp } const fieldMap = Array.isArray(newConf.field_map) ? [...newConf.field_map] : [] diff --git a/includes/Actions/Fabman/FabmanController.php b/includes/Actions/Fabman/FabmanController.php index 0d6a39f50..3fdd857b2 100644 --- a/includes/Actions/Fabman/FabmanController.php +++ b/includes/Actions/Fabman/FabmanController.php @@ -75,18 +75,21 @@ public static function fetchWorkspaces($requestParams) ], 200); } - public static function fetchMembers($requestParams) + public static function fetchMemberByEmail($requestParams) { - if (empty($requestParams->apiKey) || empty($requestParams->workspaceId)) { - wp_send_json_error(__('API Key and Workspace ID are required', 'bit-integrations'), 400); + if (empty($requestParams->apiKey) || empty($requestParams->email)) { + wp_send_json_error(__('API Key and Email are required', 'bit-integrations'), 400); } + $email = $requestParams->email; + $header = [ 'Authorization' => 'Bearer ' . $requestParams->apiKey, 'Content-Type' => 'application/json' ]; - $apiEndpoint = 'https://fabman.io/api/v1/members'; + $apiEndpoint = 'https://fabman.io/api/v1/members?q=' . urlencode($email); + $apiResponse = HttpHelper::get($apiEndpoint, null, $header); if (is_wp_error($apiResponse)) { @@ -94,11 +97,20 @@ public static function fetchMembers($requestParams) } if (empty($apiResponse) || isset($apiResponse->error) || !\is_array($apiResponse)) { - wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Failed to fetch members', 'bit-integrations'), 400); + wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Failed to fetch member', 'bit-integrations'), 400); + } + + $memberId = null; + $lockVersion = null; + if (!empty($apiResponse) && \is_array($apiResponse) && isset($apiResponse[0])) { + $memberId = $apiResponse[0]->id; + $lockVersion = $apiResponse[0]->lockVersion; } wp_send_json_success([ - 'members' => $apiResponse + 'member' => $apiResponse, + 'memberId' => $memberId, + 'lockVersion' => $lockVersion ], 200); } @@ -108,13 +120,76 @@ public static function execute($integrationData, $fieldValues) $apiKey = $integrationDetails->apiKey; $selectedWorkspace = $integrationDetails->selectedWorkspace ?? null; $accountId = $integrationDetails->accountId ?? null; - $memberId = $integrationDetails->selectedMember ?? null; - $integId = $integrationData->id; $actionName = $integrationDetails->actionName; + $integId = $integrationData->id; + $memberId = $integrationDetails->selectedMember ?? null; $lockVersion = $integrationDetails->selectedLockVersion ?? null; - $recordApiHelper = new RecordApiHelper($apiKey, $selectedWorkspace, $accountId, $integId, $memberId, $lockVersion); + if (\in_array($actionName, ['update_member', 'delete_member'])) { + if ($actionName === 'delete_member' || empty($memberId)) { + $email = self::getMappedValue($integrationDetails->field_map, 'emailAddress', $fieldValues); + if ($email) { + $memberData = self::fetchMemberByEmailInternal($apiKey, $email); + if ($memberData) { + $memberId = $memberData['memberId']; + $lockVersion = $memberData['lockVersion']; + } + } + } + } + + $recordApiHelper = new RecordApiHelper( + $apiKey, + $selectedWorkspace, + $accountId, + $integId, + $memberId, + $lockVersion + ); return $recordApiHelper->execute($actionName, $fieldValues, $integrationDetails); } + + private static function getMappedValue($fieldMap, $targetField, $fieldValues) + { + if (empty($fieldMap)) { + return null; + } + + foreach ($fieldMap as $map) { + if (!empty($map->fabmanFormField) && $map->fabmanFormField === $targetField) { + if ($map->formField === 'custom') { + return $map->customValue; + } + + return $fieldValues[$map->formField] ?? null; + } + } + + return null; + } + + private static function fetchMemberByEmailInternal($apiKey, $email) + { + $header = [ + 'Authorization' => 'Bearer ' . $apiKey, + 'Content-Type' => 'application/json' + ]; + + $apiEndpoint = 'https://fabman.io/api/v1/members?q=' . urlencode($email); + $apiResponse = HttpHelper::get($apiEndpoint, null, $header); + + if (is_wp_error($apiResponse) || empty($apiResponse) || isset($apiResponse->error) || !\is_array($apiResponse)) { + return null; + } + + if (isset($apiResponse[0])) { + return [ + 'memberId' => $apiResponse[0]->id, + 'lockVersion' => $apiResponse[0]->lockVersion + ]; + } + + return null; + } } diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php index 9907f67f6..3d2a1a6e8 100644 --- a/includes/Actions/Fabman/RecordApiHelper.php +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -8,42 +8,42 @@ class RecordApiHelper { - private $_integrationID; + private $integrationID; - private $_apiKey; + private $apiKey; - private $_workspaceId; + private $workspaceId; - private $_accountId; + private $accountId; - private $_memberId; + private $memberId; - private $_apiEndpoint; + private $apiEndpoint; - private $_lockVersion; + private $lockVersion; public function __construct($apiKey, $workspaceId, $accountId, $integrationID = null, $memberId = null, $lockVersion) { - $this->_integrationID = $integrationID; - $this->_apiKey = $apiKey; - $this->_workspaceId = $workspaceId; - $this->_accountId = $accountId; - $this->_memberId = $memberId; - $this->_lockVersion = $lockVersion; - $this->_apiEndpoint = 'https://fabman.io/api/v1'; + $this->integrationID = $integrationID; + $this->apiKey = $apiKey; + $this->workspaceId = $workspaceId; + $this->accountId = $accountId; + $this->memberId = $memberId; + $this->lockVersion = $lockVersion; + $this->apiEndpoint = 'https://fabman.io/api/v1'; } public function execute($actionName, $fieldValues, $integrationDetails) { $finalData = []; - $finalData['account'] = $this->_accountId; + $finalData['account'] = $this->accountId; - if ($this->_workspaceId) { - $finalData['space'] = $this->_workspaceId; + if ($this->workspaceId) { + $finalData['space'] = $this->workspaceId; } - if ($this->_memberId) { - $finalData['memberId'] = $this->_memberId; + if ($this->memberId) { + $finalData['memberId'] = $this->memberId; } if (!empty($integrationDetails->field_map)) { @@ -84,7 +84,7 @@ public function execute($actionName, $fieldValues, $integrationDetails) $status = \in_array(HttpHelper::$responseCode, [200, 201, 204]) ? 'success' : 'error'; - LogHandler::save($this->_integrationID, ['type' => 'record', 'type_name' => $actionName], $status, $apiResponse); + LogHandler::save($this->integrationID, ['type' => 'record', 'type_name' => $actionName], $status, $apiResponse); return $apiResponse; } @@ -92,7 +92,7 @@ public function execute($actionName, $fieldValues, $integrationDetails) private function createMember($data) { unset($data['memberId']); - $apiEndpoint = $this->_apiEndpoint . '/members'; + $apiEndpoint = $this->apiEndpoint . '/members'; $header = $this->setHeaders(); $apiResponse = HttpHelper::post($apiEndpoint, json_encode($data), $header); @@ -110,61 +110,51 @@ private function createMember($data) private function updateMember($data) { - $data['lockVersion'] = $this->_lockVersion; - if (empty($this->_memberId)) { - return new WP_Error('MISSING_MEMBER_ID', \__('Member ID is required for update operation', 'bit-integrations')); + $data['lockVersion'] = $this->lockVersion; + if (empty($this->memberId)) { + return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for update operation', 'bit-integrations')); } - $response = \apply_filters('btcbi_fabman_update_member', false, json_encode($data), $this->setHeaders(), $this->_apiEndpoint, $this->_memberId); - return $this->handleFilterResponse($response, 'update_member'); + error_log(print_r($data, true)); + $response = \apply_filters('btcbi_fabman_update_member', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint, $this->memberId); + + return $this->handleFilterResponse($response); } private function deleteMember() { - if (empty($this->_memberId)) { - return new WP_Error('MISSING_MEMBER_ID', \__('Member ID is required for delete operation', 'bit-integrations')); + if (empty($this->memberId)) { + return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for delete operation', 'bit-integrations')); } - $response = \apply_filters('btcbi_fabman_delete_member', false, $this->setHeaders(), $this->_apiEndpoint, $this->_memberId); + error_log(print_r($this->memberId, true)); + $response = \apply_filters('btcbi_fabman_delete_member', false, $this->setHeaders(), $this->apiEndpoint, $this->memberId); - return $this->handleFilterResponse($response, 'delete_member'); + return $this->handleFilterResponse($response); } private function createSpace($data) { unset($data['space']); - $response = \apply_filters('btcbi_fabman_create_space', false, json_encode($data), $this->setHeaders(), $this->_apiEndpoint); + $response = \apply_filters('btcbi_fabman_create_space', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint); - return $this->handleFilterResponse($response, 'create_space'); + return $this->handleFilterResponse($response); } private function updateSpace($data) { - $data['lockVersion'] = $this->_lockVersion; - if (empty($this->_workspaceId)) { - return new WP_Error('MISSING_SPACE_ID', \__('Space ID is required for update operation', 'bit-integrations')); + $data['lockVersion'] = $this->lockVersion; + if (empty($this->workspaceId)) { + return new WP_Error('MISSING_SPACE_ID', __('Space ID is required for update operation', 'bit-integrations')); } - $response = \apply_filters('btcbi_fabman_update_space', false, json_encode($data), $this->setHeaders(), $this->_apiEndpoint, $this->_workspaceId); + $response = \apply_filters('btcbi_fabman_update_space', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint, $this->workspaceId); - return $this->handleFilterResponse($response, 'update_space'); + return $this->handleFilterResponse($response); } - /** - * Handle the response from filter-based (Pro) actions. - * - * @param mixed $response - * @param string $action - * - * @return mixed|WP_Error - */ - private function handleFilterResponse($response, $action = '') + private function handleFilterResponse($response) { - if (empty($response) || (\is_object($response) && isset($response->error))) { - $msg = \wp_sprintf(\__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integration Pro'); - if ($action) { - $msg = \sprintf('%s: %s', ucfirst(str_replace('_', ' ', $action)), $msg); - } - - return new WP_Error('PRO_FEATURE_REQUIRED', $msg); + if (empty($response)) { + return (object) ['error' => \wp_sprintf(\__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integration Pro')]; } return $response; @@ -173,7 +163,7 @@ private function handleFilterResponse($response, $action = '') private function setHeaders(): array { return [ - 'Authorization' => 'Bearer ' . $this->_apiKey, + 'Authorization' => 'Bearer ' . $this->apiKey, 'Content-Type' => 'application/json' ]; } diff --git a/includes/Actions/Fabman/Routes.php b/includes/Actions/Fabman/Routes.php index ca06065ca..1d1aeb2ef 100644 --- a/includes/Actions/Fabman/Routes.php +++ b/includes/Actions/Fabman/Routes.php @@ -9,4 +9,4 @@ Route::post('fabman_authorization', [FabmanController::class, 'authorization']); Route::post('fabman_fetch_workspaces', [FabmanController::class, 'fetchWorkspaces']); -Route::post('fabman_fetch_members', [FabmanController::class, 'fetchMembers']); +Route::post('fabman_fetch_member_by_email', [FabmanController::class, 'fetchMemberByEmail']); From e4b0bfcb61f43474c3981eebd9ad5d01150e8158 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:42:48 +0600 Subject: [PATCH 07/13] chore: update member button fix --- .../AllIntegrations/Fabman/EditFabman.jsx | 49 +++++++------- .../AllIntegrations/Fabman/Fabman.jsx | 13 ---- .../AllIntegrations/Fabman/FabmanActions.jsx | 2 - .../Fabman/FabmanAuthorization.jsx | 6 -- .../Fabman/FabmanCommonFunc.js | 26 -------- .../AllIntegrations/Fabman/FabmanFieldMap.jsx | 1 - .../Fabman/FabmanIntegLayout.jsx | 65 +++++++------------ 7 files changed, 48 insertions(+), 114 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index a955d0c4a..43b69defd 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -1,6 +1,3 @@ -/* eslint-disable no-unused-vars */ -/* eslint-disable no-param-reassign */ - import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useRecoilState, useRecoilValue } from 'recoil' @@ -34,17 +31,16 @@ function EditFabman({ allIntegURL }) { const isConfigInvalid = () => { if (!fabmanConf.actionName) return true - const isDeleteAction = fabmanConf.actionName === 'delete_member' - if (!isDeleteAction && !checkMappedFields(fabmanConf)) return true + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { + if (!checkMappedFields(fabmanConf)) return true + return false + } + if (!checkMappedFields(fabmanConf)) return true if ( - ['update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( - fabmanConf.actionName - ) && + ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && !fabmanConf.selectedWorkspace ) return true - if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && !fabmanConf.selectedMember) - return true return false } @@ -53,26 +49,32 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - const isDeleteAction = fabmanConf.actionName === 'delete_member' - if (!isDeleteAction && !checkMappedFields(fabmanConf)) { + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { + if (!checkMappedFields(fabmanConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + saveActionConf({ + flow, + allIntegURL, + conf: fabmanConf, + navigate, + edit: 1, + setLoading, + setSnackbar + }) + return + } + if (!checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } const requiresWorkspaceSelection = - fabmanConf.actionName === 'update_member' || - fabmanConf.actionName === 'delete_member' || - fabmanConf.actionName === 'update_spaces' || - fabmanConf.actionName === 'delete_spaces' + fabmanConf.actionName === 'update_spaces' || fabmanConf.actionName === 'delete_spaces' if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } - const requiresMemberSelection = - fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' - if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) - return - } saveActionConf({ flow, allIntegURL, @@ -96,7 +98,6 @@ function EditFabman({ allIntegURL }) { return (
-
{__('Integration Name:', 'bit-integrations')}

- - field.fabmanFormField === 'emailAddress' && field.formField @@ -260,7 +255,6 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { const handleActionChange = e => { setFabmanConf(prev => { const newConf = { ...prev, actionName: e.target.value } - return newConf }) } @@ -271,8 +265,6 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) {
- - {/* STEP 1 */} - - {/* STEP 2 */} {step === 2 && (
-
)} - - {/* STEP 3 */} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx index 73cfa0f35..ebddf4493 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx @@ -1,5 +1,3 @@ -/* eslint-disable no-param-reassign */ - import { __ } from '../../../Utils/i18nwrap' export default function FabmanActions({ fabmanConf, setFabmanConf, loading, setLoading }) { diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 9193790bf..484c6a2ac 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ import { useState, useCallback, useEffect } from 'react' import { __ } from '../../../Utils/i18nwrap' import LoaderSm from '../../Loaders/LoaderSm' @@ -59,7 +57,6 @@ export default function FabmanAuthorization({
{fabman?.youTubeLink && } {fabman?.docLink && } -
{__('Integration Name:', 'bit-integrations')}
@@ -73,7 +70,6 @@ export default function FabmanAuthorization({ placeholder={__('Integration Name...', 'bit-integrations')} disabled={isInfo} /> -
{__('API Key:', 'bit-integrations')}
@@ -89,7 +85,6 @@ export default function FabmanAuthorization({
{error.apiKey}
- {!isInfo && (
-

- {/* Workspace selector for all except delete_member */} {fabmanConf.actionName && !isDeleteMember && (!isSpaceAction || fabmanConf.actionName === 'update_spaces') && ( @@ -213,8 +210,6 @@ export default function FabmanIntegLayout({
)} - {/* Remove the entire member selector section for delete_member */} - {/* Field map for delete_member: only one required email field, no + button */} {isDeleteMember && ( <>
@@ -245,7 +240,6 @@ export default function FabmanIntegLayout({ /> )} - {/* Field map for other actions */} {fabmanConf.actionName && fabmanConf.actionName !== 'delete_member' && (!isSpaceAction || @@ -265,43 +259,32 @@ export default function FabmanIntegLayout({ {__('Fabman Fields', 'bit-integrations')}
- {fabmanConf?.field_map.map((itm, i) => ( - - ))} -
-
- -
-
-
-
- {__('Utilities', 'bit-integrations')} -
-
- -
+ {Array.isArray(activeStaticFields) && + activeStaticFields.map((field, i) => ( + + ))} + )} +
) } From 8d68b436af1ddfcc6db4f0b00e690f6488545dd6 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:38:18 +0600 Subject: [PATCH 08/13] Revert "chore: update member button fix" This reverts commit e4b0bfcb61f43474c3981eebd9ad5d01150e8158. --- .../AllIntegrations/Fabman/EditFabman.jsx | 49 +++++++------- .../AllIntegrations/Fabman/Fabman.jsx | 13 ++++ .../AllIntegrations/Fabman/FabmanActions.jsx | 2 + .../Fabman/FabmanAuthorization.jsx | 6 ++ .../Fabman/FabmanCommonFunc.js | 26 ++++++++ .../AllIntegrations/Fabman/FabmanFieldMap.jsx | 1 + .../Fabman/FabmanIntegLayout.jsx | 65 ++++++++++++------- 7 files changed, 114 insertions(+), 48 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index 43b69defd..a955d0c4a 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -1,3 +1,6 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-param-reassign */ + import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useRecoilState, useRecoilValue } from 'recoil' @@ -31,16 +34,17 @@ function EditFabman({ allIntegURL }) { const isConfigInvalid = () => { if (!fabmanConf.actionName) return true - if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { - if (!checkMappedFields(fabmanConf)) return true - return false - } - if (!checkMappedFields(fabmanConf)) return true + const isDeleteAction = fabmanConf.actionName === 'delete_member' + if (!isDeleteAction && !checkMappedFields(fabmanConf)) return true if ( - ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && + ['update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( + fabmanConf.actionName + ) && !fabmanConf.selectedWorkspace ) return true + if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && !fabmanConf.selectedMember) + return true return false } @@ -49,32 +53,26 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { - if (!checkMappedFields(fabmanConf)) { - setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) - return - } - saveActionConf({ - flow, - allIntegURL, - conf: fabmanConf, - navigate, - edit: 1, - setLoading, - setSnackbar - }) - return - } - if (!checkMappedFields(fabmanConf)) { + const isDeleteAction = fabmanConf.actionName === 'delete_member' + if (!isDeleteAction && !checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } const requiresWorkspaceSelection = - fabmanConf.actionName === 'update_spaces' || fabmanConf.actionName === 'delete_spaces' + fabmanConf.actionName === 'update_member' || + fabmanConf.actionName === 'delete_member' || + fabmanConf.actionName === 'update_spaces' || + fabmanConf.actionName === 'delete_spaces' if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } + const requiresMemberSelection = + fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' + if (requiresMemberSelection && !fabmanConf.selectedMember) { + setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) + return + } saveActionConf({ flow, allIntegURL, @@ -98,6 +96,7 @@ function EditFabman({ allIntegURL }) { return (
+
{__('Integration Name:', 'bit-integrations')}

+ + field.fabmanFormField === 'emailAddress' && field.formField @@ -255,6 +260,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { const handleActionChange = e => { setFabmanConf(prev => { const newConf = { ...prev, actionName: e.target.value } + return newConf }) } @@ -265,6 +271,8 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) {
+ + {/* STEP 1 */} + + {/* STEP 2 */} {step === 2 && (
+
)} + + {/* STEP 3 */} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx index ebddf4493..73cfa0f35 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx @@ -1,3 +1,5 @@ +/* eslint-disable no-param-reassign */ + import { __ } from '../../../Utils/i18nwrap' export default function FabmanActions({ fabmanConf, setFabmanConf, loading, setLoading }) { diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 484c6a2ac..9193790bf 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable no-unused-expressions */ import { useState, useCallback, useEffect } from 'react' import { __ } from '../../../Utils/i18nwrap' import LoaderSm from '../../Loaders/LoaderSm' @@ -57,6 +59,7 @@ export default function FabmanAuthorization({
{fabman?.youTubeLink && } {fabman?.docLink && } +
{__('Integration Name:', 'bit-integrations')}
@@ -70,6 +73,7 @@ export default function FabmanAuthorization({ placeholder={__('Integration Name...', 'bit-integrations')} disabled={isInfo} /> +
{__('API Key:', 'bit-integrations')}
@@ -85,6 +89,7 @@ export default function FabmanAuthorization({
{error.apiKey}
+ {!isInfo && (
+

+ {/* Workspace selector for all except delete_member */} {fabmanConf.actionName && !isDeleteMember && (!isSpaceAction || fabmanConf.actionName === 'update_spaces') && ( @@ -210,6 +213,8 @@ export default function FabmanIntegLayout({
)} + {/* Remove the entire member selector section for delete_member */} + {/* Field map for delete_member: only one required email field, no + button */} {isDeleteMember && ( <>
@@ -240,6 +245,7 @@ export default function FabmanIntegLayout({ /> )} + {/* Field map for other actions */} {fabmanConf.actionName && fabmanConf.actionName !== 'delete_member' && (!isSpaceAction || @@ -259,32 +265,43 @@ export default function FabmanIntegLayout({ {__('Fabman Fields', 'bit-integrations')}
- {Array.isArray(activeStaticFields) && - activeStaticFields.map((field, i) => ( - - ))} - + {fabmanConf?.field_map.map((itm, i) => ( + + ))} +
+
+ +
+
+
+
+ {__('Utilities', 'bit-integrations')} +
+
+ +
)} -
) } From 760a9f54e20f441f813a1d0520461f22f88ec9d4 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:52:01 +0600 Subject: [PATCH 09/13] chore: update --- .../AllIntegrations/Fabman/EditFabman.jsx | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index a955d0c4a..7eec3ee71 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -34,17 +34,17 @@ function EditFabman({ allIntegURL }) { const isConfigInvalid = () => { if (!fabmanConf.actionName) return true - const isDeleteAction = fabmanConf.actionName === 'delete_member' - if (!isDeleteAction && !checkMappedFields(fabmanConf)) return true + // For update_member and delete_member, only require mapped fields + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { + if (!checkMappedFields(fabmanConf)) return true + return false + } + if (!checkMappedFields(fabmanConf)) return true if ( - ['update_member', 'delete_member', 'update_spaces', 'delete_spaces'].includes( - fabmanConf.actionName - ) && + ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && !fabmanConf.selectedWorkspace ) return true - if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && !fabmanConf.selectedMember) - return true return false } @@ -53,26 +53,33 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - const isDeleteAction = fabmanConf.actionName === 'delete_member' - if (!isDeleteAction && !checkMappedFields(fabmanConf)) { + // For update_member and delete_member, only require mapped fields + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { + if (!checkMappedFields(fabmanConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + saveActionConf({ + flow, + allIntegURL, + conf: fabmanConf, + navigate, + edit: 1, + setLoading, + setSnackbar + }) + return + } + if (!checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } const requiresWorkspaceSelection = - fabmanConf.actionName === 'update_member' || - fabmanConf.actionName === 'delete_member' || - fabmanConf.actionName === 'update_spaces' || - fabmanConf.actionName === 'delete_spaces' + fabmanConf.actionName === 'update_spaces' || fabmanConf.actionName === 'delete_spaces' if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } - const requiresMemberSelection = - fabmanConf.actionName === 'update_member' || fabmanConf.actionName === 'delete_member' - if (requiresMemberSelection && !fabmanConf.selectedMember) { - setSnackbar({ show: true, msg: __('Please select a member', 'bit-integrations') }) - return - } saveActionConf({ flow, allIntegURL, From cbec046146b936fb805ccfbcb46e67e16d12ef89 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:21:05 +0600 Subject: [PATCH 10/13] chore: udpate respone --- includes/Actions/Fabman/RecordApiHelper.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php index 3d2a1a6e8..5c01013d3 100644 --- a/includes/Actions/Fabman/RecordApiHelper.php +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -79,10 +79,17 @@ public function execute($actionName, $fieldValues, $integrationDetails) break; default: - $apiResponse = new WP_Error('INVALID_ACTION', \__('Invalid action name', 'bit-integrations')); + $apiResponse = new WP_Error( + 'INVALID_ACTION', + __('Invalid action name', 'bit-integrations') + ); } - $status = \in_array(HttpHelper::$responseCode, [200, 201, 204]) ? 'success' : 'error'; + if (is_wp_error($apiResponse) || (\is_object($apiResponse) && isset($apiResponse->error))) { + $status = 'error'; + } else { + $status = \in_array(HttpHelper::$responseCode, [200, 201, 204]) ? 'success' : 'error'; + } LogHandler::save($this->integrationID, ['type' => 'record', 'type_name' => $actionName], $status, $apiResponse); @@ -112,10 +119,9 @@ private function updateMember($data) { $data['lockVersion'] = $this->lockVersion; if (empty($this->memberId)) { - return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for update operation', 'bit-integrations')); + return new WP_Error('MISSING_MEMBER_ID', __('The email provided did not match any existing Fabman member.', 'bit-integrations')); } - error_log(print_r($data, true)); $response = \apply_filters('btcbi_fabman_update_member', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint, $this->memberId); return $this->handleFilterResponse($response); @@ -124,9 +130,9 @@ private function updateMember($data) private function deleteMember() { if (empty($this->memberId)) { - return new WP_Error('MISSING_MEMBER_ID', __('Member ID is required for delete operation', 'bit-integrations')); + return new WP_Error('MISSING_MEMBER_ID', __('The email provided did not match any existing Fabman member.', 'bit-integrations')); } - error_log(print_r($this->memberId, true)); + $response = \apply_filters('btcbi_fabman_delete_member', false, $this->setHeaders(), $this->apiEndpoint, $this->memberId); return $this->handleFilterResponse($response); @@ -144,7 +150,7 @@ private function updateSpace($data) { $data['lockVersion'] = $this->lockVersion; if (empty($this->workspaceId)) { - return new WP_Error('MISSING_SPACE_ID', __('Space ID is required for update operation', 'bit-integrations')); + return new WP_Error('MISSING_SPACE_ID', __('Please select a space to update.', 'bit-integrations')); } $response = \apply_filters('btcbi_fabman_update_space', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint, $this->workspaceId); From 45a975060b007a1ca27a0541d10ea0d899c87302 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:01:18 +0600 Subject: [PATCH 11/13] chore: update --- .../AllIntegrations/Fabman/EditFabman.jsx | 34 +++++++- .../AllIntegrations/Fabman/Fabman.jsx | 36 ++++++++- .../Fabman/FabmanCommonFunc.js | 81 ------------------- .../Fabman/FabmanIntegLayout.jsx | 23 ++++-- includes/Actions/Fabman/FabmanController.php | 47 +---------- includes/Actions/Fabman/Routes.php | 1 - 6 files changed, 85 insertions(+), 137 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index 7eec3ee71..92095e1a6 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -14,6 +14,7 @@ import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import { checkMappedFields, handleInput } from './FabmanCommonFunc' import FabmanIntegLayout from './FabmanIntegLayout' +import { checkValidEmail } from '../../../Utils/Helpers' function EditFabman({ allIntegURL }) { const navigate = useNavigate() @@ -32,11 +33,36 @@ function EditFabman({ allIntegURL }) { setLocalName(fabmanConf.name || '') }, [fabmanConf.name]) + const getEmailMappingRow = () => { + const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] + return rows.find(r => r?.fabmanFormField === 'emailAddress') + } + + const isEmailMappingInvalid = () => { + const emailRow = getEmailMappingRow() + if (!emailRow) return true + if (emailRow.formField === 'custom') { + const customValue = (emailRow.customValue || '').trim() + return !customValue || !checkValidEmail(customValue) + } + // Non-custom: a form field is selected. Consider valid if: + // - field type is email, OR + // - type is missing/unknown, OR + // - field name/label includes "email" + const selectedField = (formField || []).find(f => f.name === emailRow.formField) + if (!selectedField) return false + const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' + const looksLikeEmailField = + /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') + return !(hasEmailType || !selectedField.type || looksLikeEmailField) + } + const isConfigInvalid = () => { if (!fabmanConf.actionName) return true - // For update_member and delete_member, only require mapped fields + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { if (!checkMappedFields(fabmanConf)) return true + if (isEmailMappingInvalid()) return true return false } if (!checkMappedFields(fabmanConf)) return true @@ -53,12 +79,16 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) return } - // For update_member and delete_member, only require mapped fields + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { if (!checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } + if (isEmailMappingInvalid()) { + setSnackbar({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) + return + } saveActionConf({ flow, allIntegURL, diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx index 7aeb361b7..2f781b76c 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/Fabman.jsx @@ -12,6 +12,7 @@ import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import FabmanAuthorization from './FabmanAuthorization' import { checkMappedFields } from './FabmanCommonFunc' import FabmanIntegLayout from './FabmanIntegLayout' +import { checkValidEmail } from '../../../Utils/Helpers' export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() @@ -157,6 +158,27 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { selectedLockVersion: '' }) + const getEmailMappingRow = () => { + const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] + return rows.find(r => r?.fabmanFormField === 'emailAddress') + } + + const isEmailMappingInvalid = () => { + const emailRow = getEmailMappingRow() + if (!emailRow) return true + if (emailRow.formField === 'custom') { + const customValue = (emailRow.customValue || '').trim() + return !customValue || !checkValidEmail(customValue) + } + + const selectedField = (formFields || []).find(f => f.name === emailRow.formField) + if (!selectedField) return false + const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' + const looksLikeEmailField = + /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') + return !(hasEmailType || !selectedField.type || looksLikeEmailField) + } + const isConfigInvalid = () => { if (!fabmanConf.actionName) return true if ( @@ -164,6 +186,8 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { !checkMappedFields(fabmanConf) ) return true + if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && isEmailMappingInvalid()) + return true if ( ['create_member', 'update_member', 'update_spaces', 'delete_spaces'].includes( fabmanConf.actionName @@ -171,7 +195,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { !fabmanConf.selectedWorkspace ) return true - // For delete_member, just need email field mapped (no need for selectedMember) + if (fabmanConf.actionName === 'delete_member') { const hasEmailField = fabmanConf.field_map?.some( field => field.fabmanFormField === 'emailAddress' && field.formField @@ -192,6 +216,11 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { !checkMappedFields(fabmanConf) ) { setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + } else if ( + ['update_member', 'delete_member'].includes(fabmanConf.actionName) && + isEmailMappingInvalid() + ) { + setSnack({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) } else if ( ['create_member', 'update_member', 'update_spaces', 'delete_spaces'].includes( fabmanConf.actionName @@ -224,6 +253,11 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { !checkMappedFields(fabmanConf) ) { setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + } else if ( + ['update_member', 'delete_member'].includes(fabmanConf.actionName) && + isEmailMappingInvalid() + ) { + setSnack({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) } else if ( ['create_member', 'update_member', 'update_spaces', 'delete_spaces'].includes( fabmanConf.actionName diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js index 3f9186ca3..6c2de5255 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -109,84 +109,3 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ toast.error(__('Failed to fetch workspaces', 'bit-integrations')) }) } - -export const fetchMemberByEmail = (confTmp, setConf, loading, setLoading, type = 'fetch') => { - console.log('=== fetchMemberByEmail called ===') - console.log('confTmp:', confTmp) - console.log('Action:', confTmp.actionName) - - if (!confTmp.apiKey) { - console.log('API key missing') - toast.error(__('API key is required to fetch member by email', 'bit-integrations')) - return - } - - - let email = null - if (Array.isArray(confTmp.field_map)) { - const emailField = confTmp.field_map.find(field => field.fabmanFormField === 'emailAddress') - if (emailField) { - if (emailField.formField === 'custom' && emailField.customValue) { - email = emailField.customValue - console.log('Found email from customValue:', email) - } else if (emailField.formField && emailField.formField !== 'custom') { - email = emailField.formField - console.log('Found email from formField:', email) - } - } - } - - if (!email) { - console.log('No email found in field map') - toast.error(__('Email field not found in field map', 'bit-integrations')) - return - } - - setLoading({ ...loading, members: true }) - - const requestParams = { - apiKey: confTmp.apiKey, - email: email - } - - console.log('Request params:', requestParams) - - bitsFetch(requestParams, 'fabman_fetch_member_by_email') - .then(result => { - console.log('API Response:', result) - setLoading({ ...loading, members: false }) - if (result && result.success) { - const newConf = { ...confTmp } - - console.log('Before setting memberId - selectedMember:', newConf.selectedMember) - console.log('Before setting memberId - actionName:', newConf.actionName) - - if (result.data.memberId) { - newConf.selectedMember = result.data.memberId - console.log('Set selectedMember:', result.data.memberId) - } - if (result.data.lockVersion) { - newConf.selectedLockVersion = result.data.lockVersion - console.log('Set selectedLockVersion:', result.data.lockVersion) - } - - console.log('After setting - selectedMember:', newConf.selectedMember) - console.log('After setting - actionName:', newConf.actionName) - console.log('Final newConf:', newConf) - - setConf(newConf) - toast.success( - type === 'refresh' - ? __('Member found and selected successfully', 'bit-integrations') - : __('Member found and selected successfully', 'bit-integrations') - ) - return - } - toast.error(__('Failed to fetch member by email', 'bit-integrations')) - }) - .catch(error => { - console.log('API Error:', error) - setLoading({ ...loading, members: false }) - toast.error(__('Failed to fetch member by email', 'bit-integrations')) - }) -} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx index ca0de3799..dedc1dd48 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -4,9 +4,10 @@ import { __ } from '../../../Utils/i18nwrap' import FabmanFieldMap from './FabmanFieldMap' import { addFieldMap } from './IntegrationHelpers' import FabmanActions from './FabmanActions' -import { fetchFabmanWorkspaces, generateMappedField, fetchMemberByEmail } from './FabmanCommonFunc' +import { fetchFabmanWorkspaces, generateMappedField } from './FabmanCommonFunc' import Loader from '../../Loaders/Loader' import { useEffect, useMemo, useCallback, useRef } from 'react' +import Note from '../../Utilities/Note' export default function FabmanIntegLayout({ formFields, @@ -40,12 +41,10 @@ export default function FabmanIntegLayout({ return fields } - // Special handling for update_member and delete_member actions if (conf.actionName === 'update_member' || conf.actionName === 'delete_member') { const fields = Array.isArray(conf.memberStaticFields) ? conf.memberStaticFields.map(f => ({ ...f })) : [] - // Make emailAddress required and firstName not required for both actions const emailIdx = fields.findIndex(f => String(f.key) === 'emailAddress') if (emailIdx > -1) fields[emailIdx].required = true const firstNameIdx = fields.findIndex(f => String(f.key) === 'firstName') @@ -170,7 +169,6 @@ export default function FabmanIntegLayout({

- {/* Workspace selector for all except delete_member */} {fabmanConf.actionName && !isDeleteMember && (!isSpaceAction || fabmanConf.actionName === 'update_spaces') && ( @@ -211,10 +209,10 @@ export default function FabmanIntegLayout({ /> )}
+ )} - {/* Remove the entire member selector section for delete_member */} - {/* Field map for delete_member: only one required email field, no + button */} + {isDeleteMember && ( <>
@@ -245,13 +243,14 @@ export default function FabmanIntegLayout({ /> )} - {/* Field map for other actions */} + {fabmanConf.actionName && fabmanConf.actionName !== 'delete_member' && (!isSpaceAction || (isSpaceAction && (fabmanConf.actionName !== 'update_spaces' || fabmanConf.selectedWorkspace))) && ( <> + {fabmanConf.actionName === 'create_spaces' && }
{__('Field Map', 'bit-integrations')}
@@ -305,3 +304,13 @@ export default function FabmanIntegLayout({
) } + +const fabmanWorkspaceNote = `

${__( + 'Please select workspace for Create Member, Update Member, and Update Spaces.', + 'bit-integrations' +)}

` + +const fabmanTimezoneNote = `

${__( + 'For Create Spaces, Timezone must be like Asia/Dhaka (IANA timezone format).', + 'bit-integrations' +)}

` diff --git a/includes/Actions/Fabman/FabmanController.php b/includes/Actions/Fabman/FabmanController.php index 3fdd857b2..4f142d879 100644 --- a/includes/Actions/Fabman/FabmanController.php +++ b/includes/Actions/Fabman/FabmanController.php @@ -75,45 +75,6 @@ public static function fetchWorkspaces($requestParams) ], 200); } - public static function fetchMemberByEmail($requestParams) - { - if (empty($requestParams->apiKey) || empty($requestParams->email)) { - wp_send_json_error(__('API Key and Email are required', 'bit-integrations'), 400); - } - - $email = $requestParams->email; - - $header = [ - 'Authorization' => 'Bearer ' . $requestParams->apiKey, - 'Content-Type' => 'application/json' - ]; - - $apiEndpoint = 'https://fabman.io/api/v1/members?q=' . urlencode($email); - - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (is_wp_error($apiResponse)) { - wp_send_json_error($apiResponse->get_error_message(), 400); - } - - if (empty($apiResponse) || isset($apiResponse->error) || !\is_array($apiResponse)) { - wp_send_json_error(isset($apiResponse->error) ? $apiResponse->error : __('Failed to fetch member', 'bit-integrations'), 400); - } - - $memberId = null; - $lockVersion = null; - if (!empty($apiResponse) && \is_array($apiResponse) && isset($apiResponse[0])) { - $memberId = $apiResponse[0]->id; - $lockVersion = $apiResponse[0]->lockVersion; - } - - wp_send_json_success([ - 'member' => $apiResponse, - 'memberId' => $memberId, - 'lockVersion' => $lockVersion - ], 200); - } - public static function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; @@ -153,7 +114,7 @@ public static function execute($integrationData, $fieldValues) private static function getMappedValue($fieldMap, $targetField, $fieldValues) { if (empty($fieldMap)) { - return null; + return; } foreach ($fieldMap as $map) { @@ -165,8 +126,6 @@ private static function getMappedValue($fieldMap, $targetField, $fieldValues) return $fieldValues[$map->formField] ?? null; } } - - return null; } private static function fetchMemberByEmailInternal($apiKey, $email) @@ -180,7 +139,7 @@ private static function fetchMemberByEmailInternal($apiKey, $email) $apiResponse = HttpHelper::get($apiEndpoint, null, $header); if (is_wp_error($apiResponse) || empty($apiResponse) || isset($apiResponse->error) || !\is_array($apiResponse)) { - return null; + return; } if (isset($apiResponse[0])) { @@ -189,7 +148,5 @@ private static function fetchMemberByEmailInternal($apiKey, $email) 'lockVersion' => $apiResponse[0]->lockVersion ]; } - - return null; } } diff --git a/includes/Actions/Fabman/Routes.php b/includes/Actions/Fabman/Routes.php index 1d1aeb2ef..cc3118b52 100644 --- a/includes/Actions/Fabman/Routes.php +++ b/includes/Actions/Fabman/Routes.php @@ -9,4 +9,3 @@ Route::post('fabman_authorization', [FabmanController::class, 'authorization']); Route::post('fabman_fetch_workspaces', [FabmanController::class, 'fetchWorkspaces']); -Route::post('fabman_fetch_member_by_email', [FabmanController::class, 'fetchMemberByEmail']); From 9f3728b6ca7a2330eab0acade44c05d659977be2 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:16:36 +0600 Subject: [PATCH 12/13] chore: update --- .../AllIntegrations/Fabman/EditFabman.jsx | 31 +- .../AllIntegrations/Fabman/Fabman.jsx | 309 ++++++++---------- .../AllIntegrations/Fabman/FabmanActions.jsx | 7 - .../Fabman/FabmanAuthorization.jsx | 27 +- .../Fabman/FabmanCommonFunc.js | 73 ++++- .../AllIntegrations/Fabman/FabmanFieldMap.jsx | 4 +- .../Fabman/FabmanIntegLayout.jsx | 28 +- .../Fabman/IntegrationHelpers.jsx | 47 +-- includes/Actions/Fabman/FabmanController.php | 18 +- includes/Actions/Fabman/RecordApiHelper.php | 3 +- 10 files changed, 256 insertions(+), 291 deletions(-) delete mode 100644 frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index 92095e1a6..c20df71e8 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -12,7 +12,7 @@ import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents import EditWebhookInteg from '../EditWebhookInteg' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' -import { checkMappedFields, handleInput } from './FabmanCommonFunc' +import { checkMappedFields, handleInput, isConfigInvalid } from './FabmanCommonFunc' import FabmanIntegLayout from './FabmanIntegLayout' import { checkValidEmail } from '../../../Utils/Helpers' @@ -29,9 +29,6 @@ function EditFabman({ allIntegURL }) { const formField = useRecoilValue($formFields) const [localName, setLocalName] = useState(fabmanConf.name || '') - useEffect(() => { - setLocalName(fabmanConf.name || '') - }, [fabmanConf.name]) const getEmailMappingRow = () => { const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] @@ -57,23 +54,6 @@ function EditFabman({ allIntegURL }) { return !(hasEmailType || !selectedField.type || looksLikeEmailField) } - const isConfigInvalid = () => { - if (!fabmanConf.actionName) return true - - if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { - if (!checkMappedFields(fabmanConf)) return true - if (isEmailMappingInvalid()) return true - return false - } - if (!checkMappedFields(fabmanConf)) return true - if ( - ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && - !fabmanConf.selectedWorkspace - ) - return true - return false - } - const saveConfig = () => { if (!fabmanConf.actionName) { setSnackbar({ show: true, msg: __('Please select an action', 'bit-integrations') }) @@ -123,11 +103,7 @@ function EditFabman({ allIntegURL }) { const handleNameChange = e => { setLocalName(e.target.value) - } - const handleNameBlur = () => { - if (localName !== fabmanConf.name) { - setFabmanConf(prev => ({ ...prev, name: localName })) - } + setFabmanConf(prev => ({ ...prev, name: e.target.value })) } return ( @@ -139,7 +115,6 @@ function EditFabman({ allIntegURL }) { { - const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] - return rows.find(r => r?.fabmanFormField === 'emailAddress') - } - - const isEmailMappingInvalid = () => { - const emailRow = getEmailMappingRow() - if (!emailRow) return true - if (emailRow.formField === 'custom') { - const customValue = (emailRow.customValue || '').trim() - return !customValue || !checkValidEmail(customValue) - } - - const selectedField = (formFields || []).find(f => f.name === emailRow.formField) - if (!selectedField) return false - const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' - const looksLikeEmailField = - /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') - return !(hasEmailType || !selectedField.type || looksLikeEmailField) - } - const isConfigInvalid = () => { if (!fabmanConf.actionName) return true if ( @@ -186,7 +172,10 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { !checkMappedFields(fabmanConf) ) return true - if (['update_member', 'delete_member'].includes(fabmanConf.actionName) && isEmailMappingInvalid()) + if ( + ['update_member', 'delete_member'].includes(fabmanConf.actionName) && + isEmailMappingInvalid(fabmanConf, formFields, checkValidEmail) + ) return true if ( ['create_member', 'update_member', 'update_spaces', 'delete_spaces'].includes( @@ -218,7 +207,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) } else if ( ['update_member', 'delete_member'].includes(fabmanConf.actionName) && - isEmailMappingInvalid() + isEmailMappingInvalid(fabmanConf, formFields, checkValidEmail) ) { setSnack({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) } else if ( @@ -229,10 +218,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { ) { setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) } else if (fabmanConf.actionName === 'delete_member') { - const hasEmailField = fabmanConf.field_map?.some( - field => field.fabmanFormField === 'emailAddress' && field.formField - ) - if (!hasEmailField) { + if (!hasEmailFieldMapped(fabmanConf)) { setSnack({ show: true, msg: __('Please map email field for member lookup', 'bit-integrations') @@ -255,7 +241,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { setSnack({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) } else if ( ['update_member', 'delete_member'].includes(fabmanConf.actionName) && - isEmailMappingInvalid() + isEmailMappingInvalid(fabmanConf, formFields, checkValidEmail) ) { setSnack({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) } else if ( @@ -266,10 +252,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { ) { setSnack({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) } else if (fabmanConf.actionName === 'delete_member') { - const hasEmailField = fabmanConf.field_map?.some( - field => field.fabmanFormField === 'emailAddress' && field.formField - ) - if (!hasEmailField) { + if (!hasEmailFieldMapped(fabmanConf)) { setSnack({ show: true, msg: __('Please map email field for member lookup', 'bit-integrations') @@ -281,24 +264,6 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { setStep(3) } - const handleWorkspaceChange = e => { - const selectedId = e.target.value - const ws = workspaces.find(w => String(w.id) === String(selectedId)) - setFabmanConf(prev => ({ - ...prev, - selectedWorkspace: selectedId, - selectedLockVersion: ws ? ws.lockVersion : null - })) - } - - const handleActionChange = e => { - setFabmanConf(prev => { - const newConf = { ...prev, actionName: e.target.value } - - return newConf - }) - } - return (
diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx deleted file mode 100644 index 73cfa0f35..000000000 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanActions.jsx +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-param-reassign */ - -import { __ } from '../../../Utils/i18nwrap' - -export default function FabmanActions({ fabmanConf, setFabmanConf, loading, setLoading }) { - return
-} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 9193790bf..708369735 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -1,7 +1,7 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable no-unused-expressions */ import { useState, useCallback, useEffect } from 'react' -import { __ } from '../../../Utils/i18nwrap' +import { __, sprintf } from '../../../Utils/i18nwrap' import LoaderSm from '../../Loaders/LoaderSm' import { fabmanAuthentication } from './FabmanCommonFunc' import Note from '../../Utilities/Note' @@ -21,12 +21,6 @@ export default function FabmanAuthorization({ const [error, setError] = useState({ name: '', apiKey: '' }) const { fabman } = tutorialLinks - const [localName, setLocalName] = useState(fabmanConf.name || '') - - useEffect(() => { - setLocalName(fabmanConf.name || '') - }, [fabmanConf.name]) - const nextPage = useCallback(() => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -37,21 +31,13 @@ export default function FabmanAuthorization({ const handleInput = useCallback( e => { const { name, value } = e.target - if (name === 'name') { - setLocalName(value) - } else { - setFabmanConf(prev => ({ ...prev, [name]: value })) - } + setFabmanConf(prev => ({ ...prev, [name]: value })) setError(prev => ({ ...prev, [name]: '' })) }, [setFabmanConf, setError] ) - const handleNameBlur = useCallback(() => { - if (localName !== fabmanConf.name) { - setFabmanConf(prev => ({ ...prev, name: localName })) - } - }, [localName, fabmanConf.name, setFabmanConf]) + const handleNameBlur = useCallback(() => {}, [setFabmanConf]) const styleStep1 = step === 1 ? { width: 900, height: 'auto' } : {} @@ -68,7 +54,7 @@ export default function FabmanAuthorization({ onChange={handleInput} onBlur={handleNameBlur} name="name" - value={localName} + value={fabmanConf.name} type="text" placeholder={__('Integration Name...', 'bit-integrations')} disabled={isInfo} @@ -130,7 +116,10 @@ export default function FabmanAuthorization({ const fabmanApiKeyNote = `

${__('To get your Fabman API key:', 'bit-integrations')}

    -
  • ${__('Log in to your Fabman account.', 'bit-integrations')}
  • +
  • ${sprintf( + __('Log in to your %s.', 'bit-integrations'), + 'Fabman account' + )}
  • ${__('Go to "Configure" → "Integrations (API & Webhooks)".', 'bit-integrations')}
  • ${__('Click "Create API key", add a title, and choose a member.', 'bit-integrations')}
  • ${__('Save, then click "Reveal" to copy your API key.', 'bit-integrations')}
  • diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js index 6c2de5255..8bba4ac6d 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -3,6 +3,7 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +import { create } from 'mutative' export const handleInput = (e, fabmanConf, setFabmanConf) => { const newConf = { ...fabmanConf } @@ -16,6 +17,8 @@ export const handleInput = (e, fabmanConf, setFabmanConf) => { } export const generateMappedField = fabmanConf => { + // The double exclamation mark (!!) is used to convert any value to a boolean. + // This ensures only fields with a truthy 'required' property are included. const requiredFlds = fabmanConf?.staticFields.filter(fld => !!fld.required) return requiredFlds.length > 0 ? requiredFlds.map(field => ({ formField: '', fabmanFormField: field.key })) @@ -24,7 +27,7 @@ export const generateMappedField = fabmanConf => { export const checkMappedFields = fabmanConf => { const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] - const invalid = rows.filter(r => { + const mappedFieldPresent = rows.filter(r => { const hasAnySide = (r?.formField && r.formField !== '') || (r?.fabmanFormField && r.fabmanFormField !== '') if (!hasAnySide) return false @@ -32,7 +35,7 @@ export const checkMappedFields = fabmanConf => { if (r.formField === 'custom' && !r.customValue) return true return false }) - return invalid.length === 0 + return mappedFieldPresent.length === 0 } export const fabmanAuthentication = ( @@ -59,12 +62,14 @@ export const fabmanAuthentication = ( bitsFetch(requestParams, 'fabman_authorization').then(result => { if (result && result.success) { - const newConf = { ...confTmp } + // Use mutative's produce for state update + const newConf = create(confTmp, draft => { + if (type === 'authentication' && result.data && result.data.accountId) { + draft.accountId = result.data.accountId + } + }) setIsAuthorized(true) if (type === 'authentication') { - if (result.data && result.data.accountId) { - newConf.accountId = result.data.accountId - } setConf(newConf) setLoading({ ...loading, auth: false }) toast.success(__('Authorized Successfully', 'bit-integrations')) @@ -91,13 +96,15 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ bitsFetch(requestParams, 'fabman_fetch_workspaces').then(result => { setLoading({ ...loading, workspaces: false }) if (result && result.success) { - const newConf = { ...confTmp } - if (result.data && result.data.workspaces && Array.isArray(result.data.workspaces)) { - newConf.workspaces = result.data.workspaces - if (result.data.workspaces.length === 1) { - newConf.selectedWorkspace = result.data.workspaces[0].id + // Use mutative's produce for state update + const newConf = create(confTmp, draft => { + if (result.data && result.data.workspaces && Array.isArray(result.data.workspaces)) { + draft.workspaces = result.data.workspaces + if (result.data.workspaces.length === 1) { + draft.selectedWorkspace = result.data.workspaces[0].id + } } - } + }) setConf(newConf) toast.success( type === 'refresh' @@ -109,3 +116,45 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ toast.error(__('Failed to fetch workspaces', 'bit-integrations')) }) } + +export const isConfigInvalid = (fabmanConf, formField) => { + if (!fabmanConf.actionName) return true + + if (['update_member', 'delete_member'].includes(fabmanConf.actionName)) { + // isEmailMappingInvalid needs to be passed or imported + if (isEmailMappingInvalid(fabmanConf, formField)) return true + if (!checkMappedFields(fabmanConf)) return true + return false + } + if ( + ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && + !fabmanConf.selectedWorkspace + ) + return true + if (!checkMappedFields(fabmanConf)) return true + return false +} + +export const hasEmailFieldMapped = fabmanConf => { + return fabmanConf.field_map?.some(field => field.fabmanFormField === 'emailAddress' && field.formField) +} + +export const getEmailMappingRow = fabmanConf => { + const rows = Array.isArray(fabmanConf?.field_map) ? fabmanConf.field_map : [] + return rows.find(r => r?.fabmanFormField === 'emailAddress') +} + +export const isEmailMappingInvalid = (fabmanConf, formFields, checkValidEmail) => { + const emailRow = getEmailMappingRow(fabmanConf) + if (!emailRow) return true + if (emailRow.formField === 'custom') { + const customValue = (emailRow.customValue || '').trim() + return !customValue || !checkValidEmail(customValue) + } + const selectedField = (formFields || []).find(f => f.name === emailRow.formField) + if (!selectedField) return false + const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' + const looksLikeEmailField = + /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') + return !(hasEmailType || !selectedField.type || looksLikeEmailField) +} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx index db40720e7..0347d00bd 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanFieldMap.jsx @@ -11,11 +11,11 @@ import { handleCustomValue } from '../IntegrationHelpers/IntegrationHelpers' export default function FabmanFieldMap({ i, formFields, field, fabmanConf, setFabmanConf }) { const requiredFields = useMemo( - () => fabmanConf?.staticFields.filter(fld => !!fld.required) || [], + () => (fabmanConf?.staticFields ? fabmanConf.staticFields.filter(fld => !!fld.required) : []), [fabmanConf?.staticFields] ) const nonRequriedFields = useMemo( - () => fabmanConf?.staticFields?.filter(fld => !fld.required) || [], + () => (fabmanConf?.staticFields ? fabmanConf.staticFields.filter(fld => !fld.required) : []), [fabmanConf?.staticFields] ) const customFields = useMemo( diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx index dedc1dd48..49dcbb595 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -3,12 +3,19 @@ import { __ } from '../../../Utils/i18nwrap' import FabmanFieldMap from './FabmanFieldMap' import { addFieldMap } from './IntegrationHelpers' -import FabmanActions from './FabmanActions' import { fetchFabmanWorkspaces, generateMappedField } from './FabmanCommonFunc' import Loader from '../../Loaders/Loader' import { useEffect, useMemo, useCallback, useRef } from 'react' import Note from '../../Utilities/Note' +const fabmanActionsList = [ + { label: __('Create Member', 'bit-integrations'), value: 'create_member' }, + { label: __('Update Member', 'bit-integrations'), value: 'update_member' }, + { label: __('Delete Member', 'bit-integrations'), value: 'delete_member' }, + { label: __('Create Spaces', 'bit-integrations'), value: 'create_spaces' }, + { label: __('Update Spaces', 'bit-integrations'), value: 'update_spaces' } +] + export default function FabmanIntegLayout({ formFields, fabmanConf, @@ -17,17 +24,6 @@ export default function FabmanIntegLayout({ setLoading, setSnackbar }) { - const actions = useMemo( - () => [ - { label: __('Create Member', 'bit-integrations'), value: 'create_member' }, - { label: __('Update Member', 'bit-integrations'), value: 'update_member' }, - { label: __('Delete Member', 'bit-integrations'), value: 'delete_member' }, - { label: __('Create Spaces', 'bit-integrations'), value: 'create_spaces' }, - { label: __('Update Spaces', 'bit-integrations'), value: 'update_spaces' } - ], - [] - ) - const getActiveStaticFields = useCallback(conf => { if (conf.actionName === 'create_spaces' || conf.actionName === 'update_spaces') { const fields = Array.isArray(conf.spacesStaticFields) @@ -161,7 +157,7 @@ export default function FabmanIntegLayout({ value={fabmanConf?.actionName} className="btcd-paper-inp w-5"> - {actions.map(({ label, value }) => ( + {fabmanActionsList.map(({ label, value }) => ( @@ -292,12 +288,6 @@ export default function FabmanIntegLayout({ {__('Utilities', 'bit-integrations')}
-
)} diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx index 9f6d60322..2d81cff9c 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx @@ -1,29 +1,34 @@ +import { create } from 'mutative' + export const addFieldMap = (i, confTmp, setConf) => { - const newConf = { ...confTmp } - const fieldMap = Array.isArray(newConf.field_map) ? [...newConf.field_map] : [] - fieldMap.splice(i, 0, {}) - newConf.field_map = fieldMap - setConf({ ...newConf }) + const newConf = create(confTmp, draft => { + const fieldMap = Array.isArray(draft.field_map) ? [...draft.field_map] : [] + fieldMap.splice(i, 0, {}) + draft.field_map = fieldMap + }) + setConf(newConf) } export const delFieldMap = (i, confTmp, setConf) => { - const newConf = { ...confTmp } - const fieldMap = Array.isArray(newConf.field_map) ? [...newConf.field_map] : [] - if (fieldMap.length > 1) { - fieldMap.splice(i, 1) - } - newConf.field_map = fieldMap - setConf({ ...newConf }) + const newConf = create(confTmp, draft => { + const fieldMap = Array.isArray(draft.field_map) ? [...draft.field_map] : [] + if (fieldMap.length > 1) { + fieldMap.splice(i, 1) + } + draft.field_map = fieldMap + }) + setConf(newConf) } export const handleFieldMapping = (event, index, conftTmp, setConf) => { - const newConf = { ...conftTmp } - const fieldMap = Array.isArray(newConf.field_map) ? [...newConf.field_map] : [] - if (!fieldMap[index]) fieldMap[index] = {} - fieldMap[index][event.target.name] = event.target.value - if (event.target.value === 'custom') { - fieldMap[index].customValue = '' - } - newConf.field_map = fieldMap - setConf({ ...newConf }) + const newConf = create(conftTmp, draft => { + const fieldMap = Array.isArray(draft.field_map) ? [...draft.field_map] : [] + if (!fieldMap[index]) fieldMap[index] = {} + fieldMap[index][event.target.name] = event.target.value + if (event.target.value === 'custom') { + fieldMap[index].customValue = '' + } + draft.field_map = fieldMap + }) + setConf(newConf) } diff --git a/includes/Actions/Fabman/FabmanController.php b/includes/Actions/Fabman/FabmanController.php index 4f142d879..b5e4e3062 100644 --- a/includes/Actions/Fabman/FabmanController.php +++ b/includes/Actions/Fabman/FabmanController.php @@ -10,15 +10,15 @@ class FabmanController { - public static function info() - { - return [ - 'name' => 'Fabman', - 'title' => __('Fabman', 'bit-integrations'), - 'type' => 'action', - 'integrationID' => 0 - ]; - } + // public static function info() + // { + // return [ + // 'name' => 'Fabman', + // 'title' => __('Fabman', 'bit-integrations'), + // 'type' => 'action', + // 'integrationID' => 0 + // ]; + // } public static function authorization($requestParams) { diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php index 5c01013d3..99154a40d 100644 --- a/includes/Actions/Fabman/RecordApiHelper.php +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -59,7 +59,6 @@ public function execute($actionName, $fieldValues, $integrationDetails) switch ($actionName) { case 'create_member': $apiResponse = $this->createMember($finalData); - $apiResponse = HttpHelper::$responseCode === 201 ? 'Member Created Successfully' : 'Failed'; break; case 'update_member': @@ -112,7 +111,7 @@ private function createMember($data) return new WP_Error('API_ERROR', isset($apiResponse->error) ? $apiResponse->error : \__('Failed to create member', 'bit-integrations')); } - return $apiResponse; + return HttpHelper::$responseCode === 201 ? 'Member Created Successfully' : 'Failed'; } private function updateMember($data) From 0dbf4e51d5638698b684061708a89ba4ce685de7 Mon Sep 17 00:00:00 2001 From: "Md. Abed Hossen" <95869988+Md-Abed-Hossen@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:21:58 +0600 Subject: [PATCH 13/13] chore: formatting --- .../AllIntegrations/Fabman/EditFabman.jsx | 11 +- .../AllIntegrations/Fabman/Fabman.jsx | 247 +++++++++--------- .../Fabman/FabmanAuthorization.jsx | 2 - .../Fabman/FabmanCommonFunc.js | 16 ++ .../Fabman/FabmanIntegLayout.jsx | 8 + .../Fabman/IntegrationHelpers.jsx | 6 + includes/Actions/Fabman/FabmanController.php | 10 - includes/Actions/Fabman/RecordApiHelper.php | 4 +- 8 files changed, 165 insertions(+), 139 deletions(-) diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx index c20df71e8..1ee096299 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/EditFabman.jsx @@ -47,7 +47,9 @@ function EditFabman({ allIntegURL }) { // - type is missing/unknown, OR // - field name/label includes "email" const selectedField = (formField || []).find(f => f.name === emailRow.formField) + if (!selectedField) return false + const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' const looksLikeEmailField = /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') @@ -65,10 +67,12 @@ function EditFabman({ allIntegURL }) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } + if (isEmailMappingInvalid()) { setSnackbar({ show: true, msg: __('Please map a valid email address', 'bit-integrations') }) return } + saveActionConf({ flow, allIntegURL, @@ -80,16 +84,20 @@ function EditFabman({ allIntegURL }) { }) return } + if (!checkMappedFields(fabmanConf)) { setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) return } + const requiresWorkspaceSelection = fabmanConf.actionName === 'update_spaces' || fabmanConf.actionName === 'delete_spaces' + if (requiresWorkspaceSelection && !fabmanConf.selectedWorkspace) { setSnackbar({ show: true, msg: __('Please select a workspace', 'bit-integrations') }) return } + saveActionConf({ flow, allIntegURL, @@ -109,7 +117,6 @@ function EditFabman({ allIntegURL }) { return (
-
{__('Integration Name:', 'bit-integrations')}

- - { if (!fabmanConf.actionName) return true + if ( !['delete_member', 'delete_spaces'].includes(fabmanConf.actionName) && !checkMappedFields(fabmanConf) ) return true + if ( ['update_member', 'delete_member'].includes(fabmanConf.actionName) && isEmailMappingInvalid(fabmanConf, formFields, checkValidEmail) ) return true + if ( ['create_member', 'update_member', 'update_spaces', 'delete_spaces'].includes( fabmanConf.actionName @@ -189,6 +73,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { const hasEmailField = fabmanConf.field_map?.some( field => field.fabmanFormField === 'emailAddress' && field.formField ) + if (!hasEmailField) { return true } @@ -270,7 +155,6 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) {
- {/* STEP 1 */} - {/* STEP 2 */} {step === 2 && (
-
)} - {/* STEP 3 */} ) } + +const memberStaticFields = [ + { key: 'emailAddress', label: __('Email Address', 'bit-integrations'), required: false }, + { key: 'firstName', label: __('First Name', 'bit-integrations'), required: true }, + { key: 'lastName', label: __('Last Name', 'bit-integrations'), required: false }, + { key: 'memberNumber', label: __('Member Number', 'bit-integrations'), required: false }, + { key: 'gender', label: __('Gender', 'bit-integrations'), required: false }, + { key: 'dateOfBirth', label: __('Date of Birth', 'bit-integrations'), required: false }, + { key: 'company', label: __('Company', 'bit-integrations'), required: false }, + { key: 'phone', label: __('Phone', 'bit-integrations'), required: false }, + { key: 'address', label: __('Address', 'bit-integrations'), required: false }, + { key: 'address2', label: __('Address 2', 'bit-integrations'), required: false }, + { key: 'city', label: __('City', 'bit-integrations'), required: false }, + { key: 'zip', label: __('ZIP Code', 'bit-integrations'), required: false }, + { key: 'countryCode', label: __('Country Code', 'bit-integrations'), required: false }, + { key: 'region', label: __('Region', 'bit-integrations'), required: false }, + { key: 'notes', label: __('Notes', 'bit-integrations'), required: false }, + { key: 'billingFirstName', label: __('Billing First Name', 'bit-integrations'), required: false }, + { key: 'billingLastName', label: __('Billing Last Name', 'bit-integrations'), required: false }, + { key: 'billingCompany', label: __('Billing Company', 'bit-integrations'), required: false }, + { key: 'billingAddress', label: __('Billing Address', 'bit-integrations'), required: false }, + { key: 'billingAddress2', label: __('Billing Address 2', 'bit-integrations'), required: false }, + { key: 'billingCity', label: __('Billing City', 'bit-integrations'), required: false }, + { key: 'billingZip', label: __('Billing ZIP Code', 'bit-integrations'), required: false }, + { + key: 'billingCountryCode', + label: __('Billing Country Code', 'bit-integrations'), + required: false + }, + { key: 'billingRegion', label: __('Billing Region', 'bit-integrations'), required: false }, + { + key: 'billingInvoiceText', + label: __('Billing Invoice Text', 'bit-integrations'), + required: false + }, + { + key: 'billingEmailAddress', + label: __('Billing Email Address', 'bit-integrations'), + required: false + }, + { key: 'language', label: __('Language', 'bit-integrations'), required: false }, + { key: 'state', label: __('State', 'bit-integrations'), required: false }, + { key: 'taxExempt', label: __('Tax Exempt', 'bit-integrations'), required: false }, + { + key: 'hasBillingAddress', + label: __('Has Billing Address', 'bit-integrations'), + required: false + }, + { + key: 'requireUpfrontPayment', + label: __('Require Upfront Payment', 'bit-integrations'), + required: false + }, + { + key: 'upfrontMinimumBalance', + label: __('Upfront Minimum Balance', 'bit-integrations'), + required: false + } +] + +const spacesStaticFields = [ + { key: 'name', label: __('Name', 'bit-integrations'), required: true }, + { key: 'shortName', label: __('Short Name', 'bit-integrations'), required: false }, + { key: 'timezone', label: __('Timezone', 'bit-integrations'), required: true }, + { key: 'emailAddress', label: __('Email Address', 'bit-integrations'), required: false }, + { key: 'website', label: __('Website', 'bit-integrations'), required: false }, + { key: 'phone', label: __('Phone', 'bit-integrations'), required: false }, + { key: 'infoText', label: __('Info Text', 'bit-integrations'), required: false }, + { + key: 'bookingTermsOfService', + label: __('Booking Terms Of Service', 'bit-integrations'), + required: false + }, + { + key: 'bookingSlotsPerHour', + label: __('Booking Slots Per Hour', 'bit-integrations'), + required: false + }, + { + key: 'bookingWindowMinHours', + label: __('Booking Window Min Hours', 'bit-integrations'), + required: false + }, + { + key: 'bookingWindowMaxDays', + label: __('Booking Window Max Days', 'bit-integrations'), + required: false + }, + { + key: 'bookingLockInHours', + label: __('Booking Lock-in Hours', 'bit-integrations'), + required: false + }, + { + key: 'bookingMaxMinutesPerMemberDay', + label: __('Booking Max Minutes Per Member/Day', 'bit-integrations'), + required: false + }, + { + key: 'bookingMaxMinutesPerMemberWeek', + label: __('Booking Max Minutes Per Member/Week', 'bit-integrations'), + required: false + }, + { + key: 'bookingExclusiveMinutes', + label: __('Booking Exclusive Minutes', 'bit-integrations'), + required: false + }, + { + key: 'bookingOpeningHours', + label: __('Booking Opening Hours', 'bit-integrations'), + required: false + }, + { key: 'bookingRefundable', label: __('Booking Refundable', 'bit-integrations'), required: false }, + { + key: 'bookingNamesPublic', + label: __('Booking Names Public', 'bit-integrations'), + required: false + } +] diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 708369735..c22f82fe9 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -45,7 +45,6 @@ export default function FabmanAuthorization({
{fabman?.youTubeLink && } {fabman?.docLink && } -
{__('Integration Name:', 'bit-integrations')}
@@ -59,7 +58,6 @@ export default function FabmanAuthorization({ placeholder={__('Integration Name...', 'bit-integrations')} disabled={isInfo} /> -
{__('API Key:', 'bit-integrations')}
diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js index 8bba4ac6d..99385bfa8 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -8,11 +8,13 @@ import { create } from 'mutative' export const handleInput = (e, fabmanConf, setFabmanConf) => { const newConf = { ...fabmanConf } const { name } = e.target + if (e.target.value !== '') { newConf[name] = e.target.value } else { delete newConf[name] } + setFabmanConf({ ...newConf }) } @@ -30,8 +32,11 @@ export const checkMappedFields = fabmanConf => { const mappedFieldPresent = rows.filter(r => { const hasAnySide = (r?.formField && r.formField !== '') || (r?.fabmanFormField && r.fabmanFormField !== '') + if (!hasAnySide) return false + if (!r.formField || !r.fabmanFormField) return true + if (r.formField === 'custom' && !r.customValue) return true return false }) @@ -68,7 +73,9 @@ export const fabmanAuthentication = ( draft.accountId = result.data.accountId } }) + setIsAuthorized(true) + if (type === 'authentication') { setConf(newConf) setLoading({ ...loading, auth: false }) @@ -95,6 +102,7 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ bitsFetch(requestParams, 'fabman_fetch_workspaces').then(result => { setLoading({ ...loading, workspaces: false }) + if (result && result.success) { // Use mutative's produce for state update const newConf = create(confTmp, draft => { @@ -105,6 +113,7 @@ export const fetchFabmanWorkspaces = (confTmp, setConf, loading, setLoading, typ } } }) + setConf(newConf) toast.success( type === 'refresh' @@ -126,11 +135,13 @@ export const isConfigInvalid = (fabmanConf, formField) => { if (!checkMappedFields(fabmanConf)) return true return false } + if ( ['update_spaces', 'delete_spaces'].includes(fabmanConf.actionName) && !fabmanConf.selectedWorkspace ) return true + if (!checkMappedFields(fabmanConf)) return true return false } @@ -146,13 +157,18 @@ export const getEmailMappingRow = fabmanConf => { export const isEmailMappingInvalid = (fabmanConf, formFields, checkValidEmail) => { const emailRow = getEmailMappingRow(fabmanConf) + if (!emailRow) return true + if (emailRow.formField === 'custom') { const customValue = (emailRow.customValue || '').trim() return !customValue || !checkValidEmail(customValue) } + const selectedField = (formFields || []).find(f => f.name === emailRow.formField) + if (!selectedField) return false + const hasEmailType = selectedField.type && String(selectedField.type).toLowerCase() === 'email' const looksLikeEmailField = /email/i.test(selectedField.name || '') || /email/i.test(selectedField.label || '') diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx index 49dcbb595..c2eabbcb0 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/FabmanIntegLayout.jsx @@ -31,8 +31,11 @@ export default function FabmanIntegLayout({ : [] const isCreate = conf.actionName === 'create_spaces' const nameIdx = fields.findIndex(f => String(f.key) === 'name') + if (nameIdx > -1) fields[nameIdx].required = true + const tzIdx = fields.findIndex(f => String(f.key) === 'timezone') + if (tzIdx > -1) fields[tzIdx].required = isCreate return fields } @@ -42,8 +45,11 @@ export default function FabmanIntegLayout({ ? conf.memberStaticFields.map(f => ({ ...f })) : [] const emailIdx = fields.findIndex(f => String(f.key) === 'emailAddress') + if (emailIdx > -1) fields[emailIdx].required = true + const firstNameIdx = fields.findIndex(f => String(f.key) === 'firstName') + if (firstNameIdx > -1) fields[firstNameIdx].required = false return fields } @@ -54,6 +60,7 @@ export default function FabmanIntegLayout({ useEffect(() => { const staticFields = getActiveStaticFields(fabmanConf) || [] const requiredFields = staticFields.filter(f => !!f.required) + if (!Array.isArray(fabmanConf.field_map)) return let changed = false @@ -99,6 +106,7 @@ export default function FabmanIntegLayout({ newConf.selectedWorkspace = e.target.value const ws = (fabmanConf?.workspaces || []).find(w => String(w.id) === String(e.target.value)) + if (ws && typeof ws.lockVersion !== 'undefined') { newConf.selectedLockVersion = ws.lockVersion } diff --git a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx index 2d81cff9c..c4e635e61 100644 --- a/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx +++ b/frontend-dev/src/components/AllIntegrations/Fabman/IntegrationHelpers.jsx @@ -6,29 +6,35 @@ export const addFieldMap = (i, confTmp, setConf) => { fieldMap.splice(i, 0, {}) draft.field_map = fieldMap }) + setConf(newConf) } export const delFieldMap = (i, confTmp, setConf) => { const newConf = create(confTmp, draft => { const fieldMap = Array.isArray(draft.field_map) ? [...draft.field_map] : [] + if (fieldMap.length > 1) { fieldMap.splice(i, 1) } draft.field_map = fieldMap }) + setConf(newConf) } export const handleFieldMapping = (event, index, conftTmp, setConf) => { const newConf = create(conftTmp, draft => { const fieldMap = Array.isArray(draft.field_map) ? [...draft.field_map] : [] + if (!fieldMap[index]) fieldMap[index] = {} fieldMap[index][event.target.name] = event.target.value + if (event.target.value === 'custom') { fieldMap[index].customValue = '' } draft.field_map = fieldMap }) + setConf(newConf) } diff --git a/includes/Actions/Fabman/FabmanController.php b/includes/Actions/Fabman/FabmanController.php index b5e4e3062..7e73f935c 100644 --- a/includes/Actions/Fabman/FabmanController.php +++ b/includes/Actions/Fabman/FabmanController.php @@ -10,16 +10,6 @@ class FabmanController { - // public static function info() - // { - // return [ - // 'name' => 'Fabman', - // 'title' => __('Fabman', 'bit-integrations'), - // 'type' => 'action', - // 'integrationID' => 0 - // ]; - // } - public static function authorization($requestParams) { if (empty($requestParams->apiKey)) { diff --git a/includes/Actions/Fabman/RecordApiHelper.php b/includes/Actions/Fabman/RecordApiHelper.php index 99154a40d..3f88b48e7 100644 --- a/includes/Actions/Fabman/RecordApiHelper.php +++ b/includes/Actions/Fabman/RecordApiHelper.php @@ -100,7 +100,6 @@ private function createMember($data) unset($data['memberId']); $apiEndpoint = $this->apiEndpoint . '/members'; $header = $this->setHeaders(); - $apiResponse = HttpHelper::post($apiEndpoint, json_encode($data), $header); if (\is_wp_error($apiResponse)) { @@ -117,6 +116,7 @@ private function createMember($data) private function updateMember($data) { $data['lockVersion'] = $this->lockVersion; + if (empty($this->memberId)) { return new WP_Error('MISSING_MEMBER_ID', __('The email provided did not match any existing Fabman member.', 'bit-integrations')); } @@ -148,9 +148,11 @@ private function createSpace($data) private function updateSpace($data) { $data['lockVersion'] = $this->lockVersion; + if (empty($this->workspaceId)) { return new WP_Error('MISSING_SPACE_ID', __('Please select a space to update.', 'bit-integrations')); } + $response = \apply_filters('btcbi_fabman_update_space', false, json_encode($data), $this->setHeaders(), $this->apiEndpoint, $this->workspaceId); return $this->handleFilterResponse($response);