diff --git a/backend/Actions/MondayCom/MondayComController.php b/backend/Actions/MondayCom/MondayComController.php new file mode 100644 index 000000000..9a3f01a77 --- /dev/null +++ b/backend/Actions/MondayCom/MondayComController.php @@ -0,0 +1,239 @@ +apiToken)) { + wp_send_json_error(__('API Token is empty', 'bit-integrations'), 400); + } + + $query = 'query { me { id name email } }'; + $response = self::request($requestParams->apiToken, $query); + + if (!self::hasErrors($response) && isset($response->data->me->id)) { + wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); + } + + wp_send_json_error(self::errorMessage($response, __('Authentication failed', 'bit-integrations')), 400); + } + + public function getBoards($requestParams) + { + self::validateToken($requestParams); + + $query = <<<'GRAPHQL' + query ($limit: Int) { + boards (limit: $limit) { + id + name + } + } + GRAPHQL; + $response = self::request($requestParams->apiToken, $query, ['limit' => 100]); + + if (self::hasErrors($response) || !isset($response->data->boards)) { + wp_send_json_error(self::errorMessage($response, __('Boards fetching failed', 'bit-integrations')), 400); + } + + $boards = array_map( + fn ($b) => (object) ['id' => $b->id, 'name' => $b->name], + $response->data->boards + ); + + wp_send_json_success($boards, 200); + } + + public function getGroups($requestParams) + { + self::validateToken($requestParams); + if (empty($requestParams->boardId)) { + wp_send_json_error(__('Board ID is empty', 'bit-integrations'), 400); + } + + $query = <<<'GRAPHQL' + query ($boardIds: [ID!]) { + boards (ids: $boardIds) { + groups { + id + title + } + } + } + GRAPHQL; + $response = self::request($requestParams->apiToken, $query, ['boardIds' => [(string) $requestParams->boardId]]); + + if (self::hasErrors($response) || !isset($response->data->boards[0]->groups)) { + wp_send_json_error(self::errorMessage($response, __('Groups fetching failed', 'bit-integrations')), 400); + } + + $groups = array_map( + fn ($g) => (object) ['id' => $g->id, 'name' => $g->title], + $response->data->boards[0]->groups + ); + + wp_send_json_success($groups, 200); + } + + public function getColumns($requestParams) + { + self::validateToken($requestParams); + if (empty($requestParams->boardId)) { + wp_send_json_error(__('Board ID is empty', 'bit-integrations'), 400); + } + + $query = <<<'GRAPHQL' + query ($boardIds: [ID!]) { + boards (ids: $boardIds) { + columns { + id + title + type + } + } + } + GRAPHQL; + $response = self::request($requestParams->apiToken, $query, ['boardIds' => [(string) $requestParams->boardId]]); + + if (self::hasErrors($response) || !isset($response->data->boards[0]->columns)) { + wp_send_json_error(self::errorMessage($response, __('Columns fetching failed', 'bit-integrations')), 400); + } + + $columns = []; + foreach (reset($response->data->boards)->columns as $column) { + if ($column->type !== 'file') { + $columns[] = (object) [ + 'key' => $column->id, + 'label' => $column->title, + 'type' => $column->type, + 'required' => false, + ]; + } + } + + wp_send_json_success($columns, 200); + } + + public function getItems($requestParams) + { + self::validateToken($requestParams); + if (empty($requestParams->boardId)) { + wp_send_json_error(__('Board ID is empty', 'bit-integrations'), 400); + } + + $query = <<<'GRAPHQL' + query ($boardIds: [ID!], $limit: Int) { + boards (ids: $boardIds) { + items_page (limit: $limit) { + items { + id + name + } + } + } + } + GRAPHQL; + $response = self::request( + $requestParams->apiToken, + $query, + [ + 'boardIds' => [(string) $requestParams->boardId], + 'limit' => 100, + ] + ); + + if (self::hasErrors($response) || !isset($response->data->boards[0]->items_page->items)) { + wp_send_json_error(self::errorMessage($response, __('Items fetching failed', 'bit-integrations')), 400); + } + + $items = array_map( + fn ($i) => (object) ['id' => $i->id, 'name' => $i->name], + $response->data->boards[0]->items_page->items + ); + + wp_send_json_success($items, 200); + } + + public function execute($integrationData, $fieldValues) + { + $integrationDetails = $integrationData->flow_details; + $integId = $integrationData->id; + $fieldMap = $integrationDetails->field_map ?? []; + $apiToken = $integrationDetails->apiToken ?? ''; + + if (empty($apiToken)) { + return new WP_Error('REQ_FIELD_EMPTY', __('API Token is required for Monday.com api', 'bit-integrations')); + } + + $recordApiHelper = new RecordApiHelper($integrationDetails, $integId, $apiToken); + $response = $recordApiHelper->execute($fieldValues, $fieldMap); + + if (is_wp_error($response)) { + return $response; + } + + return $response; + } + + private static function validateToken($requestParams) + { + if (empty($requestParams->apiToken)) { + wp_send_json_error(__('API Token is empty', 'bit-integrations'), 400); + } + } + + private static function setHeaders($apiToken) + { + return [ + 'Authorization' => $apiToken, + 'Content-Type' => 'application/json', + 'API-Version' => self::API_VERSION, + ]; + } + + private static function request($apiToken, $query, $variables = []) + { + $body = ['query' => $query]; + + if (!empty($variables)) { + $body['variables'] = $variables; + } + + return HttpHelper::post(self::API_URL, wp_json_encode($body), self::setHeaders($apiToken)); + } + + private static function hasErrors($response) + { + return is_wp_error($response) || !empty($response->errors) || !empty($response->error); + } + + private static function errorMessage($response, $fallback) + { + if (is_wp_error($response)) { + return $response->get_error_message(); + } + + if (!empty($response->errors[0]->message)) { + return $response->errors[0]->message; + } + + if (!empty($response->error)) { + return \is_string($response->error) ? $response->error : wp_json_encode($response->error); + } + + return $fallback; + } +} diff --git a/backend/Actions/MondayCom/RecordApiHelper.php b/backend/Actions/MondayCom/RecordApiHelper.php new file mode 100644 index 000000000..560bc0548 --- /dev/null +++ b/backend/Actions/MondayCom/RecordApiHelper.php @@ -0,0 +1,151 @@ +integrationDetails = $integrationDetails; + $this->integrationId = $integId; + $this->apiToken = $apiToken; + } + + public function handleFilterResponse($response) + { + if ($response) { + return $response; + } + + // translators: %s: Placeholder value + return (object) ['error' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro')]; + } + + public function generateReqDataFromFieldMap($fieldValues, $fieldMap) + { + $dataFinal = []; + foreach ($fieldMap as $item) { + $triggerValue = $item->formField ?? ''; + $actionValue = $item->mondayComField ?? ''; + + if (empty($actionValue)) { + continue; + } + + if ($triggerValue === 'custom') { + $dataFinal[$actionValue] = Common::replaceFieldWithValue($item->customValue ?? '', $fieldValues); + } elseif (!empty($triggerValue)) { + $dataFinal[$actionValue] = $fieldValues[$triggerValue] ?? ''; + } + } + + return $dataFinal; + } + + public function execute($fieldValues, $fieldMap) + { + $fieldData = $this->generateReqDataFromFieldMap($fieldValues, $fieldMap); + $mainAction = $this->integrationDetails->mainAction ?? ''; + $apiResponse = null; + $responseType = null; + + $this->type = 'item'; + $this->typeName = $mainAction; + + switch ($mainAction) { + case 'create_item': + $this->type = 'item'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_create_item'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'update_item': + $this->type = 'item'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_update_item'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'create_subitem': + $this->type = 'subitem'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_create_subitem'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'move_item_to_group': + $this->type = 'item'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_move_item_to_group'), false, $fieldData, $this->apiToken); + + break; + case 'archive_item': + $this->type = 'item'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_archive_item'), false, $fieldData, $this->apiToken); + + break; + case 'delete_item': + $this->type = 'item'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_delete_item'), false, $fieldData, $this->apiToken); + + break; + case 'archive_board': + $this->type = 'board'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_archive_board'), false, $fieldData, $this->apiToken); + + break; + case 'archive_group': + $this->type = 'group'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_archive_group'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'delete_group': + $this->type = 'group'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_delete_group'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'create_group': + $this->type = 'group'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_create_group'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'duplicate_group': + $this->type = 'group'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_duplicate_group'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + case 'create_column': + $this->type = 'column'; + $apiResponse = Hooks::apply(Config::withPrefix('mondayCom_create_column'), false, $fieldData, $this->integrationDetails, $this->apiToken); + + break; + default: + $apiResponse = null; + } + + $apiResponse = $this->handleFilterResponse($apiResponse); + $responseType = $this->hasErrors($apiResponse) || !isset($apiResponse->data) ? 'error' : 'success'; + + LogHandler::save($this->integrationId, wp_json_encode(['type' => $this->type, 'type_name' => $this->typeName]), $responseType, wp_json_encode($apiResponse)); + + return $apiResponse; + } + + private function hasErrors($response) + { + return is_wp_error($response) || !empty($response->errors) || !empty($response->error); + } +} diff --git a/backend/Actions/MondayCom/Routes.php b/backend/Actions/MondayCom/Routes.php new file mode 100644 index 000000000..73fe8153c --- /dev/null +++ b/backend/Actions/MondayCom/Routes.php @@ -0,0 +1,14 @@ + import('./TheEventsCalendar/EditTheEven const EditLMFWC = lazy(() => import('./LMFWC/EditLMFWC')) const EditVoxel = lazy(() => import('./Voxel/EditVoxel')) const EditSmartSuite = lazy(() => import('./SmartSuite/EditSmartSuite')) +const EditMondayCom = lazy(() => import('./MondayCom/EditMondayCom')) const EditBento = lazy(() => import('./Bento/EditBento')) const EditLine = lazy(() => import('./Line/EditLine')) const EditACPT = lazy(() => import('./ACPT/EditACPT')) @@ -580,6 +581,8 @@ const IntegType = memo(({ allIntegURL, flow }) => { return case 'SmartSuite': return + case 'Monday.Com': + return case 'Bento': return case 'Line': diff --git a/frontend/src/components/AllIntegrations/IntegInfo.jsx b/frontend/src/components/AllIntegrations/IntegInfo.jsx index 03de88b5b..0a797372c 100644 --- a/frontend/src/components/AllIntegrations/IntegInfo.jsx +++ b/frontend/src/components/AllIntegrations/IntegInfo.jsx @@ -166,6 +166,7 @@ const TheEventsCalendarAuthorization = lazy( const LMFWCAuthorization = lazy(() => import('./LMFWC/LMFWCAuthorization')) const VoxelAuthorization = lazy(() => import('./Voxel/VoxelAuthorization')) const SmartSuiteAuthorization = lazy(() => import('./SmartSuite/SmartSuiteAuthorization')) +const MondayComAuthorization = lazy(() => import('./MondayCom/MondayComAuthorization')) const BentoAuthorization = lazy(() => import('./Bento/BentoAuthorization')) const LineAuthorization = lazy(() => import('./Line/LineAuthorization')) const ACPTAuthorization = lazy(() => import('./ACPT/ACPTAuthorization')) @@ -617,6 +618,8 @@ export default function IntegInfo() { return case 'SmartSuite': return + case 'Monday.Com': + return case 'Bento': return case 'Line': diff --git a/frontend/src/components/AllIntegrations/MondayCom/EditMondayCom.jsx b/frontend/src/components/AllIntegrations/MondayCom/EditMondayCom.jsx new file mode 100644 index 000000000..65a0ab4ff --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/EditMondayCom.jsx @@ -0,0 +1,96 @@ +/* eslint-disable no-unused-vars */ +import { useState } from 'react' +import { toast } from 'react-hot-toast' +import { useNavigate } from 'react-router' +import { useRecoilState, useRecoilValue } from 'recoil' +import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import SnackMsg from '../../Utilities/SnackMsg' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' +import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents' +import { checkMappedFields, handleInput } from './MondayComCommonFunc' +import MondayComIntegLayout from './MondayComIntegLayout' +import { needsBoard, needsItem } from './staticData' + +function EditMondayCom({ allIntegURL }) { + const navigate = useNavigate() + const [flow, setFlow] = useRecoilState($newFlow) + const [mondayComConf, setMondayComConf] = useRecoilState($actionConf) + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({}) + const [snack, setSnackbar] = useState({ show: false }) + const formField = useRecoilValue($formFields) + + const saveConfig = () => { + if (!checkMappedFields(mondayComConf)) { + setSnackbar({ show: true, msg: __('Please map mandatory fields', 'bit-integrations') }) + return + } + const action = mondayComConf.mainAction + if (needsBoard.includes(action) && !mondayComConf.selectedBoard) { + toast.error(__('Please select a board', 'bit-integrations')) + return + } + if (needsItem.includes(action) && !mondayComConf.selectedItem) { + toast.error(__('Please select an item', 'bit-integrations')) + return + } + if (action === 'create_column' && !mondayComConf.columnType) { + toast.error(__('Please select a column type', 'bit-integrations')) + return + } + + saveActionConf({ + flow, + allIntegURL, + conf: mondayComConf, + navigate, + edit: 1, + setIsLoading, + setSnackbar + }) + } + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} + handleInput(e, mondayComConf, setMondayComConf)} + name="name" + value={mondayComConf.name} + type="text" + placeholder={__('Integration Name...', 'bit-integrations')} + /> +
+
+ + + + + +
+
+ ) +} + +export default EditMondayCom diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx new file mode 100644 index 000000000..2f8c10c04 --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx @@ -0,0 +1,142 @@ +/* eslint-disable no-console */ +/* eslint-disable no-unused-expressions */ +import { useState } from 'react' +import toast from 'react-hot-toast' +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useNavigate } from 'react-router' +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 MondayComAuthorization from './MondayComAuthorization' +import { checkMappedFields, generateMappedField } from './MondayComCommonFunc' +import MondayComIntegLayout from './MondayComIntegLayout' +import { actionLists, needsBoard, needsItem } from './staticData' + +function MondayCom({ formFields, setFlow, flow, allIntegURL }) { + const navigate = useNavigate() + const [isLoading, setIsLoading] = useState(false) + const [loading, setLoading] = useState({}) + + const [step, setStep] = useState(1) + const [snack, setSnackbar] = useState({ show: false }) + + const mondayComFields = [] + const [mondayComConf, setMondayComConf] = useState({ + name: 'Monday.Com', + type: 'Monday.Com', + apiToken: '', + field_map: generateMappedField(mondayComFields), + mainAction: '', + mondayComFields, + actionLists + }) + + const saveConfig = () => { + setIsLoading(true) + const resp = saveIntegConfig( + flow, + setFlow, + allIntegURL, + mondayComConf, + navigate, + '', + '', + setIsLoading + ) + resp.then(res => { + if (res.success) { + toast.success(res.data?.msg) + navigate(allIntegURL) + } else { + toast.error(res.data || res) + } + }) + } + + const nextPage = pageNo => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + + if (!checkMappedFields(mondayComConf)) { + toast.error(__('Please map mandatory fields', 'bit-integrations')) + return + } + + const action = mondayComConf.mainAction + if (needsBoard.includes(action) && !mondayComConf.selectedBoard) { + toast.error(__('Please select a board', 'bit-integrations')) + return + } + if (needsItem.includes(action) && !mondayComConf.selectedItem) { + toast.error(__('Please select an item', 'bit-integrations')) + return + } + if (action === 'create_column' && !mondayComConf.columnType) { + toast.error(__('Please select a column type', 'bit-integrations')) + return + } + setStep(pageNo) + } + + return ( +
+ +
+ +
+ + {/* STEP 1 */} + + + {/* STEP 2 */} +
+ + + {mondayComConf?.mainAction && ( + + )} +
+ + {/* STEP 3 */} + {mondayComConf?.mainAction && ( + saveConfig()} + isLoading={isLoading} + dataConf={mondayComConf} + setDataConf={setMondayComConf} + formFields={formFields} + /> + )} +
+ ) +} + +export default MondayCom diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayComActions.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayComActions.jsx new file mode 100644 index 000000000..238bc2538 --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComActions.jsx @@ -0,0 +1,33 @@ +import { __ } from '../../../Utils/i18nwrap' +import TableCheckBox from '../../Utilities/TableCheckBox' + +export default function MondayComActions({ mondayComConf, setMondayComConf }) { + const actionHandler = (val, typ) => { + const newConf = { ...mondayComConf } + + if (typ === 'addToTop') { + if (val.target.checked) { + newConf.addToTop = true + } else { + delete newConf.addToTop + } + } + + setMondayComConf({ ...newConf }) + } + + return ( +
+
+ actionHandler(e, 'addToTop')} + checked={!!mondayComConf?.addToTop} + className="wdt-200 mt-4 mr-2" + value="addToTop" + title={__('Add To Top', 'bit-integrations')} + subTitle={__('Duplicate selected group at the top of the board', 'bit-integrations')} + /> +
+
+ ) +} diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx new file mode 100644 index 000000000..8fe6f8e3b --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx @@ -0,0 +1,126 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useState } from 'react' +import { __ } from '../../../Utils/i18nwrap' +import LoaderSm from '../../Loaders/LoaderSm' +import Note from '../../Utilities/Note' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import TutorialLink from '../../Utilities/TutorialLink' +import { mondayComAuthentication } from './MondayComCommonFunc' +import { create } from 'mutative' + +export default function MondayComAuthorization({ + mondayComConf, + setMondayComConf, + step, + setStep, + loading, + setLoading, + isInfo +}) { + const [isAuthorized, setIsAuthorized] = useState(false) + const [error, setError] = useState({ apiToken: '' }) + + const nextPage = () => { + setTimeout(() => { + document.getElementById('btcd-settings-wrp').scrollTop = 0 + }, 300) + setStep(2) + } + + const handleInput = e => { + const { name, value } = e.target + setError(err => + create(err, draft => { + draft[name] = '' + }) + ) + setMondayComConf(prev => + create(prev, draft => { + draft[name] = value + }) + ) + } + + const ActiveInstructions = ` +

${__('To Get Monday.com API Token', 'bit-integrations')}

+
    +
  • ${__('Log in to your Monday.com account.', 'bit-integrations')}
  • +
  • ${__('Click on your avatar in the bottom left corner.', 'bit-integrations')}
  • +
  • ${__('Select Developers → API Token.', 'bit-integrations')}
  • +
  • ${__('Copy your personal API token (v2).', 'bit-integrations')}
  • +
` + + return ( +
+ + +
+ {__('Integration Name:', 'bit-integrations')} +
+ + +
+ {__('API Token:', 'bit-integrations')} +
+ +
{error.apiToken}
+ + + {__('To Get API Token, Please Visit', 'bit-integrations')} +   + + {__('Monday.com Developers', 'bit-integrations')} + + +
+
+ + {!isInfo && ( +
+ +
+ +
+ )} + +
+ ) +} diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayComCommonFunc.js b/frontend/src/components/AllIntegrations/MondayCom/MondayComCommonFunc.js new file mode 100644 index 000000000..12fa97eb6 --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComCommonFunc.js @@ -0,0 +1,203 @@ +/* eslint-disable no-console */ +/* eslint-disable no-else-return */ +import toast from 'react-hot-toast' +import bitsFetch from '../../../Utils/bitsFetch' +import { __ } from '../../../Utils/i18nwrap' +import { create } from 'mutative' +import { staticFieldsMap, needsColumnMap } from './staticData' + +export const handleInput = (e, mondayComConf, setMondayComConf) => { + setMondayComConf(mondayComConf => + create(mondayComConf, draftConf => { + const { name, value } = e.target + if (value !== '') { + draftConf[name] = value + } else { + delete draftConf[name] + } + }) + ) +} + +export const generateMappedField = mondayComFields => { + const requiredFlds = mondayComFields?.filter(fld => fld.required === true) || [] + + if (requiredFlds.length > 0) { + return requiredFlds.map(field => ({ formField: '', mondayComField: field.key })) + } + + if (!mondayComFields || mondayComFields.length === 0) { + return [] + } + + return [{ formField: '', mondayComField: '' }] +} + +export const checkMappedFields = mondayComConf => { + const mappedFields = mondayComConf?.field_map + ? mondayComConf.field_map.filter( + mappedField => + !mappedField.formField || + !mappedField.mondayComField || + (mappedField.formField === 'custom' && !mappedField.customValue) + ) + : [] + if (mappedFields.length > 0) { + return false + } + return true +} + +export const mondayComAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { + if (!confTmp.apiToken) { + setError({ + apiToken: !confTmp.apiToken ? __("API Token can't be empty", 'bit-integrations') : '' + }) + return + } + + setError({}) + setLoading({ ...loading, auth: true }) + + const requestParams = { + apiToken: confTmp.apiToken + } + + bitsFetch(requestParams, 'mondayCom_authentication').then(result => { + if (result && result.success) { + setIsAuthorized(true) + setLoading({ ...loading, auth: false }) + toast.success(__('Authorized Successfully', 'bit-integrations')) + return + } + setLoading({ ...loading, auth: false }) + const message = typeof result?.data === 'string' ? result.data : result?.data?.message + const authErrorMessage = result?.message || result?.error || message + toast.error( + authErrorMessage + ? `${__('Authorization failed', 'bit-integrations')}: ${authErrorMessage}` + : __('Authorization failed', 'bit-integrations') + ) + }) +} + +export const getAllBoards = (confTmp, setConf, setLoading) => { + setLoading(prev => ({ ...prev, board: true })) + + const requestParams = { + apiToken: confTmp.apiToken + } + + bitsFetch(requestParams, 'mondayCom_fetch_boards').then(result => { + if (result && result.success) { + if (result.data) { + setConf(prevConf => + create(prevConf, draftConf => { + draftConf.boards = result.data + }) + ) + + setLoading(prev => ({ ...prev, board: false })) + toast.success(__('Boards fetched successfully', 'bit-integrations')) + return + } + setLoading(prev => ({ ...prev, board: false })) + toast.error(__('Boards Not Found!', 'bit-integrations')) + return + } + setLoading(prev => ({ ...prev, board: false })) + toast.error(__('Boards fetching failed', 'bit-integrations')) + }) +} + +export const getAllGroups = (confTmp, setConf, boardId, setLoading) => { + setLoading(prev => ({ ...prev, group: true })) + + const requestParams = { + apiToken: confTmp.apiToken, + boardId + } + + bitsFetch(requestParams, 'mondayCom_fetch_groups').then(result => { + if (result && result.success) { + if (result.data) { + setConf(prevConf => + create(prevConf, draftConf => { + draftConf.groups = result.data + }) + ) + + setLoading(prev => ({ ...prev, group: false })) + return + } + setLoading(prev => ({ ...prev, group: false })) + toast.error(__('Groups Not Found!', 'bit-integrations')) + return + } + setLoading(prev => ({ ...prev, group: false })) + toast.error(__('Groups fetching failed', 'bit-integrations')) + }) +} + +export const getAllColumns = (confTmp, setConf, boardId, setLoading) => { + setLoading(prev => ({ ...prev, column: true })) + + const requestParams = { + apiToken: confTmp.apiToken, + boardId + } + + bitsFetch(requestParams, 'mondayCom_fetch_columns').then(result => { + if (result && result.success) { + if (result.data) { + setConf(prevConf => + create(prevConf, draftConf => { + draftConf.columns = result.data + if (needsColumnMap.includes(draftConf.mainAction)) { + const base = staticFieldsMap[draftConf.mainAction] || [] + draftConf.mondayComFields = [...base, ...result.data] + draftConf.field_map = generateMappedField(draftConf.mondayComFields) + } + }) + ) + + setLoading(prev => ({ ...prev, column: false })) + return + } + setLoading(prev => ({ ...prev, column: false })) + toast.error(__('Columns Not Found!', 'bit-integrations')) + return + } + setLoading(prev => ({ ...prev, column: false })) + toast.error(__('Columns fetching failed', 'bit-integrations')) + }) +} + +export const getAllItems = (confTmp, setConf, boardId, setLoading) => { + setLoading(prev => ({ ...prev, item: true })) + + const requestParams = { + apiToken: confTmp.apiToken, + boardId + } + + bitsFetch(requestParams, 'mondayCom_fetch_items').then(result => { + if (result && result.success) { + if (result.data) { + setConf(prevConf => + create(prevConf, draftConf => { + draftConf.items = result.data + }) + ) + + setLoading(prev => ({ ...prev, item: false })) + return + } + setLoading(prev => ({ ...prev, item: false })) + toast.error(__('Items Not Found!', 'bit-integrations')) + return + } + setLoading(prev => ({ ...prev, item: false })) + toast.error(__('Items fetching failed', 'bit-integrations')) + }) +} diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayComFieldMap.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayComFieldMap.jsx new file mode 100644 index 000000000..1f7664352 --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComFieldMap.jsx @@ -0,0 +1,110 @@ +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { SmartTagField } from '../../../Utils/StaticData/SmartTagField' +import { __, sprintf } from '../../../Utils/i18nwrap' +import TagifyInput from '../../Utilities/TagifyInput' +import { addFieldMap, delFieldMap, handleFieldMapping } from '../IntegrationHelpers/FieldMapHelper' +import { handleCustomValue } from '../IntegrationHelpers/IntegrationHelpers' + +export default function MondayComFieldMap({ + i, + formFields, + field, + mondayComConf, + setMondayComConf +}) { + const { isPro } = useRecoilValue($appConfigState) + + const availableFields = mondayComConf?.mondayComFields || [] + const requiredFields = availableFields.filter(f => f.required === true) + const nonRequiredFields = availableFields.filter(f => f.required === false) + + return ( +
+
+
+ + + {field.formField === 'custom' && ( + handleCustomValue(e, i, mondayComConf, setMondayComConf)} + 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/src/components/AllIntegrations/MondayCom/MondayComIntegLayout.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayComIntegLayout.jsx new file mode 100644 index 000000000..3f9645125 --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComIntegLayout.jsx @@ -0,0 +1,325 @@ +/* eslint-disable no-unused-vars */ +import 'react-multiple-select-dropdown-lite/dist/index.css' +import { useRecoilValue } from 'recoil' +import { $appConfigState } from '../../../GlobalStates' +import { __ } from '../../../Utils/i18nwrap' +import Loader from '../../Loaders/Loader' +import { addFieldMap } from '../IntegrationHelpers/FieldMapHelper' +import MultiSelect from 'react-multiple-select-dropdown-lite' +import { + getAllBoards, + getAllGroups, + getAllColumns, + getAllItems, + generateMappedField +} from './MondayComCommonFunc' +import MondayComFieldMap from './MondayComFieldMap' +import { + staticFieldsMap, + columnTypeList, + needsBoard, + needsItem, + needsColumnMap +} from './staticData' +import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' +import { create } from 'mutative' +import MondayComActions from './MondayComActions' + +export default function MondayComIntegLayout({ + formFields, + mondayComConf, + setMondayComConf, + loading, + setLoading, + isLoading +}) { + const btcbi = useRecoilValue($appConfigState) + const { isPro } = btcbi + + const handleActionChange = (value, name) => { + const needBoard = needsBoard.includes(value) + setMondayComConf(prevConf => + create(prevConf, draftConf => { + if (value !== '') { + draftConf[name] = value + } else { + delete draftConf[name] + } + + delete draftConf.selectedBoard + delete draftConf.selectedGroup + delete draftConf.selectedItem + delete draftConf.columns + delete draftConf.groups + delete draftConf.items + delete draftConf.columnType + delete draftConf.addToTop + + const base = staticFieldsMap[value] || [] + draftConf.mondayComFields = base + draftConf.field_map = generateMappedField(base) + }) + ) + + if (needBoard) { + getAllBoards(mondayComConf, setMondayComConf, setLoading) + } + } + + const handleBoardChange = val => { + setMondayComConf(prevConf => + create(prevConf, draftConf => { + draftConf.selectedBoard = val + delete draftConf.selectedGroup + delete draftConf.selectedItem + delete draftConf.groups + delete draftConf.items + delete draftConf.columns + }) + ) + + if (!val) return + + const action = mondayComConf.mainAction + + getAllGroups(mondayComConf, setMondayComConf, val, setLoading) + + if (needsColumnMap.includes(action)) { + getAllColumns(mondayComConf, setMondayComConf, val, setLoading) + } + if (needsItem.includes(action)) { + getAllItems(mondayComConf, setMondayComConf, val, setLoading) + } + } + + const handleSelectChange = (val, name) => { + setMondayComConf(prevConf => + create(prevConf, draftConf => { + if (val === '' || val === null || val === undefined) { + delete draftConf[name] + } else { + draftConf[name] = val + } + }) + ) + } + + const mainAction = mondayComConf?.mainAction + const hasFieldMap = + mainAction + && (staticFieldsMap[mainAction]?.length > 0 || needsColumnMap.includes(mainAction)) + + return ( + <> +
+
+ {__('Select Action:', 'bit-integrations')} + handleActionChange(val, 'mainAction')} + options={mondayComConf?.actionLists?.map(actionType => ({ + label: checkIsPro(isPro, actionType.is_pro) + ? actionType.label + : getProLabel(actionType.label), + value: actionType.name, + disabled: !checkIsPro(isPro, actionType.is_pro) + }))} + singleSelect + closeOnSelect + /> +
+ + {mainAction && needsBoard.includes(mainAction) && !loading.board && ( + <> +
+
+ {__('Select Board:', 'bit-integrations')} + ({ label: b.name, value: `${b.id}` })) || [] + } + className="msl-wrp-options dropdown-custom-width" + defaultValue={mondayComConf?.selectedBoard} + onChange={handleBoardChange} + singleSelect + closeOnSelect + disabled={loading.board} + /> + +
+ + )} + + {mainAction + && !['create_column', 'delete_group', 'archive_group', 'duplicate_group', 'archive_item', 'delete_item', 'move_item_to_group'].includes(mainAction) + && mondayComConf?.selectedBoard + && !loading.group && ( + <> +
+
+ {__('Select Group:', 'bit-integrations')} + ({ label: g.name, value: `${g.id}` })) || [] + } + className="msl-wrp-options dropdown-custom-width" + defaultValue={mondayComConf?.selectedGroup} + onChange={val => handleSelectChange(val, 'selectedGroup')} + singleSelect + closeOnSelect + /> + +
+ + )} + + {mainAction + && needsItem.includes(mainAction) + && mondayComConf?.selectedBoard + && !loading.item && ( + <> +
+
+ {__('Select Item:', 'bit-integrations')} + ({ label: it.name, value: `${it.id}` })) || [] + } + className="msl-wrp-options dropdown-custom-width" + defaultValue={mondayComConf?.selectedItem} + onChange={val => handleSelectChange(val, 'selectedItem')} + singleSelect + closeOnSelect + /> + +
+ + )} + + {mainAction === 'create_column' && ( + <> +
+
+ {__('Column Type:', 'bit-integrations')} + handleSelectChange(val, 'columnType')} + singleSelect + closeOnSelect + /> +
+ + )} + + {(loading.board || loading.group || loading.column || loading.item) && ( + + )} + + {mainAction && hasFieldMap && !isLoading && ( +
+
+
+ {__('Field Map', 'bit-integrations')} +
+
+
+
+
+ {__('Form Fields', 'bit-integrations')} +
+
+ {__('Monday.com Fields', 'bit-integrations')} +
+
+ {mondayComConf?.field_map?.map((itm, i) => ( + + ))} + {needsColumnMap.includes(mainAction) && ( +
+ +
+ )} +
+
+ )} + + {mainAction === 'duplicate_group' && ( + <> +
+
+ {__('Utilities', 'bit-integrations')} +
+
+ + + )} + + ) +} diff --git a/frontend/src/components/AllIntegrations/MondayCom/staticData.js b/frontend/src/components/AllIntegrations/MondayCom/staticData.js new file mode 100644 index 000000000..96131d4ab --- /dev/null +++ b/frontend/src/components/AllIntegrations/MondayCom/staticData.js @@ -0,0 +1,83 @@ +import { __ } from '../../../Utils/i18nwrap' + +export const actionLists = [ + { name: 'create_item', label: __('Create Item', 'bit-integrations'), is_pro: true }, + { name: 'update_item', label: __('Update Item', 'bit-integrations'), is_pro: true }, + { name: 'create_subitem', label: __('Create Subitem', 'bit-integrations'), is_pro: true }, + { name: 'move_item_to_group', label: __('Move Item to Group', 'bit-integrations'), is_pro: true }, + { name: 'archive_item', label: __('Archive Item', 'bit-integrations'), is_pro: true }, + { name: 'delete_item', label: __('Delete Item', 'bit-integrations'), is_pro: true }, + { name: 'archive_board', label: __('Archive Board', 'bit-integrations'), is_pro: true }, + { name: 'create_group', label: __('Create Group', 'bit-integrations'), is_pro: true }, + { name: 'duplicate_group', label: __('Duplicate Group', 'bit-integrations'), is_pro: true }, + { name: 'archive_group', label: __('Archive Group', 'bit-integrations'), is_pro: true }, + { name: 'delete_group', label: __('Delete Group', 'bit-integrations'), is_pro: true }, + { name: 'create_column', label: __('Create Column', 'bit-integrations'), is_pro: true } +] + +export const staticFieldsMap = { + create_item: [{ label: __('Item Name', 'bit-integrations'), key: 'item_name', required: true }], + update_item: [{ label: __('Item ID', 'bit-integrations'), key: 'item_id', required: true }], + create_subitem: [ + { label: __('Subitem Name', 'bit-integrations'), key: 'subitem_name', required: true } + ], + move_item_to_group: [ + { label: __('Group ID', 'bit-integrations'), key: 'group_id', required: true }, + { label: __('Item ID', 'bit-integrations'), key: 'item_id', required: true } + ], + archive_item: [ + { label: __('Item ID', 'bit-integrations'), key: 'item_id', required: true } + ], + delete_item: [ + { label: __('Item ID', 'bit-integrations'), key: 'item_id', required: true } + ], + archive_board: [ + { label: __('Board ID', 'bit-integrations'), key: 'board_id', required: true } + ], + create_group: [{ label: __('Group Name', 'bit-integrations'), key: 'group_name', required: true }], + duplicate_group: [ + { label: __('Group ID', 'bit-integrations'), key: 'group_id', required: true }, + { label: __('Group Title', 'bit-integrations'), key: 'group_title', required: true } + ], + archive_group: [ + { label: __('Group ID', 'bit-integrations'), key: 'group_id', required: true } + ], + delete_group: [ + { label: __('Group ID', 'bit-integrations'), key: 'group_id', required: true } + ], + create_column: [{ label: __('Column Title', 'bit-integrations'), key: 'column_title', required: true }] +} + +export const columnTypeList = [ + { label: __('Text', 'bit-integrations'), value: 'text' }, + { label: __('Long Text', 'bit-integrations'), value: 'long_text' }, + { label: __('Number', 'bit-integrations'), value: 'numbers' }, + { label: __('Status', 'bit-integrations'), value: 'status' }, + { label: __('Date', 'bit-integrations'), value: 'date' }, + { label: __('Checkbox', 'bit-integrations'), value: 'checkbox' }, + { label: __('Email', 'bit-integrations'), value: 'email' }, + { label: __('Phone', 'bit-integrations'), value: 'phone' }, + { label: __('Link', 'bit-integrations'), value: 'link' }, + { label: __('Rating', 'bit-integrations'), value: 'rating' }, + { label: __('Tags', 'bit-integrations'), value: 'tag' }, + { label: __('People', 'bit-integrations'), value: 'people' }, + { label: __('Timeline', 'bit-integrations'), value: 'timeline' } +] + +export const needsBoard = [ + 'create_item', + 'update_item', + 'create_subitem', + 'move_item_to_group', + 'archive_item', + 'delete_item', + 'archive_group', + 'delete_group', + 'create_group', + 'duplicate_group', + 'create_column' +] + +export const needsItem = ['create_subitem'] + +export const needsColumnMap = ['create_item', 'update_item'] diff --git a/frontend/src/components/AllIntegrations/NewInteg.jsx b/frontend/src/components/AllIntegrations/NewInteg.jsx index 505955d35..59978c59c 100644 --- a/frontend/src/components/AllIntegrations/NewInteg.jsx +++ b/frontend/src/components/AllIntegrations/NewInteg.jsx @@ -164,6 +164,7 @@ const TheEventsCalendar = lazy(() => import('./TheEventsCalendar/TheEventsCalend const LMFWC = lazy(() => import('./LMFWC/LMFWC')) const Voxel = lazy(() => import('./Voxel/Voxel')) const SmartSuite = lazy(() => import('./SmartSuite/SmartSuite')) +const MondayCom = lazy(() => import('./MondayCom/MondayCom')) const Bento = lazy(() => import('./Bento/Bento')) const Line = lazy(() => import('./Line/Line')) const ACPT = lazy(() => import('./ACPT/ACPT')) @@ -1606,6 +1607,15 @@ export default function NewInteg({ allIntegURL }) { setFlow={setFlow} /> ) + case 'Monday.Com': + return ( + + ) case 'Bento': return (