From b38cb16db2d0594b47aba5c46b3e855d7feb41aa Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Sat, 2 May 2026 16:14:13 +0600 Subject: [PATCH 01/58] feat: Implement authorization types and connection management - Added AuthorizationType class to define various authorization methods. - Implemented BasicAuthorization for basic authentication handling. - Created BearerTokenAuthorization for bearer token management. - Developed OAuth2Authorization to handle OAuth2 flows, including token refresh. - Introduced ConnectionModel for database interactions related to connections. - Added Hash utility for encrypting and decrypting sensitive data. - Created ConnectionController to manage connection CRUD operations. - Developed frontend API utilities for connection management. - Implemented ConnectionAccountList and ConnectionAccountSelect components for displaying and managing connections. - Created Connections page for listing, searching, and managing connections. --- .../Actions/MailChimp/MailChimpController.php | 44 +- backend/Admin/Admin_Bar.php | 1 + .../AbstractBaseAuthorization.php | 212 +++++++ .../ApiKey/ApiKeyAuthorization.php | 46 ++ .../Authorization/AuthorizationFactory.php | 70 +++ backend/Authorization/AuthorizationType.php | 20 + .../Basic/BasicAuthorization.php | 26 + .../Bearer/BearerTokenAuthorization.php | 26 + .../OAuth2/OAuth2Authorization.php | 138 +++++ backend/Config.php | 2 +- backend/Core/Database/ConnectionModel.php | 14 + backend/Core/Database/DB.php | 18 + backend/Core/Util/Hash.php | 65 ++ backend/Routes/ajax.php | 9 + backend/controller/ConnectionController.php | 562 ++++++++++++++++++ frontend/src/App.jsx | 10 + frontend/src/Utils/connectionApi.js | 41 ++ .../AllIntegrations/MailChimp/MailChimp.jsx | 2 + .../MailChimp/MailChimpAuthorization.jsx | 272 ++++++--- .../MailChimp/MailChimpCommonFunc.js | 96 ++- .../Connections/ConnectionAccountList.jsx | 125 ++++ .../Connections/ConnectionAccountSelect.jsx | 142 +++++ frontend/src/pages/Connections.jsx | 280 +++++++++ 23 files changed, 2131 insertions(+), 90 deletions(-) create mode 100644 backend/Authorization/AbstractBaseAuthorization.php create mode 100644 backend/Authorization/ApiKey/ApiKeyAuthorization.php create mode 100644 backend/Authorization/AuthorizationFactory.php create mode 100644 backend/Authorization/AuthorizationType.php create mode 100644 backend/Authorization/Basic/BasicAuthorization.php create mode 100644 backend/Authorization/Bearer/BearerTokenAuthorization.php create mode 100644 backend/Authorization/OAuth2/OAuth2Authorization.php create mode 100644 backend/Core/Database/ConnectionModel.php create mode 100644 backend/Core/Util/Hash.php create mode 100644 backend/controller/ConnectionController.php create mode 100644 frontend/src/Utils/connectionApi.js create mode 100644 frontend/src/components/Connections/ConnectionAccountList.jsx create mode 100644 frontend/src/components/Connections/ConnectionAccountSelect.jsx create mode 100644 frontend/src/pages/Connections.jsx diff --git a/backend/Actions/MailChimp/MailChimpController.php b/backend/Actions/MailChimp/MailChimpController.php index 467d0a4fc..39e1a8445 100644 --- a/backend/Actions/MailChimp/MailChimpController.php +++ b/backend/Actions/MailChimp/MailChimpController.php @@ -6,6 +6,8 @@ namespace BitApps\Integrations\Actions\MailChimp; +use BitApps\Integrations\Authorization\AuthorizationFactory; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -106,6 +108,9 @@ public static function generateTokens($requestsParams) $metaData = HttpHelper::post($metaDataEndPoint, null, $authorizationHeader); $apiResponse->dc = $metaData->dc; + $apiResponse->accountname = $metaData->accountname ?? null; + $apiResponse->login_email = $metaData->login->email ?? null; + $apiResponse->login_name = $metaData->login->login_name ?? null; if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { wp_send_json_error( @@ -272,7 +277,11 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; - $tokenDetails = $integrationDetails->tokenDetails; + $tokenDetails = self::resolveTokenDetails($integrationDetails); + if (is_wp_error($tokenDetails)) { + return $tokenDetails; + } + $listId = $integrationDetails->listId; $module = isset($integrationDetails->module) ? $integrationDetails->module : ''; $tags = $integrationDetails->tags; @@ -308,4 +317,37 @@ public function execute($integrationData, $fieldValues) return $mChimpApiResponse; } + + /** + * Resolve MailChimp tokenDetails for execution. + * + * Prefers the new Connection store when `connection_id` is present in flow_details; + * falls back to legacy inline tokenDetails for older flows. + */ + private static function resolveTokenDetails($integrationDetails) + { + $connectionId = isset($integrationDetails->connection_id) ? (int) $integrationDetails->connection_id : 0; + + if ($connectionId > 0) { + try { + $handler = AuthorizationFactory::getAuthorizationHandler(AuthorizationType::OAUTH2, $connectionId, 'MailChimp'); + $authDetails = $handler->getAuthDetails(); + } catch (\Exception $e) { + return new WP_Error('CONN_AUTH_FAIL', $e->getMessage()); + } + + if (isset($authDetails['error']) && $authDetails['error']) { + return new WP_Error('CONN_AUTH_FAIL', $authDetails['message'] ?? 'Connection authorization failed'); + } + + return (object) [ + 'access_token' => $authDetails['access_token'] ?? '', + 'dc' => $authDetails['dc'] ?? ($authDetails['userInfo']['dc'] ?? ''), + 'expires_in' => $authDetails['expires_in'] ?? null, + 'generated_at' => $authDetails['generated_at'] ?? null, + ]; + } + + return $integrationDetails->tokenDetails ?? null; + } } diff --git a/backend/Admin/Admin_Bar.php b/backend/Admin/Admin_Bar.php index aa842ada0..d70bda6cc 100644 --- a/backend/Admin/Admin_Bar.php +++ b/backend/Admin/Admin_Bar.php @@ -43,6 +43,7 @@ public function AdminMenu() $submenu['bit-integrations'] = [ [__('All Integrations', 'bit-integrations'), $capability, 'admin.php?page=bit-integrations#/'], + [__('Connections', 'bit-integrations'), $capability, 'admin.php?page=bit-integrations#/connections'], [__('Doc & Support', 'bit-integrations'), $capability, 'admin.php?page=bit-integrations#/doc-support'], [__('Settings', 'bit-integrations'), $capability, 'admin.php?page=bit-integrations#/app-settings'], ]; diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php new file mode 100644 index 000000000..dded88b20 --- /dev/null +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -0,0 +1,212 @@ +connectionId = (int) $connectionId; + } + + abstract public function getAccessToken(); + + public function getConnectionId(): int + { + return (int) $this->connectionId; + } + + public function getConnection() + { + if ($this->connection === null) { + $this->connection = $this->loadConnection(); + } + + return $this->connection; + } + + public function getAuthDetails() + { + $connection = $this->getConnection(); + + if (!$connection) { + return null; + } + + $authDetails = $this->decodeAuthDetails($connection->auth_details ?? null); + + if (empty($authDetails)) { + return $authDetails; + } + + $encryptKeys = $this->parseEncryptKeys($connection->encrypt_keys ?? ''); + + if (empty($encryptKeys)) { + return $authDetails; + } + + foreach ($encryptKeys as $path) { + $value = self::getNestedValue($authDetails, $path); + + if (!is_string($value) || $value === '') { + continue; + } + + self::setNestedValue($authDetails, $path, Hash::decrypt($value)); + } + + return $authDetails; + } + + public function isTokenExpired($generatedAt, $expiresIn): bool + { + if (empty($generatedAt) || empty($expiresIn) || (int) $expiresIn <= 0) { + return false; + } + + return time() > ((int) $generatedAt + (int) $expiresIn - 30); + } + + public function updateAuthDetails(array $authDetails): bool + { + $connection = $this->getConnection(); + + if (!$connection) { + return false; + } + + $encryptKeys = $this->parseEncryptKeys($connection->encrypt_keys ?? ''); + + foreach ($encryptKeys as $path) { + $value = self::getNestedValue($authDetails, $path); + + if (!is_string($value) || $value === '') { + continue; + } + + self::setNestedValue($authDetails, $path, Hash::encrypt($value)); + } + + $connectionModel = new ConnectionModel(); + $result = $connectionModel->update( + [ + 'auth_details' => wp_json_encode($authDetails), + 'updated_at' => current_time('mysql'), + ], + ['id' => $this->connectionId] + ); + + if (is_wp_error($result) && $result->get_error_code() !== 'result_empty') { + return false; + } + + $this->connection = null; + + return true; + } + + protected function decodeAuthDetails($value): array + { + if (is_array($value)) { + return $value; + } + + if (is_object($value)) { + return json_decode(wp_json_encode($value), true) ?: []; + } + + if (is_string($value) && $value !== '') { + $decoded = json_decode($value, true); + + return is_array($decoded) ? $decoded : []; + } + + return []; + } + + protected function parseEncryptKeys($value): array + { + if (is_array($value)) { + return array_values(array_filter(array_map('strval', $value))); + } + + if (is_string($value) && $value !== '') { + return array_values(array_filter(array_map('trim', explode(',', $value)))); + } + + return []; + } + + protected function http() + { + return new HttpHelper(); + } + + protected static function getNestedValue(array $data, string $path) + { + if ($path === '') { + return null; + } + + $segments = explode('.', $path); + $cursor = $data; + + foreach ($segments as $segment) { + if (!is_array($cursor) || !array_key_exists($segment, $cursor)) { + return null; + } + + $cursor = $cursor[$segment]; + } + + return $cursor; + } + + protected static function setNestedValue(array &$data, string $path, $value): void + { + if ($path === '') { + return; + } + + $segments = explode('.', $path); + $last = array_pop($segments); + $cursor = &$data; + + foreach ($segments as $segment) { + if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) { + $cursor[$segment] = []; + } + + $cursor = &$cursor[$segment]; + } + + $cursor[$last] = $value; + } + + private function loadConnection() + { + $connectionModel = new ConnectionModel(); + $result = $connectionModel->get( + ['id', 'app_slug', 'auth_type', 'connection_name', 'account_name', 'encrypt_keys', 'auth_details', 'status'], + ['id' => $this->connectionId], + 1 + ); + + if (is_wp_error($result) || empty($result[0])) { + return null; + } + + return $result[0]; + } +} diff --git a/backend/Authorization/ApiKey/ApiKeyAuthorization.php b/backend/Authorization/ApiKey/ApiKeyAuthorization.php new file mode 100644 index 000000000..7ff901d61 --- /dev/null +++ b/backend/Authorization/ApiKey/ApiKeyAuthorization.php @@ -0,0 +1,46 @@ +getAuthDetails(); + + if (empty($authDetails) || !isset($authDetails['value']) || $authDetails['value'] === '') { + return [ + 'error' => true, + 'message' => 'token field is missing', + ]; + } + + return $authDetails['value']; + } + + public function setAuthHeadersOrParams() + { + $authDetails = $this->getAuthDetails(); + + if (empty($authDetails) || !isset($authDetails['value'])) { + return [ + 'error' => true, + 'message' => 'token field is missing', + ]; + } + + $key = $authDetails['key'] ?? 'api_key'; + $location = $authDetails['addTo'] ?? 'header'; + + return [ + 'authLocation' => $location, + 'data' => [$key => $authDetails['value']], + ]; + } +} diff --git a/backend/Authorization/AuthorizationFactory.php b/backend/Authorization/AuthorizationFactory.php new file mode 100644 index 000000000..420426253 --- /dev/null +++ b/backend/Authorization/AuthorizationFactory.php @@ -0,0 +1,70 @@ +getAuthDetails(); + + if (empty($authDetails) || empty($authDetails['username']) || !isset($authDetails['password'])) { + return [ + 'error' => true, + 'message' => 'username or password field is missing', + ]; + } + + return 'Basic ' . base64_encode($authDetails['username'] . ':' . $authDetails['password']); + } +} diff --git a/backend/Authorization/Bearer/BearerTokenAuthorization.php b/backend/Authorization/Bearer/BearerTokenAuthorization.php new file mode 100644 index 000000000..7f4e6aa54 --- /dev/null +++ b/backend/Authorization/Bearer/BearerTokenAuthorization.php @@ -0,0 +1,26 @@ +getAuthDetails(); + + if (empty($authDetails) || empty($authDetails['token'])) { + return [ + 'error' => true, + 'message' => 'access token field is missing', + ]; + } + + return 'Bearer ' . $authDetails['token']; + } +} diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php new file mode 100644 index 000000000..4b960bfc6 --- /dev/null +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -0,0 +1,138 @@ +bodyParams = $bodyParams; + + return $this; + } + + public function setRefreshTokenUrl($refreshTokenUrl) + { + $this->refreshTokenUrl = $refreshTokenUrl; + + return $this; + } + + public function setTokenPrefix($prefix) + { + $this->tokenPrefix = $prefix === null ? '' : (string) $prefix; + + return $this; + } + + public function getAuthDetails() + { + $authDetails = parent::getAuthDetails(); + + if (empty($authDetails)) { + return [ + 'error' => true, + 'message' => 'Connection auth details are missing', + ]; + } + + $generatedAt = $authDetails['generated_at'] ?? null; + $expiresIn = $authDetails['expires_in'] ?? null; + + if ($this->isTokenExpired($generatedAt, $expiresIn)) { + return $this->refreshAccessToken($authDetails); + } + + return $authDetails; + } + + public function getAccessToken() + { + $authDetails = $this->getAuthDetails(); + + if (isset($authDetails['error']) && $authDetails['error']) { + return $authDetails; + } + + if (empty($authDetails['access_token'])) { + return [ + 'error' => true, + 'message' => 'Access token is missing', + ]; + } + + return $this->tokenPrefix . $authDetails['access_token']; + } + + public function refreshAccessToken(array $authDetails) + { + $url = $this->refreshTokenUrl ?: ($authDetails['refresh_token_url'] ?? ($authDetails['refreshTokenUrl'] ?? '')); + + if (empty($url)) { + return [ + 'error' => true, + 'message' => 'Refresh token endpoint is missing', + ]; + } + + $body = $this->bodyParams ?: $this->buildRefreshBody($authDetails); + + $response = HttpHelper::post($url, $body, ['Content-Type' => 'application/x-www-form-urlencoded']); + + if (HttpHelper::$responseCode !== 200 || (is_object($response) && isset($response->error))) { + return [ + 'error' => true, + 'message' => is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed', + 'response' => $response, + ]; + } + + $response = is_object($response) ? json_decode(wp_json_encode($response), true) : (array) $response; + + $authDetails['access_token'] = $response['access_token'] ?? ($authDetails['access_token'] ?? ''); + + if (!empty($response['refresh_token'])) { + $authDetails['refresh_token'] = $response['refresh_token']; + } + + if (isset($response['expires_in'])) { + $authDetails['expires_in'] = (int) $response['expires_in']; + } + + $authDetails['generated_at'] = time(); + + $this->updateAuthDetails($authDetails); + + return $authDetails; + } + + private function buildRefreshBody(array $authDetails): array + { + $grantType = $authDetails['grant_type'] ?? 'authorization_code'; + + $body = [ + 'grant_type' => $grantType === 'client_credentials' ? 'client_credentials' : 'refresh_token', + 'client_id' => $authDetails['client_id'] ?? ($authDetails['clientId'] ?? ''), + 'client_secret' => $authDetails['client_secret'] ?? ($authDetails['clientSecret'] ?? ''), + ]; + + if (!empty($authDetails['refresh_token'])) { + $body['refresh_token'] = $authDetails['refresh_token']; + } + + return $body; + } +} diff --git a/backend/Config.php b/backend/Config.php index 65886814c..b9f2a7548 100644 --- a/backend/Config.php +++ b/backend/Config.php @@ -24,7 +24,7 @@ class Config public const VERSION = '2.8.2'; - public const DB_VERSION = '1.1'; + public const DB_VERSION = '1.3'; public const REQUIRED_PHP_VERSION = '7.4'; diff --git a/backend/Core/Database/ConnectionModel.php b/backend/Core/Database/ConnectionModel.php new file mode 100644 index 000000000..8aed36bff --- /dev/null +++ b/backend/Core/Database/ConnectionModel.php @@ -0,0 +1,14 @@ +prefix}btcbi_connections` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `app_slug` varchar(191) NOT NULL, + `auth_type` varchar(50) NOT NULL DEFAULT 'oauth2', + `connection_name` varchar(255) DEFAULT NULL, + `account_name` varchar(255) DEFAULT NULL, + `encrypt_keys` text DEFAULT NULL, + `auth_details` longtext DEFAULT NULL, + `status` tinyint(1) DEFAULT 1, + `user_id` bigint(20) unsigned DEFAULT NULL, + `created_at` datetime DEFAULT NULL, + `updated_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `app_slug` (`app_slug`), + KEY `account_name` (`account_name`), + KEY `status` (`status`) ) {$collate};" ]; diff --git a/backend/Core/Util/Hash.php b/backend/Core/Util/Hash.php new file mode 100644 index 000000000..626635dba --- /dev/null +++ b/backend/Core/Util/Hash.php @@ -0,0 +1,65 @@ +guardRead(); + + $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); + $condition = ['status' => ConnectionModel::STATUS_VERIFIED]; + + if ($appSlug !== '') { + $condition['app_slug'] = $appSlug; + } + + $rows = (new ConnectionModel())->get(self::COLUMNS, $condition, null, null, 'id', 'DESC'); + + if (is_wp_error($rows)) { + wp_send_json_success(['data' => []]); + } + + $payload = []; + + foreach ($rows as $row) { + $payload[] = $this->formatRow($row); + } + + wp_send_json_success(['data' => $payload]); + } + + public function getById($request) + { + $this->guardRead(); + + $id = $this->normalizeId($request); + + if ($id === 0) { + wp_send_json_error(__('Connection id is required', 'bit-integrations')); + } + + $row = $this->findById($id); + + if (is_wp_error($row)) { + wp_send_json_error($row->get_error_message()); + } + + wp_send_json_success(['data' => $this->formatRow($row)]); + } + + public function save($request) + { + $this->guardWrite(); + + $payload = $this->buildPayload($request, false); + + if (is_wp_error($payload)) { + wp_send_json_error($payload->get_error_message()); + } + + $existingId = $this->findExistingIdForAccount($payload['app_slug'], $payload['account_name']); + + if ($existingId > 0) { + $payload['id'] = $existingId; + + $updated = $this->persist($payload, $existingId); + + if (is_wp_error($updated)) { + wp_send_json_error($updated->get_error_message()); + } + + wp_send_json_success(['data' => $this->formatRow($updated)]); + } + + $created = $this->persist($payload, 0); + + if (is_wp_error($created)) { + wp_send_json_error($created->get_error_message()); + } + + wp_send_json_success(['data' => $this->formatRow($created)]); + } + + public function update($request) + { + $this->guardWrite(); + + $id = $this->normalizeId($request); + + if ($id === 0) { + wp_send_json_error(__('Connection id is required', 'bit-integrations')); + } + + $name = $this->sanitizeScalar($request->connection_name ?? ''); + + if ($name === '') { + wp_send_json_error(__('Connection name is required', 'bit-integrations')); + } + + $existing = $this->findById($id); + + if (is_wp_error($existing)) { + wp_send_json_error($existing->get_error_message()); + } + + $update = [ + 'connection_name' => $name, + 'updated_at' => current_time('mysql'), + ]; + + if (isset($request->status)) { + $update['status'] = absint($request->status); + } + + $result = (new ConnectionModel())->update($update, ['id' => $id]); + + if (is_wp_error($result) && $result->get_error_code() !== 'result_empty') { + wp_send_json_error($result->get_error_message()); + } + + $row = $this->findById($id); + + if (is_wp_error($row)) { + wp_send_json_error($row->get_error_message()); + } + + wp_send_json_success(['data' => $this->formatRow($row)]); + } + + public function reauthorize($request) + { + $this->guardWrite(); + + $id = $this->normalizeId($request); + + if ($id === 0) { + wp_send_json_error(__('Connection id is required', 'bit-integrations')); + } + + if (empty($request->auth_details)) { + wp_send_json_error(__('Authorization details are required', 'bit-integrations')); + } + + $existing = $this->findById($id); + + if (is_wp_error($existing)) { + wp_send_json_error($existing->get_error_message()); + } + + $payload = [ + 'app_slug' => $existing->app_slug, + 'auth_type' => $existing->auth_type, + 'connection_name' => $existing->connection_name, + 'account_name' => $existing->account_name, + 'status' => ConnectionModel::STATUS_VERIFIED, + 'auth_details' => $this->normalizeArray($request->auth_details), + 'encrypt_keys' => $this->resolveEncryptKeys($request), + ]; + + if (!empty($request->account_name)) { + $payload['account_name'] = $this->sanitizeScalar($request->account_name); + } + + if (!empty($request->connection_name)) { + $payload['connection_name'] = $this->sanitizeScalar($request->connection_name); + } + + $row = $this->persist($payload, $id); + + if (is_wp_error($row)) { + wp_send_json_error($row->get_error_message()); + } + + wp_send_json_success(['data' => $this->formatRow($row)]); + } + + public function delete($request) + { + $this->guardWrite(); + + $id = $this->normalizeId($request); + + if ($id === 0) { + wp_send_json_error(__('Connection id is required', 'bit-integrations')); + } + + $result = (new ConnectionModel())->delete(['id' => $id]); + + if (is_wp_error($result)) { + wp_send_json_error($result->get_error_message()); + } + + wp_send_json_success(['id' => $id]); + } + + private function buildPayload($request, bool $isUpdate) + { + $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); + $authType = $this->sanitizeScalar($request->auth_type ?? ''); + + if (!$isUpdate && $appSlug === '') { + return new WP_Error('missing_app_slug', __('App slug is required', 'bit-integrations')); + } + + if ($authType !== '' && !in_array($authType, self::ALLOWED_AUTH_TYPES, true)) { + return new WP_Error('invalid_auth_type', __('Invalid auth type', 'bit-integrations')); + } + + if (empty($request->auth_details)) { + return new WP_Error('missing_auth_details', __('Authorization details are required', 'bit-integrations')); + } + + $authDetails = $this->normalizeArray($request->auth_details); + + if (empty($authDetails)) { + return new WP_Error('missing_auth_details', __('Authorization details are required', 'bit-integrations')); + } + + $accountName = $this->sanitizeScalar($request->account_name ?? ''); + $connectionName = $this->sanitizeScalar($request->connection_name ?? ''); + + if ($connectionName === '') { + $connectionName = $accountName !== '' ? $accountName : $appSlug; + } + + return [ + 'app_slug' => $appSlug, + 'auth_type' => $authType !== '' ? $authType : AuthorizationType::OAUTH2, + 'connection_name' => $connectionName, + 'account_name' => $accountName, + 'auth_details' => $authDetails, + 'encrypt_keys' => $this->resolveEncryptKeys($request), + 'status' => isset($request->status) ? absint($request->status) : ConnectionModel::STATUS_VERIFIED, + ]; + } + + private function persist(array $payload, int $existingId) + { + $authDetails = $payload['auth_details']; + $encryptKeys = $payload['encrypt_keys']; + + if (!isset($authDetails['generated_at'])) { + $authDetails['generated_at'] = time(); + } + + $authDetails = $this->encryptValues($authDetails, $encryptKeys); + + $now = current_time('mysql'); + + $row = [ + 'app_slug' => $payload['app_slug'], + 'auth_type' => $payload['auth_type'], + 'connection_name' => $payload['connection_name'], + 'account_name' => $payload['account_name'], + 'encrypt_keys' => implode(',', $encryptKeys), + 'auth_details' => wp_json_encode($authDetails), + 'status' => $payload['status'], + 'updated_at' => $now, + ]; + + $connectionModel = new ConnectionModel(); + + if ($existingId > 0) { + $update = $connectionModel->update($row, ['id' => $existingId]); + + if (is_wp_error($update) && $update->get_error_code() !== 'result_empty') { + return $update; + } + + return $this->findById($existingId); + } + + $row['user_id'] = get_current_user_id(); + $row['created_at'] = $now; + + $insertId = $connectionModel->insert($row); + + if (is_wp_error($insertId)) { + return $insertId; + } + + return $this->findById((int) $insertId); + } + + private function encryptValues(array $authDetails, array $encryptKeys): array + { + foreach ($encryptKeys as $path) { + $value = $this->getNestedValue($authDetails, $path); + + if (!is_string($value) || $value === '') { + continue; + } + + $this->setNestedValue($authDetails, $path, Hash::encrypt($value)); + } + + return $authDetails; + } + + private function decryptValues(array $authDetails, array $encryptKeys): array + { + foreach ($encryptKeys as $path) { + $value = $this->getNestedValue($authDetails, $path); + + if (!is_string($value) || $value === '') { + continue; + } + + $this->setNestedValue($authDetails, $path, Hash::decrypt($value)); + } + + return $authDetails; + } + + private function findById(int $id) + { + $rows = (new ConnectionModel())->get(self::COLUMNS, ['id' => $id], 1); + + if (is_wp_error($rows) || empty($rows[0])) { + return new WP_Error('connection_not_found', __('Connection not found', 'bit-integrations')); + } + + return $rows[0]; + } + + private function findExistingIdForAccount(string $appSlug, string $accountName): int + { + if ($appSlug === '' || $accountName === '') { + return 0; + } + + $rows = (new ConnectionModel())->get( + ['id'], + [ + 'app_slug' => $appSlug, + 'account_name' => $accountName, + 'status' => ConnectionModel::STATUS_VERIFIED, + ], + 1, + null, + 'id', + 'DESC' + ); + + if (is_wp_error($rows) || empty($rows[0])) { + return 0; + } + + return (int) $rows[0]->id; + } + + private function formatRow($row): array + { + $encryptKeys = $this->parseEncryptKeys($row->encrypt_keys ?? ''); + $authDetails = $this->normalizeArray($row->auth_details ?? null); + $authDetails = $this->decryptValues($authDetails, $encryptKeys); + + return [ + 'id' => (int) $row->id, + 'app_slug' => $row->app_slug, + 'auth_type' => $row->auth_type, + 'connection_name' => $row->connection_name, + 'account_name' => $row->account_name, + 'encrypt_keys' => $encryptKeys, + 'auth_details' => $authDetails, + 'status' => isset($row->status) ? (int) $row->status : ConnectionModel::STATUS_VERIFIED, + 'user_id' => isset($row->user_id) ? (int) $row->user_id : 0, + 'created_at' => $row->created_at ?? null, + 'updated_at' => $row->updated_at ?? null, + ]; + } + + private function resolveEncryptKeys($request): array + { + if (!isset($request->encrypt_keys)) { + return []; + } + + if (is_string($request->encrypt_keys)) { + return $this->parseEncryptKeys($request->encrypt_keys); + } + + if (is_array($request->encrypt_keys)) { + $keys = []; + foreach ($request->encrypt_keys as $key) { + $key = $this->sanitizeScalar($key); + + if ($key !== '') { + $keys[] = $key; + } + } + + return array_values(array_unique($keys)); + } + + return []; + } + + private function parseEncryptKeys($value): array + { + if (is_array($value)) { + $keys = array_filter(array_map([$this, 'sanitizeScalar'], $value)); + } elseif (is_string($value) && $value !== '') { + $keys = array_filter(array_map('trim', explode(',', $value))); + } else { + return []; + } + + return array_values(array_unique($keys)); + } + + private function normalizeArray($value): array + { + if (is_array($value)) { + return $value; + } + + if (is_object($value)) { + return json_decode(wp_json_encode($value), true) ?: []; + } + + if (is_string($value) && $value !== '') { + $decoded = json_decode($value, true); + + return is_array($decoded) ? $decoded : []; + } + + return []; + } + + private function getNestedValue(array $data, string $path) + { + if ($path === '') { + return null; + } + + $segments = explode('.', $path); + $cursor = $data; + + foreach ($segments as $segment) { + if (!is_array($cursor) || !array_key_exists($segment, $cursor)) { + return null; + } + + $cursor = $cursor[$segment]; + } + + return $cursor; + } + + private function setNestedValue(array &$data, string $path, $value): void + { + if ($path === '') { + return; + } + + $segments = explode('.', $path); + $last = array_pop($segments); + $cursor = &$data; + + foreach ($segments as $segment) { + if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) { + $cursor[$segment] = []; + } + + $cursor = &$cursor[$segment]; + } + + $cursor[$last] = $value; + } + + private function normalizeId($request): int + { + if (is_numeric($request)) { + return absint($request); + } + + if (is_object($request)) { + foreach (['id', 'connection_id', 'connectionId'] as $key) { + if (!empty($request->{$key})) { + return absint($request->{$key}); + } + } + } + + if (is_array($request)) { + foreach (['id', 'connection_id', 'connectionId'] as $key) { + if (!empty($request[$key])) { + return absint($request[$key]); + } + } + } + + return 0; + } + + private function sanitizeScalar($value): string + { + if (!is_scalar($value)) { + return ''; + } + + return sanitize_text_field((string) $value); + } + + private function guardRead(): void + { + if ( + !Capabilities::Check('manage_options') + && !Capabilities::Check('bit_integrations_manage_integrations') + && !Capabilities::Check('bit_integrations_create_integrations') + && !Capabilities::Check('bit_integrations_edit_integrations') + ) { + wp_send_json_error(__('You do not have permission to access connections', 'bit-integrations')); + } + } + + private function guardWrite(): void + { + if ( + !Capabilities::Check('manage_options') + && !Capabilities::Check('bit_integrations_manage_integrations') + && !Capabilities::Check('bit_integrations_create_integrations') + && !Capabilities::Check('bit_integrations_edit_integrations') + ) { + wp_send_json_error(__('You do not have permission to manage connections', 'bit-integrations')); + } + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7dbe0c986..26fe00616 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -30,6 +30,7 @@ const Integrations = lazy(() => import('./components/Integrations')) const Settings = lazy(() => import('./pages/Settings')) const DocSupport = lazy(() => import('./pages/DocSupport')) const FlowBuilder = lazy(() => import('./pages/FlowBuilder')) +const Connections = lazy(() => import('./pages/Connections')) const Error404 = lazy(() => import('./pages/Error404')) const integrationsElement = getIntegrationsElement(Integrations) @@ -115,6 +116,15 @@ function App() { } /> + + + + } + /> + + bitsFetch(null, 'connections/list', { app_slug: appSlug }, 'GET') + +export const getConnection = id => bitsFetch(null, 'connections/get', { id }, 'GET') + +export const saveConnection = payload => bitsFetch(payload, 'connections/save') + +export const updateConnection = payload => bitsFetch(payload, 'connections/update') + +export const reauthorizeConnection = payload => bitsFetch(payload, 'connections/reauthorize') + +export const deleteConnection = id => bitsFetch({ id }, 'connections/delete') + +/** + * Persist auth payload as a reusable connection. + * + * @returns {Promise<{ success: boolean, data: object }>} + */ +export const persistConnection = ({ + appSlug, + authType = 'oauth2', + connectionName, + accountName, + authDetails, + encryptKeys = [] +}) => + saveConnection({ + app_slug: appSlug, + auth_type: authType, + connection_name: connectionName, + account_name: accountName, + auth_details: authDetails, + encrypt_keys: encryptKeys + }) diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx index 98f682c13..91d19318c 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx @@ -26,6 +26,8 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { const [mailChimpConf, setMailChimpConf] = useState({ name: 'Mail Chimp', type: 'Mail Chimp', + connection_id: null, + connectionName: '', clientId: '', clientSecret: '', listId: '', diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx index 401458bf3..40518fdcb 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx @@ -1,11 +1,19 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' +import { listConnections } from '../../../Utils/connectionApi' import LoaderSm from '../../Loaders/LoaderSm' import CopyText from '../../Utilities/CopyText' -import { handleMailChimpAuthorize, refreshAudience, refreshModules } from './MailChimpCommonFunc' +import { + applyConnectionToConf, + handleMailChimpAuthorize, + refreshModules +} from './MailChimpCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import TutorialLink from '../../Utilities/TutorialLink' +import ConnectionAccountSelect from '../../Connections/ConnectionAccountSelect' + +const APP_SLUG = 'MailChimp' export default function MailChimpAuthorization({ formID, @@ -20,8 +28,30 @@ export default function MailChimpAuthorization({ isInfo }) { const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) -const nextPage = () => { + const [connections, setConnections] = useState([]) + const [showNewForm, setShowNewForm] = useState(false) + const [error, setError] = useState({ connectionName: '', clientId: '', clientSecret: '' }) + + useEffect(() => { + setIsLoading(true) + listConnections(APP_SLUG) + .then(res => { + if (res?.success && Array.isArray(res.data?.data)) { + setConnections(res.data.data) + if (mailChimpConf.connection_id) { + const match = res.data.data.find(c => c.id === mailChimpConf.connection_id) + if (match) setisAuthorized(true) + } else if (res.data.data.length === 0) { + setShowNewForm(true) + } + } else { + setShowNewForm(true) + } + }) + .finally(() => setIsLoading(false)) + }, []) + + const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 }, 300) @@ -38,11 +68,74 @@ const nextPage = () => { setMailChimpConf(newConf) } + const handleSelectConnection = conn => { + if (!conn) { + setMailChimpConf(prev => ({ + ...prev, + connection_id: null, + tokenDetails: null, + clientId: '', + clientSecret: '', + connectionName: '' + })) + setisAuthorized(false) + setShowNewForm(false) + return + } + + applyConnectionToConf(conn, setMailChimpConf) + setisAuthorized(true) + setShowNewForm(false) + } + + const enterNewForm = () => { + setShowNewForm(true) + setisAuthorized(false) + setMailChimpConf(prev => ({ + ...prev, + connection_id: null, + clientId: '', + clientSecret: '', + tokenDetails: null, + connectionName: '' + })) + } + + const handleReauthorize = conn => { + setShowNewForm(true) + setMailChimpConf(prev => ({ + ...prev, + connection_id: conn.id, + connectionName: conn.connection_name || '', + clientId: conn.auth_details?.client_id || '', + clientSecret: conn.auth_details?.client_secret || '' + })) + setisAuthorized(false) + } + + const onAuthorizeClick = () => { + if (!mailChimpConf.connectionName?.trim()) { + setError({ ...error, connectionName: __("Connection name can't be empty", 'bit-integrations') }) + return + } + handleMailChimpAuthorize( + 'mailChimp', + 'mChimp', + mailChimpConf, + setMailChimpConf, + setError, + setisAuthorized, + setIsLoading, + setSnackbar, + setConnections + ) + } + return (
- +
{__('Integration Name:', 'bit-integrations')} @@ -57,79 +150,93 @@ const nextPage = () => { disabled={isInfo} /> -
- {__('Homepage URL:', 'bit-integrations')} -
- + {!isInfo && ( + + )} -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + {showNewForm && !isInfo && ( + <> +
+ {__('Connection Name:', 'bit-integrations')} +
+ +
{error.connectionName}
- - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Mail Chimp API Console', 'bit-integrations')} - - +
+ {__('Homepage URL:', 'bit-integrations')} +
+ -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+
+ {__('Authorized Redirect URIs:', 'bit-integrations')} +
+ + + + {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} + + {__('Mail Chimp API Console', 'bit-integrations')} + + + +
+ {__('Client id:', 'bit-integrations')} +
+ +
{error.clientId}
+ +
+ {__('Client secret:', 'bit-integrations')} +
+ +
{error.clientSecret}
-
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- {!isInfo && ( - <>
- )} + + {!isInfo && ( + + )}
) } diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js index be27fe405..deb188899 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js @@ -2,6 +2,33 @@ import bitsFetch from '../../../Utils/bitsFetch' import { deepCopy } from '../../../Utils/Helpers' import { __, sprintf } from '../../../Utils/i18nwrap' import { create } from 'mutative' +import { persistConnection, reauthorizeConnection } from '../../../Utils/connectionApi' + +const APP_SLUG = 'MailChimp' +// Flat auth_details shape — matches OAuth2Authorization::getAccessToken contract. +const ENCRYPT_KEYS = ['access_token', 'refresh_token', 'client_secret'] + +export const applyConnectionToConf = (connection, setMailChimpConf) => { + if (!connection) return + const auth = connection.auth_details || {} + + setMailChimpConf(prev => + create(prev, draft => { + draft.connection_id = connection.id + draft.clientId = auth.client_id || '' + draft.clientSecret = auth.client_secret || '' + draft.tokenDetails = { + access_token: auth.access_token, + dc: auth.dc, + expires_in: auth.expires_in, + generates_on: auth.generated_at, + accountname: auth.accountname, + login_email: auth.login_email, + login_name: auth.login_name + } + }) + ) +} export const handleInput = ( e, @@ -262,7 +289,8 @@ export const handleMailChimpAuthorize = ( setError, setisAuthorized, setIsLoading, - setSnackbar + setSnackbar, + setConnections ) => { if (!confTmp.clientId || !confTmp.clientSecret) { setError({ @@ -317,7 +345,8 @@ export const handleMailChimpAuthorize = ( setConf, setisAuthorized, setIsLoading, - setSnackbar + setSnackbar, + setConnections ) } } @@ -331,7 +360,8 @@ const tokenHelper = ( setConf, setisAuthorized, setIsLoading, - setSnackbar + setSnackbar, + setConnections ) => { const tokenRequestParams = { ...grantToken } tokenRequestParams.clientId = confTmp.clientId @@ -340,11 +370,63 @@ const tokenHelper = ( bitsFetch(tokenRequestParams, `${ajaxInteg}_generate_token`) .then(result => result) - .then(result => { + .then(async result => { if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) + const tokenDetails = result.data + const accountName = tokenDetails.login_email || tokenDetails.accountname || tokenDetails.dc + const connectionName = + confTmp.connectionName?.trim() || + (tokenDetails.accountname && tokenDetails.login_email + ? `${tokenDetails.accountname} (${tokenDetails.login_email})` + : accountName) + + const authDetails = { + access_token: tokenDetails.access_token, + refresh_token: tokenDetails.refresh_token || '', + dc: tokenDetails.dc, + expires_in: tokenDetails.expires_in || 0, + generated_at: tokenDetails.generates_on || Math.floor(Date.now() / 1000), + client_id: confTmp.clientId, + client_secret: confTmp.clientSecret, + accountname: tokenDetails.accountname, + login_email: tokenDetails.login_email, + login_name: tokenDetails.login_name + } + + const persist = confTmp.connection_id + ? reauthorizeConnection({ + id: confTmp.connection_id, + auth_details: authDetails, + encrypt_keys: ENCRYPT_KEYS, + account_name: accountName, + connection_name: connectionName + }) + : persistConnection({ + appSlug: APP_SLUG, + authType: 'oauth2', + connectionName, + accountName, + authDetails, + encryptKeys: ENCRYPT_KEYS + }) + + const connRes = await persist + const connection = connRes?.data?.data || null + + setConf(prev => + create(prev, draft => { + draft.tokenDetails = tokenDetails + if (connection?.id) draft.connection_id = connection.id + }) + ) + + if (connection && setConnections) { + setConnections(prev => { + const filtered = prev.filter(c => c.id !== connection.id) + return [connection, ...filtered] + }) + } + setisAuthorized(true) setSnackbar({ show: true, diff --git a/frontend/src/components/Connections/ConnectionAccountList.jsx b/frontend/src/components/Connections/ConnectionAccountList.jsx new file mode 100644 index 000000000..c0b429cbe --- /dev/null +++ b/frontend/src/components/Connections/ConnectionAccountList.jsx @@ -0,0 +1,125 @@ +import { useEffect, useRef, useState } from 'react' +import TrashIcn from '../../Icons/TrashIcn' +import { __ } from '../../Utils/i18nwrap' +import { deleteConnection } from '../../Utils/connectionApi' + +export default function ConnectionAccountList({ + connections, + setConnections, + selectedId, + onSelect, + onReauthorize, + setIsLoading, + setSnackbar, + isInfo +}) { + const [confirmId, setConfirmId] = useState(null) + const popoverRef = useRef(null) + + useEffect(() => { + const handleClickOutside = event => { + if (popoverRef.current && !popoverRef.current.contains(event.target)) { + setConfirmId(null) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) + + const handleDelete = id => { + if (setIsLoading) setIsLoading(true) + deleteConnection(id) + .then(res => { + if (res?.success) { + setConnections(prev => prev.filter(item => item.id !== id)) + if (selectedId === id) onSelect(null) + } else if (setSnackbar) { + setSnackbar({ show: true, msg: __('Failed to delete account', 'bit-integrations') }) + } + }) + .finally(() => { + if (setIsLoading) setIsLoading(false) + setConfirmId(null) + }) + } + + if (!connections?.length) return null + + return ( +
+
+ {connections.map(conn => { + const userInfo = conn.auth_details?.userInfo?.user || conn.auth_details?.userInfo || {} + const displayName = + userInfo.displayName || userInfo.name || conn.account_name || conn.connection_name + const email = userInfo.emailAddress || userInfo.email || conn.account_name + const photo = userInfo.photoLink || userInfo.picture || '' + + return ( +
+ + + {!isInfo && ( +
+ {onReauthorize && ( + + )} + {confirmId === conn.id ? ( +
+

{__('Are you sure?', 'bit-integrations')}

+ + +
+ ) : ( + + )} +
+ )} +
+ ) + })} +
+
+ ) +} diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx new file mode 100644 index 000000000..3b4b2c502 --- /dev/null +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -0,0 +1,142 @@ +import { useEffect, useRef, useState } from 'react' +import TrashIcn from '../../Icons/TrashIcn' +import { __ } from '../../Utils/i18nwrap' +import { deleteConnection } from '../../Utils/connectionApi' + +const NEW_VALUE = '__new__' + +export default function ConnectionAccountSelect({ + connections, + setConnections, + selectedId, + onSelect, + onAddNew, + onReauthorize, + setIsLoading, + setSnackbar, + isInfo, + label = __('Account:', 'bit-integrations'), + newOptionLabel = __('+ Add new connection', 'bit-integrations') +}) { + const [confirmOpen, setConfirmOpen] = useState(false) + const popoverRef = useRef(null) + + useEffect(() => { + const handleClickOutside = event => { + if (popoverRef.current && !popoverRef.current.contains(event.target)) { + setConfirmOpen(false) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) + + const handleChange = e => { + const value = e.target.value + + if (value === NEW_VALUE) { + if (onAddNew) onAddNew() + return + } + + if (!value) { + onSelect(null) + return + } + + const id = Number(value) + const conn = connections.find(c => c.id === id) + if (conn) onSelect(conn) + } + + const handleDelete = () => { + if (!selectedId) return + if (setIsLoading) setIsLoading(true) + deleteConnection(selectedId) + .then(res => { + if (res?.success) { + setConnections(prev => prev.filter(c => c.id !== selectedId)) + onSelect(null) + } else if (setSnackbar) { + setSnackbar({ show: true, msg: __('Failed to delete account', 'bit-integrations') }) + } + }) + .finally(() => { + if (setIsLoading) setIsLoading(false) + setConfirmOpen(false) + }) + } + + const dropdownValue = selectedId ? String(selectedId) : '' + + return ( +
+
+ {label} +
+
+ + + {selectedId && !isInfo && ( + <> + {onReauthorize && ( + + )} + +
+ {confirmOpen ? ( +
+

{__('Delete this account?', 'bit-integrations')}

+ + +
+ ) : ( + + )} +
+ + )} +
+
+ ) +} diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx new file mode 100644 index 000000000..8cbf47641 --- /dev/null +++ b/frontend/src/pages/Connections.jsx @@ -0,0 +1,280 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import toast from 'react-hot-toast' +import EditIcn from '../Icons/EditIcn' +import TrashIcn from '../Icons/TrashIcn' +import { + deleteConnection, + listConnections, + updateConnection +} from '../Utils/connectionApi' +import Loader from '../components/Loaders/Loader' +import ConfirmModal from '../components/Utilities/ConfirmModal' +import { __ } from '../Utils/i18nwrap' + +const loaderStyle = { + display: 'flex', + height: '60vh', + justifyContent: 'center', + alignItems: 'center' +} + +export default function Connections() { + const [connections, setConnections] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [filterApp, setFilterApp] = useState('') + const [search, setSearch] = useState('') + const [editingId, setEditingId] = useState(null) + const [editValue, setEditValue] = useState('') + const [deletingId, setDeletingId] = useState(null) + const [savingId, setSavingId] = useState(null) + const inputRef = useRef(null) + + const fetchConnections = () => { + setIsLoading(true) + listConnections('') + .then(res => { + if (res?.success && Array.isArray(res.data?.data)) { + setConnections(res.data.data) + } else { + setConnections([]) + } + }) + .catch(() => toast.error(__('Failed to load connections', 'bit-integrations'))) + .finally(() => setIsLoading(false)) + } + + useEffect(() => { + fetchConnections() + }, []) + + useEffect(() => { + if (editingId !== null && inputRef.current) inputRef.current.focus() + }, [editingId]) + + const apps = useMemo(() => { + const set = new Set(connections.map(c => c.app_slug).filter(Boolean)) + return Array.from(set).sort() + }, [connections]) + + const filtered = useMemo(() => { + const term = search.trim().toLowerCase() + return connections.filter(conn => { + if (filterApp && conn.app_slug !== filterApp) return false + if (!term) return true + return ( + (conn.connection_name || '').toLowerCase().includes(term) || + (conn.account_name || '').toLowerCase().includes(term) || + (conn.app_slug || '').toLowerCase().includes(term) + ) + }) + }, [connections, filterApp, search]) + + const startEdit = conn => { + setEditingId(conn.id) + setEditValue(conn.connection_name || '') + } + + const cancelEdit = () => { + setEditingId(null) + setEditValue('') + } + + const saveRename = id => { + const next = editValue.trim() + if (!next) { + toast.error(__('Connection name cannot be empty', 'bit-integrations')) + return + } + + const previous = connections.find(c => c.id === id) + if (previous && previous.connection_name === next) { + cancelEdit() + return + } + + setSavingId(id) + const promise = updateConnection({ id, connection_name: next }).then(res => { + if (!res?.success) throw new Error('rename_failed') + const row = res.data?.data + setConnections(prev => + prev.map(item => (item.id === id ? { ...item, ...(row || { connection_name: next }) } : item)) + ) + cancelEdit() + return __('Renamed', 'bit-integrations') + }) + + toast + .promise(promise, { + success: msg => msg, + error: __('Failed to rename', 'bit-integrations'), + loading: __('Saving...', 'bit-integrations') + }) + .finally(() => setSavingId(null)) + } + + const handleKeyDown = (e, id) => { + if (e.key === 'Enter') { + e.preventDefault() + saveRename(id) + } else if (e.key === 'Escape') { + e.preventDefault() + cancelEdit() + } + } + + const confirmDelete = () => { + const id = deletingId + if (!id) return + + const promise = deleteConnection(id).then(res => { + if (!res?.success) throw new Error('delete_failed') + setConnections(prev => prev.filter(c => c.id !== id)) + return __('Connection deleted', 'bit-integrations') + }) + + toast.promise(promise, { + success: msg => msg, + error: __('Failed to delete', 'bit-integrations'), + loading: __('Deleting...', 'bit-integrations') + }) + + setDeletingId(null) + } + + if (isLoading) return + + return ( +
+ setDeletingId(null)} + btnTxt={__('Delete', 'bit-integrations')} + btnClass="" + /> + +
+

{__('Connections', 'bit-integrations')}

+
+ +
+ setSearch(e.target.value)} + /> + + + + +
+ +
+ {filtered.length === 0 ? ( +

+ {connections.length === 0 + ? __('No connections saved yet. Authorize an app from any integration to add one.', 'bit-integrations') + : __('No connections match the current filters.', 'bit-integrations')} +

+ ) : ( + + + + + + + + + + + + + {filtered.map(conn => ( + + + + + + + + + ))} + +
{__('App', 'bit-integrations')}{__('Connection Name', 'bit-integrations')}{__('Account', 'bit-integrations')}{__('Auth Type', 'bit-integrations')}{__('Created', 'bit-integrations')}
{conn.app_slug} + {editingId === conn.id ? ( +
+ setEditValue(e.target.value)} + onKeyDown={e => handleKeyDown(e, conn.id)} + disabled={savingId === conn.id} + /> + + +
+ ) : ( +
+ {conn.connection_name || '—'} + +
+ )} +
{conn.account_name || '—'}{conn.auth_type}{conn.created_at || '—'} + +
+ )} +
+
+ ) +} From 411454fb9c5fc586b1139180a68e3d63f845b4e1 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Sat, 2 May 2026 17:33:42 +0600 Subject: [PATCH 02/58] feat: Enhance Connections page with improved styling and table functionality --- frontend/src/pages/Connections.jsx | 421 ++++++++++++++++------------ frontend/src/resource/sass/app.scss | 122 +++++++- 2 files changed, 369 insertions(+), 174 deletions(-) diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index 8cbf47641..2c56d0565 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -1,35 +1,27 @@ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import toast from 'react-hot-toast' import EditIcn from '../Icons/EditIcn' import TrashIcn from '../Icons/TrashIcn' +import Table from '../components/Utilities/Table' +import ConfirmModal from '../components/Utilities/ConfirmModal' import { deleteConnection, listConnections, updateConnection } from '../Utils/connectionApi' -import Loader from '../components/Loaders/Loader' -import ConfirmModal from '../components/Utilities/ConfirmModal' import { __ } from '../Utils/i18nwrap' -const loaderStyle = { - display: 'flex', - height: '60vh', - justifyContent: 'center', - alignItems: 'center' -} - export default function Connections() { const [connections, setConnections] = useState([]) const [isLoading, setIsLoading] = useState(true) const [filterApp, setFilterApp] = useState('') - const [search, setSearch] = useState('') const [editingId, setEditingId] = useState(null) - const [editValue, setEditValue] = useState('') const [deletingId, setDeletingId] = useState(null) const [savingId, setSavingId] = useState(null) const inputRef = useRef(null) + const editValueRef = useRef('') - const fetchConnections = () => { + const fetchConnections = useCallback(() => { setIsLoading(true) listConnections('') .then(res => { @@ -41,11 +33,11 @@ export default function Connections() { }) .catch(() => toast.error(__('Failed to load connections', 'bit-integrations'))) .finally(() => setIsLoading(false)) - } + }, []) useEffect(() => { fetchConnections() - }, []) + }, [fetchConnections]) useEffect(() => { if (editingId !== null && inputRef.current) inputRef.current.focus() @@ -56,73 +48,68 @@ export default function Connections() { return Array.from(set).sort() }, [connections]) - const filtered = useMemo(() => { - const term = search.trim().toLowerCase() - return connections.filter(conn => { - if (filterApp && conn.app_slug !== filterApp) return false - if (!term) return true - return ( - (conn.connection_name || '').toLowerCase().includes(term) || - (conn.account_name || '').toLowerCase().includes(term) || - (conn.app_slug || '').toLowerCase().includes(term) - ) - }) - }, [connections, filterApp, search]) + const filteredConnections = useMemo( + () => connections.filter(conn => !filterApp || conn.app_slug === filterApp), + [connections, filterApp] + ) - const startEdit = conn => { + const startEdit = useCallback(conn => { setEditingId(conn.id) - setEditValue(conn.connection_name || '') - } + editValueRef.current = conn.connection_name || '' + }, []) - const cancelEdit = () => { + const cancelEdit = useCallback(() => { setEditingId(null) - setEditValue('') - } - - const saveRename = id => { - const next = editValue.trim() - if (!next) { - toast.error(__('Connection name cannot be empty', 'bit-integrations')) - return - } + editValueRef.current = '' + }, []) - const previous = connections.find(c => c.id === id) - if (previous && previous.connection_name === next) { - cancelEdit() - return - } + const saveRename = useCallback( + (id, rawName = editValueRef.current) => { + const next = (rawName || '').trim() + if (!next) { + toast.error(__('Connection name cannot be empty', 'bit-integrations')) + return + } - setSavingId(id) - const promise = updateConnection({ id, connection_name: next }).then(res => { - if (!res?.success) throw new Error('rename_failed') - const row = res.data?.data - setConnections(prev => - prev.map(item => (item.id === id ? { ...item, ...(row || { connection_name: next }) } : item)) - ) - cancelEdit() - return __('Renamed', 'bit-integrations') - }) + const previous = connections.find(c => c.id === id) + if (previous && previous.connection_name === next) { + cancelEdit() + return + } - toast - .promise(promise, { - success: msg => msg, - error: __('Failed to rename', 'bit-integrations'), - loading: __('Saving...', 'bit-integrations') + setSavingId(id) + const promise = updateConnection({ id, connection_name: next }).then(res => { + if (!res?.success) throw new Error('rename_failed') + const row = res.data?.data + setConnections(prev => + prev.map(item => (item.id === id ? { ...item, ...(row || { connection_name: next }) } : item)) + ) + cancelEdit() + return __('Renamed', 'bit-integrations') }) - .finally(() => setSavingId(null)) - } + + toast + .promise(promise, { + success: msg => msg, + error: __('Failed to rename', 'bit-integrations'), + loading: __('Saving...', 'bit-integrations') + }) + .finally(() => setSavingId(null)) + }, + [connections, cancelEdit] + ) const handleKeyDown = (e, id) => { if (e.key === 'Enter') { e.preventDefault() - saveRename(id) + saveRename(id, e.currentTarget.value) } else if (e.key === 'Escape') { e.preventDefault() cancelEdit() } } - const confirmDelete = () => { + const confirmDelete = useCallback(() => { const id = deletingId if (!id) return @@ -139,12 +126,161 @@ export default function Connections() { }) setDeletingId(null) - } + }, [deletingId]) - if (isLoading) return + const setBulkDelete = useCallback(rows => { + const ids = [] + + if (Array.isArray(rows)) { + rows.forEach(row => { + if (row?.original?.id) { + ids.push(row.original.id) + } + }) + } else if (rows?.original?.id) { + ids.push(rows.original.id) + } + + if (ids.length < 1) { + return + } + + const promise = Promise.all( + ids.map(id => + deleteConnection(id).then(res => { + if (!res?.success) { + throw new Error('bulk_delete_failed') + } + + return id + }) + ) + ).then(() => { + setConnections(prev => prev.filter(item => !ids.includes(item.id))) + + return ids.length > 1 + ? __('Connections deleted', 'bit-integrations') + : __('Connection deleted', 'bit-integrations') + }) + + toast.promise(promise, { + success: msg => msg, + error: __('Failed to delete', 'bit-integrations'), + loading: __('Deleting...', 'bit-integrations') + }) + }, []) + + const columns = useMemo( + () => [ + { + Header: __('App', 'bit-integrations'), + accessor: 'app_slug', + width: 130, + minWidth: 90, + Cell: ({ value }) => {value || '—'} + }, + { + Header: __('Connection Name', 'bit-integrations'), + accessor: 'connection_name', + width: 250, + minWidth: 170, + className: 'connections-name-cell', + Cell: ({ row, value }) => { + const conn = row.original + + if (editingId === conn.id) { + return ( +
+ { + editValueRef.current = e.target.value + }} + onKeyDown={e => handleKeyDown(e, conn.id)} + disabled={savingId === conn.id} + /> + + +
+ ) + } + + return ( +
+ {value || '—'} + +
+ ) + } + }, + { + Header: __('Account', 'bit-integrations'), + accessor: 'account_name', + width: 180, + minWidth: 120, + Cell: ({ value }) => value || '—' + }, + { + Header: __('Auth Type', 'bit-integrations'), + accessor: 'auth_type', + width: 120, + minWidth: 95, + Cell: ({ value }) => {value || '—'} + }, + { + Header: __('Created', 'bit-integrations'), + accessor: 'created_at', + width: 140, + minWidth: 110, + Cell: ({ value }) => value || '—' + }, + { + id: 't_action', + Header: '', + accessor: 'id', + width: 70, + minWidth: 60, + maxWidth: 80, + disableSortBy: true, + Cell: ({ row }) => ( +
+ +
+ ) + } + ], + [editingId, savingId, cancelEdit, saveRename, startEdit] + ) return ( -
+

{__('Connections', 'bit-integrations')}

+ + {__('Showing', 'bit-integrations')} {filteredConnections.length} {__('of', 'bit-integrations')}{' '} + {connections.length} +
-
- setSearch(e.target.value)} - /> +
+ + - - - - + + + } + /> -
- {filtered.length === 0 ? ( -

+ {!isLoading && filteredConnections.length === 0 && ( +

{connections.length === 0 ? __('No connections saved yet. Authorize an app from any integration to add one.', 'bit-integrations') : __('No connections match the current filters.', 'bit-integrations')}

- ) : ( -
- - - - - - - - - - - - {filtered.map(conn => ( - - - - - - - - - ))} - -
{__('App', 'bit-integrations')}{__('Connection Name', 'bit-integrations')}{__('Account', 'bit-integrations')}{__('Auth Type', 'bit-integrations')}{__('Created', 'bit-integrations')}
{conn.app_slug} - {editingId === conn.id ? ( -
- setEditValue(e.target.value)} - onKeyDown={e => handleKeyDown(e, conn.id)} - disabled={savingId === conn.id} - /> - - -
- ) : ( -
- {conn.connection_name || '—'} - -
- )} -
{conn.account_name || '—'}{conn.auth_type}{conn.created_at || '—'} - -
)}
diff --git a/frontend/src/resource/sass/app.scss b/frontend/src/resource/sass/app.scss index 88004086a..fdabdafc8 100644 --- a/frontend/src/resource/sass/app.scss +++ b/frontend/src/resource/sass/app.scss @@ -7379,6 +7379,126 @@ _:-ms-fullscreen, } } +#connections-page { + .f-table .thead .tr .th, + .f-table .tbody .tr .td { + padding-left: 6px; + padding-right: 6px; + } + + .f-table .thead .tr .th:first-child, + .f-table .tbody .tr .td:first-child { + padding-left: 10px; + } + + .connections-count-txt { + font-size: 13px; + color: #5f6d8a; + align-self: center; + } + + .connections-table-filters { + align-items: center; + gap: 8px; + flex-wrap: wrap; + } + + .connections-filter-select { + width: 220px; + } + + .connections-app-tag { + display: inline-block; + padding: 4px 9px; + border-radius: 999px; + border: 1px solid #d8e0f1; + background: #f2f5fb; + color: #34415f; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + } + + .connections-auth-tag { + display: inline-block; + padding: 4px 9px; + border-radius: 999px; + border: 1px solid #f4d8b8; + background: #fff4e7; + color: #8f5c21; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + } + + .connections-name-cell { + line-height: 1.35; + overflow: visible !important; + position: relative; + z-index: 20; + } + + .connections-name-row { + gap: 6px; + min-width: 0; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .connections-name-row .tooltip { + position: relative; + z-index: 30; + } + + .connections-name-row .tooltip::after { + z-index: 1200; + } + + .connections-edit-row { + width: 100%; + align-items: center; + gap: 6px; + + .btcd-paper-inp { + min-width: 140px; + } + } + + .connections-action-cell { + justify-content: center; + width: 100%; + } + + .connections-empty-note { + color: #697694; + line-height: 1.5; + padding-bottom: 12px; + } +} + +@media only screen and (max-width: 767px) { + #connections-page { + .connections-table-filters { + width: 100%; + } + + .connections-filter-select { + width: 100%; + max-width: none; + } + + .connections-count-txt { + display: none; + } + } +} + @keyframes fadeIn { from { opacity: 0; @@ -7399,4 +7519,4 @@ _:-ms-fullscreen, opacity: 1; transform: translateY(0) scale(1); } -} \ No newline at end of file +} From f3fd7042dd164d0fd1a2f65cbb4571b0fe737b77 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 4 May 2026 15:13:01 +0600 Subject: [PATCH 03/58] refactor: Clean up and comment out unused code in ConnectionController, connectionApi, MailChimp components, and ConnectionAccountSelect --- backend/controller/ConnectionController.php | 54 ++++++------- frontend/src/Utils/connectionApi.js | 32 ++++---- .../AllIntegrations/MailChimp/MailChimp.jsx | 2 +- .../MailChimp/MailChimpAuthorization.jsx | 12 +-- .../MailChimp/MailChimpCommonFunc.js | 25 +++--- .../Connections/ConnectionAccountSelect.jsx | 76 ------------------- 6 files changed, 58 insertions(+), 143 deletions(-) diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index e0485263e..6e5df690e 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -91,19 +91,19 @@ public function save($request) wp_send_json_error($payload->get_error_message()); } - $existingId = $this->findExistingIdForAccount($payload['app_slug'], $payload['account_name']); + // $existingId = $this->findExistingIdForAccount($payload['app_slug'], $payload['account_name']); - if ($existingId > 0) { - $payload['id'] = $existingId; + // if ($existingId > 0) { + // $payload['id'] = $existingId; - $updated = $this->persist($payload, $existingId); + // $updated = $this->persist($payload, $existingId); - if (is_wp_error($updated)) { - wp_send_json_error($updated->get_error_message()); - } + // if (is_wp_error($updated)) { + // wp_send_json_error($updated->get_error_message()); + // } - wp_send_json_success(['data' => $this->formatRow($updated)]); - } + // wp_send_json_success(['data' => $this->formatRow($updated)]); + // } $created = $this->persist($payload, 0); @@ -235,7 +235,7 @@ private function buildPayload($request, bool $isUpdate) return new WP_Error('missing_app_slug', __('App slug is required', 'bit-integrations')); } - if ($authType !== '' && !in_array($authType, self::ALLOWED_AUTH_TYPES, true)) { + if ($authType !== '' && !\in_array($authType, self::ALLOWED_AUTH_TYPES, true)) { return new WP_Error('invalid_auth_type', __('Invalid auth type', 'bit-integrations')); } @@ -320,7 +320,7 @@ private function encryptValues(array $authDetails, array $encryptKeys): array foreach ($encryptKeys as $path) { $value = $this->getNestedValue($authDetails, $path); - if (!is_string($value) || $value === '') { + if (!\is_string($value) || $value === '') { continue; } @@ -335,7 +335,7 @@ private function decryptValues(array $authDetails, array $encryptKeys): array foreach ($encryptKeys as $path) { $value = $this->getNestedValue($authDetails, $path); - if (!is_string($value) || $value === '') { + if (!\is_string($value) || $value === '') { continue; } @@ -409,11 +409,11 @@ private function resolveEncryptKeys($request): array return []; } - if (is_string($request->encrypt_keys)) { + if (\is_string($request->encrypt_keys)) { return $this->parseEncryptKeys($request->encrypt_keys); } - if (is_array($request->encrypt_keys)) { + if (\is_array($request->encrypt_keys)) { $keys = []; foreach ($request->encrypt_keys as $key) { $key = $this->sanitizeScalar($key); @@ -431,9 +431,9 @@ private function resolveEncryptKeys($request): array private function parseEncryptKeys($value): array { - if (is_array($value)) { + if (\is_array($value)) { $keys = array_filter(array_map([$this, 'sanitizeScalar'], $value)); - } elseif (is_string($value) && $value !== '') { + } elseif (\is_string($value) && $value !== '') { $keys = array_filter(array_map('trim', explode(',', $value))); } else { return []; @@ -444,18 +444,18 @@ private function parseEncryptKeys($value): array private function normalizeArray($value): array { - if (is_array($value)) { + if (\is_array($value)) { return $value; } - if (is_object($value)) { + if (\is_object($value)) { return json_decode(wp_json_encode($value), true) ?: []; } - if (is_string($value) && $value !== '') { + if (\is_string($value) && $value !== '') { $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; + return \is_array($decoded) ? $decoded : []; } return []; @@ -464,15 +464,15 @@ private function normalizeArray($value): array private function getNestedValue(array $data, string $path) { if ($path === '') { - return null; + return; } $segments = explode('.', $path); $cursor = $data; foreach ($segments as $segment) { - if (!is_array($cursor) || !array_key_exists($segment, $cursor)) { - return null; + if (!\is_array($cursor) || !\array_key_exists($segment, $cursor)) { + return; } $cursor = $cursor[$segment]; @@ -492,7 +492,7 @@ private function setNestedValue(array &$data, string $path, $value): void $cursor = &$data; foreach ($segments as $segment) { - if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) { + if (!isset($cursor[$segment]) || !\is_array($cursor[$segment])) { $cursor[$segment] = []; } @@ -508,7 +508,7 @@ private function normalizeId($request): int return absint($request); } - if (is_object($request)) { + if (\is_object($request)) { foreach (['id', 'connection_id', 'connectionId'] as $key) { if (!empty($request->{$key})) { return absint($request->{$key}); @@ -516,7 +516,7 @@ private function normalizeId($request): int } } - if (is_array($request)) { + if (\is_array($request)) { foreach (['id', 'connection_id', 'connectionId'] as $key) { if (!empty($request[$key])) { return absint($request[$key]); @@ -529,7 +529,7 @@ private function normalizeId($request): int private function sanitizeScalar($value): string { - if (!is_scalar($value)) { + if (!\is_scalar($value)) { return ''; } diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index b09d6715c..20f469d0d 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -23,19 +23,19 @@ export const deleteConnection = id => bitsFetch({ id }, 'connections/delete') * * @returns {Promise<{ success: boolean, data: object }>} */ -export const persistConnection = ({ - appSlug, - authType = 'oauth2', - connectionName, - accountName, - authDetails, - encryptKeys = [] -}) => - saveConnection({ - app_slug: appSlug, - auth_type: authType, - connection_name: connectionName, - account_name: accountName, - auth_details: authDetails, - encrypt_keys: encryptKeys - }) +// export const persistConnection = ({ +// appSlug, +// authType = 'oauth2', +// connectionName, +// accountName, +// authDetails, +// encryptKeys = [] +// }) => +// saveConnection({ +// app_slug: appSlug, +// auth_type: authType, +// connection_name: connectionName, +// account_name: accountName, +// auth_details: authDetails, +// encrypt_keys: encryptKeys +// }) diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx index 91d19318c..0426a2f1e 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx @@ -27,7 +27,7 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { name: 'Mail Chimp', type: 'Mail Chimp', connection_id: null, - connectionName: '', + connectionName: 'Mail Chimp Connection', clientId: '', clientSecret: '', listId: '', diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx index 40518fdcb..ce21724c5 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx @@ -1,17 +1,17 @@ import { useEffect, useState } from 'react' import BackIcn from '../../../Icons/BackIcn' -import { __ } from '../../../Utils/i18nwrap' import { listConnections } from '../../../Utils/connectionApi' +import { __ } from '../../../Utils/i18nwrap' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import ConnectionAccountSelect from '../../Connections/ConnectionAccountSelect' import LoaderSm from '../../Loaders/LoaderSm' import CopyText from '../../Utilities/CopyText' +import TutorialLink from '../../Utilities/TutorialLink' import { applyConnectionToConf, handleMailChimpAuthorize, refreshModules } from './MailChimpCommonFunc' -import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import ConnectionAccountSelect from '../../Connections/ConnectionAccountSelect' const APP_SLUG = 'MailChimp' @@ -153,13 +153,9 @@ export default function MailChimpAuthorization({ {!isInfo && ( diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js index deb188899..ad1af4ceb 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js @@ -1,8 +1,7 @@ +import { create } from 'mutative' import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' +import { reauthorizeConnection, saveConnection } from '../../../Utils/connectionApi' import { __, sprintf } from '../../../Utils/i18nwrap' -import { create } from 'mutative' -import { persistConnection, reauthorizeConnection } from '../../../Utils/connectionApi' const APP_SLUG = 'MailChimp' // Flat auth_details shape — matches OAuth2Authorization::getAccessToken contract. @@ -374,11 +373,7 @@ const tokenHelper = ( if (result && result.success) { const tokenDetails = result.data const accountName = tokenDetails.login_email || tokenDetails.accountname || tokenDetails.dc - const connectionName = - confTmp.connectionName?.trim() || - (tokenDetails.accountname && tokenDetails.login_email - ? `${tokenDetails.accountname} (${tokenDetails.login_email})` - : accountName) + const connectionName = confTmp.connectionName?.trim() || sprintf(__('MailChimp Connection (%s)', 'bit-integrations'), accountName) const authDetails = { access_token: tokenDetails.access_token, @@ -401,13 +396,13 @@ const tokenHelper = ( account_name: accountName, connection_name: connectionName }) - : persistConnection({ - appSlug: APP_SLUG, - authType: 'oauth2', - connectionName, - accountName, - authDetails, - encryptKeys: ENCRYPT_KEYS + : saveConnection({ + app_slug: APP_SLUG, + auth_type: 'oauth2', + auth_details: authDetails, + encrypt_keys: ENCRYPT_KEYS, + account_name: accountName, + connection_name: connectionName }) const connRes = await persist diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx index 3b4b2c502..6adbb9d64 100644 --- a/frontend/src/components/Connections/ConnectionAccountSelect.jsx +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -7,30 +7,13 @@ const NEW_VALUE = '__new__' export default function ConnectionAccountSelect({ connections, - setConnections, selectedId, onSelect, onAddNew, - onReauthorize, - setIsLoading, - setSnackbar, isInfo, label = __('Account:', 'bit-integrations'), newOptionLabel = __('+ Add new connection', 'bit-integrations') }) { - const [confirmOpen, setConfirmOpen] = useState(false) - const popoverRef = useRef(null) - - useEffect(() => { - const handleClickOutside = event => { - if (popoverRef.current && !popoverRef.current.contains(event.target)) { - setConfirmOpen(false) - } - } - document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) - const handleChange = e => { const value = e.target.value @@ -49,24 +32,6 @@ export default function ConnectionAccountSelect({ if (conn) onSelect(conn) } - const handleDelete = () => { - if (!selectedId) return - if (setIsLoading) setIsLoading(true) - deleteConnection(selectedId) - .then(res => { - if (res?.success) { - setConnections(prev => prev.filter(c => c.id !== selectedId)) - onSelect(null) - } else if (setSnackbar) { - setSnackbar({ show: true, msg: __('Failed to delete account', 'bit-integrations') }) - } - }) - .finally(() => { - if (setIsLoading) setIsLoading(false) - setConfirmOpen(false) - }) - } - const dropdownValue = selectedId ? String(selectedId) : '' return ( @@ -95,47 +60,6 @@ export default function ConnectionAccountSelect({ })} - - {selectedId && !isInfo && ( - <> - {onReauthorize && ( - - )} - -
- {confirmOpen ? ( -
-

{__('Delete this account?', 'bit-integrations')}

- - -
- ) : ( - - )} -
- - )}
) From a3e1b5ce5cef6cce93f2bdc9bf722f4f19e7a5e0 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 00:54:09 +0600 Subject: [PATCH 04/58] feat: Add authorization handling and connection management in ConnectionController and related classes --- .../AbstractBaseAuthorization.php | 111 ++++++++++++++++++ .../Basic/BasicAuthorization.php | 14 +++ .../Bearer/BearerTokenAuthorization.php | 14 +++ .../OAuth2/OAuth2Authorization.php | 14 +++ backend/Routes/ajax.php | 1 + backend/controller/ConnectionController.php | 103 ++++++++++++++++ frontend/src/Utils/connectionApi.js | 2 + 7 files changed, 259 insertions(+) diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index dded88b20..b95c0a631 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -8,6 +8,7 @@ use BitApps\Integrations\Core\Database\ConnectionModel; use BitApps\Integrations\Core\Util\Hash; +use BitApps\Integrations\Core\Util\HttpHelper; abstract class AbstractBaseAuthorization { @@ -15,6 +16,8 @@ abstract class AbstractBaseAuthorization protected $connection; + protected $authDetailsOverride; + public function __construct($connectionId) { $this->connectionId = (int) $connectionId; @@ -22,11 +25,101 @@ public function __construct($connectionId) abstract public function getAccessToken(); + public function setAuthHeadersOrParams() + { + $token = $this->getAccessToken(); + + if (is_array($token) && !empty($token['error'])) { + return $token; + } + + return [ + 'authLocation' => 'header', + 'data' => ['Authorization' => $token], + ]; + } + + /** + * Test authorization credentials by calling an API endpoint. + */ + public function authorize(string $apiEndpoint, string $method = 'GET', $payload = null, array $headers = []): array + { + if ($apiEndpoint === '') { + return [ + 'error' => true, + 'message' => 'API endpoint is required', + ]; + } + + $authConfig = $this->setAuthHeadersOrParams(); + + if (isset($authConfig['error']) && $authConfig['error']) { + return $authConfig; + } + + $url = $apiEndpoint; + $authLocation = $authConfig['authLocation'] ?? 'header'; + $authData = (isset($authConfig['data']) && is_array($authConfig['data'])) ? $authConfig['data'] : []; + + if ($authLocation === 'query' && !empty($authData)) { + $query = http_build_query($authData); + $separator = strpos($url, '?') !== false ? '&' : '?'; + $url .= $separator . $query; + } elseif (!empty($authData)) { + $headers = array_merge($headers, $authData); + } + + $response = $this->sendRequest($url, strtoupper($method), $payload, $headers); + + if (is_wp_error($response)) { + return [ + 'error' => true, + 'message' => $response->get_error_message(), + 'response' => $response, + ]; + } + + if ((is_object($response) && !empty($response->error)) || (is_array($response) && !empty($response['error']))) { + return [ + 'error' => true, + 'message' => is_object($response) ? ($response->error ?? 'Authorization failed') : ($response['error'] ?? 'Authorization failed'), + 'response' => $response, + ]; + } + + if (isset(HttpHelper::$responseCode) && (int) HttpHelper::$responseCode >= 400) { + return [ + 'error' => true, + 'message' => 'Authorization failed', + 'response' => $response, + ]; + } + + return [ + 'success' => true, + 'response' => $response, + ]; + } + public function getConnectionId(): int { return (int) $this->connectionId; } + public function setAuthDetailsOverride(array $authDetails) + { + $this->authDetailsOverride = $authDetails; + + return $this; + } + + public function clearAuthDetailsOverride() + { + $this->authDetailsOverride = null; + + return $this; + } + public function getConnection() { if ($this->connection === null) { @@ -38,6 +131,10 @@ public function getConnection() public function getAuthDetails() { + if (is_array($this->authDetailsOverride)) { + return $this->authDetailsOverride; + } + $connection = $this->getConnection(); if (!$connection) { @@ -153,6 +250,20 @@ protected function http() return new HttpHelper(); } + protected function sendRequest(string $url, string $method, $payload, array $headers) + { + switch ($method) { + case 'GET': + return HttpHelper::get($url, $payload, $headers); + + case 'POST': + return HttpHelper::post($url, $payload, $headers); + + default: + return HttpHelper::request($url, $method, $payload, $headers); + } + } + protected static function getNestedValue(array $data, string $path) { if ($path === '') { diff --git a/backend/Authorization/Basic/BasicAuthorization.php b/backend/Authorization/Basic/BasicAuthorization.php index 847b262a2..a310d66f0 100644 --- a/backend/Authorization/Basic/BasicAuthorization.php +++ b/backend/Authorization/Basic/BasicAuthorization.php @@ -23,4 +23,18 @@ public function getAccessToken() return 'Basic ' . base64_encode($authDetails['username'] . ':' . $authDetails['password']); } + + public function setAuthHeadersOrParams() + { + $token = $this->getAccessToken(); + + if (is_array($token) && !empty($token['error'])) { + return $token; + } + + return [ + 'authLocation' => 'header', + 'data' => ['Authorization' => $token], + ]; + } } diff --git a/backend/Authorization/Bearer/BearerTokenAuthorization.php b/backend/Authorization/Bearer/BearerTokenAuthorization.php index 7f4e6aa54..b72ff6e03 100644 --- a/backend/Authorization/Bearer/BearerTokenAuthorization.php +++ b/backend/Authorization/Bearer/BearerTokenAuthorization.php @@ -23,4 +23,18 @@ public function getAccessToken() return 'Bearer ' . $authDetails['token']; } + + public function setAuthHeadersOrParams() + { + $token = $this->getAccessToken(); + + if (is_array($token) && !empty($token['error'])) { + return $token; + } + + return [ + 'authLocation' => 'header', + 'data' => ['Authorization' => $token], + ]; + } } diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index 4b960bfc6..908ed83a5 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -77,6 +77,20 @@ public function getAccessToken() return $this->tokenPrefix . $authDetails['access_token']; } + public function setAuthHeadersOrParams() + { + $token = $this->getAccessToken(); + + if (is_array($token) && !empty($token['error'])) { + return $token; + } + + return [ + 'authLocation' => 'header', + 'data' => ['Authorization' => $token], + ]; + } + public function refreshAccessToken(array $authDetails) { $url = $this->refreshTokenUrl ?: ($authDetails['refresh_token_url'] ?? ($authDetails['refreshTokenUrl'] ?? '')); diff --git a/backend/Routes/ajax.php b/backend/Routes/ajax.php index 3f0f01a50..a44be705e 100644 --- a/backend/Routes/ajax.php +++ b/backend/Routes/ajax.php @@ -63,6 +63,7 @@ // Connection management (encrypted reusable credentials) Route::get('connections/list', [ConnectionController::class, 'index']); Route::get('connections/get', [ConnectionController::class, 'getById']); +Route::post('connections/authorize', [ConnectionController::class, 'authorize']); Route::post('connections/save', [ConnectionController::class, 'save']); Route::post('connections/update', [ConnectionController::class, 'update']); Route::post('connections/reauthorize', [ConnectionController::class, 'reauthorize']); diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 6e5df690e..3df377e5f 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -7,6 +7,7 @@ } use BitApps\Integrations\Authorization\AuthorizationType; +use BitApps\Integrations\Authorization\AuthorizationFactory; use BitApps\Integrations\Core\Database\ConnectionModel; use BitApps\Integrations\Core\Util\Capabilities; use BitApps\Integrations\Core\Util\Hash; @@ -207,6 +208,66 @@ public function reauthorize($request) wp_send_json_success(['data' => $this->formatRow($row)]); } + public function authorize($request) + { + $this->guardWrite(); + + $authType = $this->sanitizeScalar($request->auth_type ?? ''); + $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); + $apiEndpoint = esc_url_raw((string) ($request->api_endpoint ?? '')); + $method = strtoupper($this->sanitizeScalar($request->method ?? 'GET')); + $payload = isset($request->payload) ? $this->normalizePayload($request->payload) : null; + $headers = $this->normalizeHeaders($request->headers ?? []); + + if ($authType === '') { + wp_send_json_error(__('Auth type is required', 'bit-integrations')); + } + + if (!\in_array($authType, self::ALLOWED_AUTH_TYPES, true)) { + wp_send_json_error(__('Invalid auth type', 'bit-integrations')); + } + + if (empty($request->auth_details)) { + wp_send_json_error(__('Authorization details are required', 'bit-integrations')); + } + + if ($apiEndpoint === '') { + wp_send_json_error(__('API endpoint is required', 'bit-integrations')); + } + + $authDetails = $this->normalizeArray($request->auth_details); + + if (empty($authDetails)) { + wp_send_json_error(__('Authorization details are required', 'bit-integrations')); + } + + try { + $handler = AuthorizationFactory::getAuthorizationHandler($authType, 0, $appSlug); + $handler->setAuthDetailsOverride($authDetails); + $result = $handler->authorize($apiEndpoint, $method, $payload, $headers); + } catch (\Exception $e) { + wp_send_json_error($e->getMessage()); + } + + if (!empty($result['error'])) { + wp_send_json_error( + [ + 'message' => $result['message'] ?? __('Authorization failed', 'bit-integrations'), + 'response' => $result['response'] ?? null, + ], + 400 + ); + } + + wp_send_json_success( + [ + 'message' => __('Authorization successful', 'bit-integrations'), + 'response' => $result['response'] ?? null, + ], + 200 + ); + } + public function delete($request) { $this->guardWrite(); @@ -461,6 +522,48 @@ private function normalizeArray($value): array return []; } + private function normalizePayload($value) + { + if (\is_array($value)) { + return $value; + } + + if (\is_object($value)) { + return json_decode(wp_json_encode($value), true) ?: []; + } + + return $value; + } + + private function normalizeHeaders($value): array + { + if (\is_object($value)) { + $value = json_decode(wp_json_encode($value), true) ?: []; + } + + if (!\is_array($value)) { + return []; + } + + $headers = []; + + foreach ($value as $key => $headerValue) { + if (!\is_scalar($key)) { + continue; + } + + $headerName = sanitize_text_field((string) $key); + + if ($headerName === '' || !\is_scalar($headerValue)) { + continue; + } + + $headers[$headerName] = sanitize_text_field((string) $headerValue); + } + + return $headers; + } + private function getNestedValue(array $data, string $path) { if ($path === '') { diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index 20f469d0d..7b9efa259 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -10,6 +10,8 @@ export const listConnections = appSlug => export const getConnection = id => bitsFetch(null, 'connections/get', { id }, 'GET') +export const authorizeConnection = payload => bitsFetch(payload, 'connections/authorize') + export const saveConnection = payload => bitsFetch(payload, 'connections/save') export const updateConnection = payload => bitsFetch(payload, 'connections/update') From 5014ec8b6de127ab151aec00dd0d016ffe5ea233 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 01:02:37 +0600 Subject: [PATCH 05/58] feat: Refactor authorization methods to unify header handling across authorization types --- .../AbstractBaseAuthorization.php | 68 +++++++++---------- .../ApiKey/ApiKeyAuthorization.php | 2 +- .../Basic/BasicAuthorization.php | 4 +- .../Bearer/BearerTokenAuthorization.php | 4 +- .../OAuth2/OAuth2Authorization.php | 10 +-- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index b95c0a631..e20de6324 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -25,25 +25,18 @@ public function __construct($connectionId) abstract public function getAccessToken(); - public function setAuthHeadersOrParams() - { - $token = $this->getAccessToken(); - - if (is_array($token) && !empty($token['error'])) { - return $token; - } - - return [ - 'authLocation' => 'header', - 'data' => ['Authorization' => $token], - ]; - } + abstract public function getAuthHeadersOrParams(); /** * Test authorization credentials by calling an API endpoint. + * + * @param null|mixed $payload */ public function authorize(string $apiEndpoint, string $method = 'GET', $payload = null, array $headers = []): array { + $apiEndpoint = trim($apiEndpoint); + $method = strtoupper(trim($method)); + if ($apiEndpoint === '') { return [ 'error' => true, @@ -51,7 +44,14 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload ]; } - $authConfig = $this->setAuthHeadersOrParams(); + $authConfig = $this->getAuthHeadersOrParams(); + + if (!\is_array($authConfig)) { + return [ + 'error' => true, + 'message' => 'Invalid authorization config', + ]; + } if (isset($authConfig['error']) && $authConfig['error']) { return $authConfig; @@ -59,7 +59,7 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload $url = $apiEndpoint; $authLocation = $authConfig['authLocation'] ?? 'header'; - $authData = (isset($authConfig['data']) && is_array($authConfig['data'])) ? $authConfig['data'] : []; + $authData = (isset($authConfig['data']) && \is_array($authConfig['data'])) ? $authConfig['data'] : []; if ($authLocation === 'query' && !empty($authData)) { $query = http_build_query($authData); @@ -69,7 +69,7 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload $headers = array_merge($headers, $authData); } - $response = $this->sendRequest($url, strtoupper($method), $payload, $headers); + $response = $this->sendRequest($url, $method === '' ? 'GET' : $method, $payload, $headers); if (is_wp_error($response)) { return [ @@ -79,15 +79,15 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload ]; } - if ((is_object($response) && !empty($response->error)) || (is_array($response) && !empty($response['error']))) { + if ((\is_object($response) && !empty($response->error)) || (\is_array($response) && !empty($response['error']))) { return [ 'error' => true, - 'message' => is_object($response) ? ($response->error ?? 'Authorization failed') : ($response['error'] ?? 'Authorization failed'), + 'message' => \is_object($response) ? ($response->error ?? 'Authorization failed') : ($response['error'] ?? 'Authorization failed'), 'response' => $response, ]; } - if (isset(HttpHelper::$responseCode) && (int) HttpHelper::$responseCode >= 400) { + if (isset(HttpHelper::$responseCode) && ((int) HttpHelper::$responseCode < 200 || (int) HttpHelper::$responseCode >= 300)) { return [ 'error' => true, 'message' => 'Authorization failed', @@ -131,14 +131,14 @@ public function getConnection() public function getAuthDetails() { - if (is_array($this->authDetailsOverride)) { + if (\is_array($this->authDetailsOverride)) { return $this->authDetailsOverride; } $connection = $this->getConnection(); if (!$connection) { - return null; + return; } $authDetails = $this->decodeAuthDetails($connection->auth_details ?? null); @@ -156,7 +156,7 @@ public function getAuthDetails() foreach ($encryptKeys as $path) { $value = self::getNestedValue($authDetails, $path); - if (!is_string($value) || $value === '') { + if (!\is_string($value) || $value === '') { continue; } @@ -188,7 +188,7 @@ public function updateAuthDetails(array $authDetails): bool foreach ($encryptKeys as $path) { $value = self::getNestedValue($authDetails, $path); - if (!is_string($value) || $value === '') { + if (!\is_string($value) || $value === '') { continue; } @@ -215,18 +215,18 @@ public function updateAuthDetails(array $authDetails): bool protected function decodeAuthDetails($value): array { - if (is_array($value)) { + if (\is_array($value)) { return $value; } - if (is_object($value)) { + if (\is_object($value)) { return json_decode(wp_json_encode($value), true) ?: []; } - if (is_string($value) && $value !== '') { + if (\is_string($value) && $value !== '') { $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; + return \is_array($decoded) ? $decoded : []; } return []; @@ -234,11 +234,11 @@ protected function decodeAuthDetails($value): array protected function parseEncryptKeys($value): array { - if (is_array($value)) { + if (\is_array($value)) { return array_values(array_filter(array_map('strval', $value))); } - if (is_string($value) && $value !== '') { + if (\is_string($value) && $value !== '') { return array_values(array_filter(array_map('trim', explode(',', $value)))); } @@ -267,15 +267,15 @@ protected function sendRequest(string $url, string $method, $payload, array $hea protected static function getNestedValue(array $data, string $path) { if ($path === '') { - return null; + return; } $segments = explode('.', $path); $cursor = $data; foreach ($segments as $segment) { - if (!is_array($cursor) || !array_key_exists($segment, $cursor)) { - return null; + if (!\is_array($cursor) || !\array_key_exists($segment, $cursor)) { + return; } $cursor = $cursor[$segment]; @@ -295,7 +295,7 @@ protected static function setNestedValue(array &$data, string $path, $value): vo $cursor = &$data; foreach ($segments as $segment) { - if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) { + if (!isset($cursor[$segment]) || !\is_array($cursor[$segment])) { $cursor[$segment] = []; } @@ -315,7 +315,7 @@ private function loadConnection() ); if (is_wp_error($result) || empty($result[0])) { - return null; + return; } return $result[0]; diff --git a/backend/Authorization/ApiKey/ApiKeyAuthorization.php b/backend/Authorization/ApiKey/ApiKeyAuthorization.php index 7ff901d61..57ab5e330 100644 --- a/backend/Authorization/ApiKey/ApiKeyAuthorization.php +++ b/backend/Authorization/ApiKey/ApiKeyAuthorization.php @@ -24,7 +24,7 @@ public function getAccessToken() return $authDetails['value']; } - public function setAuthHeadersOrParams() + public function getAuthHeadersOrParams() { $authDetails = $this->getAuthDetails(); diff --git a/backend/Authorization/Basic/BasicAuthorization.php b/backend/Authorization/Basic/BasicAuthorization.php index a310d66f0..f7bbf9bc4 100644 --- a/backend/Authorization/Basic/BasicAuthorization.php +++ b/backend/Authorization/Basic/BasicAuthorization.php @@ -24,11 +24,11 @@ public function getAccessToken() return 'Basic ' . base64_encode($authDetails['username'] . ':' . $authDetails['password']); } - public function setAuthHeadersOrParams() + public function getAuthHeadersOrParams() { $token = $this->getAccessToken(); - if (is_array($token) && !empty($token['error'])) { + if (\is_array($token) && !empty($token['error'])) { return $token; } diff --git a/backend/Authorization/Bearer/BearerTokenAuthorization.php b/backend/Authorization/Bearer/BearerTokenAuthorization.php index b72ff6e03..445d8827f 100644 --- a/backend/Authorization/Bearer/BearerTokenAuthorization.php +++ b/backend/Authorization/Bearer/BearerTokenAuthorization.php @@ -24,11 +24,11 @@ public function getAccessToken() return 'Bearer ' . $authDetails['token']; } - public function setAuthHeadersOrParams() + public function getAuthHeadersOrParams() { $token = $this->getAccessToken(); - if (is_array($token) && !empty($token['error'])) { + if (\is_array($token) && !empty($token['error'])) { return $token; } diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index 908ed83a5..6e3b87568 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -77,11 +77,11 @@ public function getAccessToken() return $this->tokenPrefix . $authDetails['access_token']; } - public function setAuthHeadersOrParams() + public function getAuthHeadersOrParams() { $token = $this->getAccessToken(); - if (is_array($token) && !empty($token['error'])) { + if (\is_array($token) && !empty($token['error'])) { return $token; } @@ -106,15 +106,15 @@ public function refreshAccessToken(array $authDetails) $response = HttpHelper::post($url, $body, ['Content-Type' => 'application/x-www-form-urlencoded']); - if (HttpHelper::$responseCode !== 200 || (is_object($response) && isset($response->error))) { + if (HttpHelper::$responseCode !== 200 || (\is_object($response) && isset($response->error))) { return [ 'error' => true, - 'message' => is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed', + 'message' => \is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed', 'response' => $response, ]; } - $response = is_object($response) ? json_decode(wp_json_encode($response), true) : (array) $response; + $response = \is_object($response) ? json_decode(wp_json_encode($response), true) : (array) $response; $authDetails['access_token'] = $response['access_token'] ?? ($authDetails['access_token'] ?? ''); From 5bb2c8fe96c2b10c55fb57060c18c4b20bf918d9 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 15:31:24 +0600 Subject: [PATCH 06/58] feat: global authorization added for api keys --- frontend/src/Utils/connectionAuth.js | 58 +++++ .../AllIntegrations/Fabman/Fabman.jsx | 1 + .../Fabman/FabmanAuthorization.jsx | 151 +++++++----- .../Connections/AddNewConnection.jsx | 230 ++++++++++++++++++ .../Connections/ConnectionAccountSelect.jsx | 79 +++--- .../Connections/PlatformAuthorization.jsx | 201 +++++++++++++++ frontend/src/hooks/useFetch.js | 4 +- 7 files changed, 618 insertions(+), 106 deletions(-) create mode 100644 frontend/src/Utils/connectionAuth.js create mode 100644 frontend/src/components/Connections/AddNewConnection.jsx create mode 100644 frontend/src/components/Connections/PlatformAuthorization.jsx diff --git a/frontend/src/Utils/connectionAuth.js b/frontend/src/Utils/connectionAuth.js new file mode 100644 index 000000000..54d52e639 --- /dev/null +++ b/frontend/src/Utils/connectionAuth.js @@ -0,0 +1,58 @@ +import { reauthorizeConnection, saveConnection } from './connectionApi' + +export const AUTH_TYPES = Object.freeze({ + NO_AUTH: 'no_auth', + OAUTH2: 'oauth2', + API_KEY: 'api_key', + BEARER_TOKEN: 'bearer_token', + BASIC_AUTH: 'basic_auth', + CUSTOM: 'custom' +}) + +export const defaultEncryptKeys = { + [AUTH_TYPES.API_KEY]: ['value'], + [AUTH_TYPES.BASIC_AUTH]: ['password'], + [AUTH_TYPES.BEARER_TOKEN]: ['token'], + [AUTH_TYPES.OAUTH2]: ['client_id', 'client_secret', 'access_token', 'refresh_token'] +} + +export const isNoAuthType = authType => authType === AUTH_TYPES.NO_AUTH + +export const normalizeAuthType = authType => + Object.values(AUTH_TYPES).includes(authType) ? authType : AUTH_TYPES.OAUTH2 + +/** + * Save or reauthorize a reusable connection. + * + * @returns {Promise<{success: boolean, data?: {data?: object}}>} + */ +export const persistConnectionAuthorization = ({ + appSlug, + authType = AUTH_TYPES.OAUTH2, + connectionId = null, + connectionName = '', + accountName = '', + authDetails = {}, + encryptKeys = [], + status +}) => { + const sanitizedConnectionName = connectionName?.trim() || '' + const sanitizedAccountName = accountName?.trim() || '' + const normalizedAuthType = normalizeAuthType(authType) + + const payload = { + auth_type: normalizedAuthType, + auth_details: isNoAuthType(normalizedAuthType) ? {} : authDetails, + encrypt_keys: Array.isArray(encryptKeys) ? encryptKeys : [] + } + + if (sanitizedConnectionName) payload.connection_name = sanitizedConnectionName + if (sanitizedAccountName) payload.account_name = sanitizedAccountName + if (typeof status === 'number') payload.status = status + + if (connectionId) { + return reauthorizeConnection({ id: connectionId, ...payload }) + } + + return saveConnection({ app_slug: appSlug, ...payload }) +} diff --git a/frontend/src/components/AllIntegrations/Fabman/Fabman.jsx b/frontend/src/components/AllIntegrations/Fabman/Fabman.jsx index 7644e8587..d20fff8eb 100644 --- a/frontend/src/components/AllIntegrations/Fabman/Fabman.jsx +++ b/frontend/src/components/AllIntegrations/Fabman/Fabman.jsx @@ -151,6 +151,7 @@ export default function Fabman({ formFields, setFlow, flow, allIntegURL }) { const [fabmanConf, setFabmanConf] = useState({ name: 'Fabman', type: 'Fabman', + app_slug: 'fabman', field_map: [{ formField: '', fabmanFormField: '' }], customFields: [], actions: {}, diff --git a/frontend/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx b/frontend/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx index 4f575a832..7a8bb72a8 100644 --- a/frontend/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Fabman/FabmanAuthorization.jsx @@ -7,6 +7,8 @@ import { fabmanAuthentication, fetchFabmanWorkspaces } from './FabmanCommonFunc' import Note from '../../Utilities/Note' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import TutorialLink from '../../Utilities/TutorialLink' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' const STEP_ONE_STYLE = { width: 900, height: 'auto' } @@ -40,77 +42,98 @@ export default function FabmanAuthorization({ } return ( -
- -
- {__('Integration Name:', 'bit-integrations')} -
- -
- {__('API Key:', 'bit-integrations')} -
- -
- {error.apiKey} -
+ + ) - {!isInfo && ( -
- + // return ( + //
+ // + //
+ // {__('Integration Name:', 'bit-integrations')} + //
+ // + //
+ // {__('API Key:', 'bit-integrations')} + //
+ // + //
+ // {error.apiKey} + //
- -
- )} - -
- ) + // {!isInfo && ( + //
+ // + + // + //
+ // )} + // + //
+ // ) } const fabmanApiKeyNote = `

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

  • ${sprintf( - __('Log in to your %s.', 'bit-integrations'), - 'Fabman account' - )}
  • + __('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/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx new file mode 100644 index 000000000..ce47f1acf --- /dev/null +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -0,0 +1,230 @@ +import { add, set } from 'lodash' +import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' +import { __ } from '../../Utils/i18nwrap' +import LoaderSm from '../Loaders/LoaderSm' +import toast from 'react-hot-toast' +import { useState } from 'react' +import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' + +export default function AddNewConnection({ + authDetails, + config, + setConfig, + isInfo = false, + setShowNextButton, + customAuthFields +}) { + const [authData, setAuthData] = useState({}) + const [isAuthorized, setIsAuthorized] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [errors, setErrors] = useState({ connectionName: '' }) + const { authType, apiEndpoint, method } = authDetails || {} + + const handleChange = e => { + const { name, value } = e.target + setAuthData(prev => ({ ...prev, [name]: value })) + } + + const resetErrors = () => { + setErrors({ connectionName: '' }) + } + + const handleAuthorize = () => { + resetErrors() + if (!authData.connectionName) { + setErrors({ connectionName: __('Connection name is required', 'bit-integrations') }) + return + } + + if (authType === AUTH_TYPES.API_KEY && !authData.api_key) { + setErrors(prev => ({ ...prev, api_key: __('API key is required', 'bit-integrations') })) + return + } + + if (authType === AUTH_TYPES.BASIC_AUTH && (!authData.username || !authData.password)) { + setErrors(prev => ({ + ...prev, + username: !authData.username ? __('Username is required', 'bit-integrations') : '', + password: !authData.password ? __('Password is required', 'bit-integrations') : '' + })) + return + } + + if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token) { + setErrors(prev => ({ ...prev, token: __('Bearer token is required', 'bit-integrations') })) + return + } + + let payload = { + auth_type: authType, + api_endpoint: apiEndpoint || '', + method: method || 'GET', + auth_details: {} + } + + if (authType === AUTH_TYPES.API_KEY) { + payload.auth_details = { key: authDetails?.key || 'X-API-Key', value: authData.api_key, addTo: authData.addTo || 'header' } + } else if (authType === AUTH_TYPES.BASIC_AUTH) { + payload.auth_details = { username: authData.username, password: authData.password } + } else if (authType === AUTH_TYPES.BEARER_TOKEN) { + payload.auth_details = { token: authData.token } + } + + setIsLoading(true) + authorizeConnection(payload).then(res => { + if (res?.success) { + saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: authType, + connection_name: authData.connectionName, + account_name: authData.connectionName, + auth_details: payload.auth_details, + encrypt_keys: defaultEncryptKeys[authType] || [] + }).then(saveRes => { + if (saveRes?.success) { + const connection = saveRes?.data?.data || null + setConfig(prev => ({ ...prev, connection_id: connection?.id })) + setIsAuthorized(true) + setShowNextButton(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + } else { + toast.error( + `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' + }. ${__('please try again', 'bit-integrations')}` + ) + } + }) + } else { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')}${res?.data?.data || res?.data || 'Unknown error'}. ${__('please try again', 'bit-integrations')}` + ) + } + setIsLoading(false) + }) + } + + return ( + <> +
    + {__('Connection Name:', 'bit-integrations')} +
    + +
    {errors?.connectionName}
    + + {authType === AUTH_TYPES.API_KEY && ( + <> +
    + {__('API Key:', 'bit-integrations')} +
    + +
    {errors?.api_key || ''}
    + + {/*
    + {__('API Key Value:', 'bit-integrations')} +
    + +
    {errors.value || ''}
    */} + + {/*
    + {__('Send Key Via:', 'bit-integrations')} +
    + +
    {errors.addTo || ''}
    */} + + )} + + {authType === AUTH_TYPES.BASIC_AUTH && ( + <> +
    + {__('Username:', 'bit-integrations')} +
    + +
    {errors?.username || ''}
    + +
    + {__('Password:', 'bit-integrations')} +
    + +
    {errors?.password || ''}
    + + )} + + {authType === AUTH_TYPES.BEARER_TOKEN && ( + <> +
    + {__('Bearer Token:', 'bit-integrations')} +
    + +
    {errors?.token || ''}
    + + )} + + {customAuthFields} + + + + ) +} diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx index 6adbb9d64..faeee1f20 100644 --- a/frontend/src/components/Connections/ConnectionAccountSelect.jsx +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -1,65 +1,64 @@ -import { useEffect, useRef, useState } from 'react' -import TrashIcn from '../../Icons/TrashIcn' +import { useParams } from 'react-router' import { __ } from '../../Utils/i18nwrap' -import { deleteConnection } from '../../Utils/connectionApi' +import MultiSelect from 'react-multiple-select-dropdown-lite' +import 'react-multiple-select-dropdown-lite/dist/index.css' const NEW_VALUE = '__new__' export default function ConnectionAccountSelect({ + config, + setConfig, connections, - selectedId, - onSelect, - onAddNew, + setShowNewConnection, isInfo, - label = __('Account:', 'bit-integrations'), - newOptionLabel = __('+ Add new connection', 'bit-integrations') }) { - const handleChange = e => { - const value = e.target.value + const { integUrlName } = useParams() + + const handleChange = value => { if (value === NEW_VALUE) { - if (onAddNew) onAddNew() + setShowNewConnection(true) return } + setShowNewConnection(false) + setConfig(prev => ({ ...prev, connection_id: value })) + } - if (!value) { - onSelect(null) - return - } + const dropdownValue = config?.connection_id ? String(config.connection_id) : '' - const id = Number(value) - const conn = connections.find(c => c.id === id) - if (conn) onSelect(conn) - } + const options = [ + ...(Array.isArray(connections) ? connections?.map(conn => { + const accountName = conn.account_name || conn.connection_name + const label = + conn.connection_name && conn.account_name && conn.connection_name !== conn.account_name + ? `${conn.connection_name} (${accountName})` + : conn.connection_name || accountName - const dropdownValue = selectedId ? String(selectedId) : '' + return { + label, + value: String(conn.id) + } + }) : []), + { label: __('+ Add new connection', 'bit-integrations'), value: NEW_VALUE } + ] return (
    - {label} + {integUrlName ? `${integUrlName} ${__('Connections:', 'bit-integrations')}` : __('Connections:', 'bit-integrations')}
    - + singleSelect + closeOnSelect + showSearch + placeholder={__('Select a connection...', 'bit-integrations')} + disabled={isInfo} + />
    ) diff --git a/frontend/src/components/Connections/PlatformAuthorization.jsx b/frontend/src/components/Connections/PlatformAuthorization.jsx new file mode 100644 index 000000000..7c564d020 --- /dev/null +++ b/frontend/src/components/Connections/PlatformAuthorization.jsx @@ -0,0 +1,201 @@ +import { useEffect, useState } from 'react' +import { useParams } from 'react-router' +import useFetch from '../../hooks/useFetch' +import BackIcn from '../../Icons/BackIcn' +import { listConnections } from '../../Utils/connectionApi' +import { __ } from '../../Utils/i18nwrap' +import Note from '../Utilities/Note' +import TutorialLink from '../Utilities/TutorialLink' +import AddNewConnection from './AddNewConnection' +import ConnectionAccountSelect from './ConnectionAccountSelect' + +export default function PlatformAuthorization({ + config, + setConfig, + step, + setStep, + isInfo, + tutorialTitle, + tutorialLinks, + noteDetails = undefined, + authDetails = {}, + customAuthFields +}) { + const [showNextButton, setShowNextButton] = useState(false) + const [errors, setErrors] = useState({ name: '' }) + const [connections, setConnections] = useState([]) + const [showNewConnection, setShowNewConnection] = useState(false) + const [isLoadingIsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const { integUrlName } = useParams() + + // const { data, mutate } = useFetch({ + // action: 'connections/list', + // method: 'GET', + // params: { app_slug: integUrlName }, + // }) + + useEffect(() => { + setIsLoading(true) + listConnections(config?.app_slug || config?.type) + .then(res => { + if (res?.success && Array.isArray(res.data?.data)) { + const savedConnections = res.data.data + setConnections(savedConnections) + } else { + setShowNewConnection(true) + } + }) + .finally(() => setIsLoading(false)) + }, []) + + const handleChange = event => { + const { name, value } = event.target + + setConfig(prev => ({ ...prev, [name]: value })) + } + + return ( +
    + {tutorialTitle && } + +
    + {__('Integration Name:', 'bit-integrations')} +
    + +
    {errors?.name}
    + + + + {showNewConnection && !isInfo && ( + <> + + + {/* {showAuthTypeSelector && ( + <> +
    + {__('Authorization Type:', 'bit-integrations')} +
    + + + )} */} + + {/* {shouldShowDefaultCredentialFields && ( + <> +
    + {__('Homepage URL:', 'bit-integrations')} +
    + + +
    + {__('Authorized Redirect URIs:', 'bit-integrations')} +
    + + + {docsUrl && ( + + {docsPrompt}{' '} + + {docsText || docsUrl} + + + )} + +
    + {clientIdLabel} +
    + +
    {clientIdError}
    + +
    + {clientSecretLabel} +
    + +
    {clientSecretError}
    + + )} */} + + + + )} + + {showNextButton && ( + + )} +
    +
    + + {noteDetails && ( + + )} +
    + ) +} diff --git a/frontend/src/hooks/useFetch.js b/frontend/src/hooks/useFetch.js index eb1bb2554..f80067c49 100644 --- a/frontend/src/hooks/useFetch.js +++ b/frontend/src/hooks/useFetch.js @@ -1,9 +1,9 @@ import useSWR from 'swr' import bitsFetch from '../Utils/bitsFetch' -const useFetch = ({ payload, action, method = 'POST' }) => { +const useFetch = ({ payload, action, method = 'POST', params=null }) => { const { data, error, mutate } = useSWR(action, uri => - bitsFetch(payload, Array.isArray(uri) ? uri[0] : uri, null, method) + bitsFetch(payload, Array.isArray(uri) ? uri[0] : uri, params, method) ) return { data, From e4d37025c8a19512835869e9f93668225328b57b Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 15:52:09 +0600 Subject: [PATCH 07/58] feat: enhance connection management with improved validation and error handling --- .../Connections/AddNewConnection.jsx | 256 ++++++++++-------- .../Connections/ConnectionAccountSelect.jsx | 81 ++++-- .../Connections/PlatformAuthorization.jsx | 227 +++++++--------- 3 files changed, 294 insertions(+), 270 deletions(-) diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index ce47f1acf..3d220e77b 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -1,108 +1,164 @@ -import { add, set } from 'lodash' +import { useCallback, useState } from 'react' +import toast from 'react-hot-toast' import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' +import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' import { __ } from '../../Utils/i18nwrap' import LoaderSm from '../Loaders/LoaderSm' -import toast from 'react-hot-toast' -import { useState } from 'react' -import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' + +const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } + +const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { + const basePayload = { + auth_type: authType, + api_endpoint: apiEndpoint || '', + method: method || 'GET', + auth_details: {} + } + + if (authType === AUTH_TYPES.API_KEY) { + basePayload.auth_details = { + key: authDetails?.key || 'X-API-Key', + value: authData.api_key, + addTo: authData.addTo || 'header' + } + return basePayload + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + basePayload.auth_details = { + username: authData.username, + password: authData.password + } + return basePayload + } + + if (authType === AUTH_TYPES.BEARER_TOKEN) { + basePayload.auth_details = { + token: authData.token + } + } + + return basePayload +} + +const getValidationErrors = (authType, authData) => { + const nextErrors = {} + + if (!authData.connectionName?.trim()) { + nextErrors.connectionName = __('Connection name is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.API_KEY && !authData.api_key?.trim()) { + nextErrors.api_key = __('API key is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + if (!authData.username?.trim()) { + nextErrors.username = __('Username is required', 'bit-integrations') + } + + if (!authData.password?.trim()) { + nextErrors.password = __('Password is required', 'bit-integrations') + } + } + + if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token?.trim()) { + nextErrors.token = __('Bearer token is required', 'bit-integrations') + } + + return nextErrors +} export default function AddNewConnection({ authDetails, config, setConfig, isInfo = false, - setShowNextButton, - customAuthFields + customAuthFields, + onConnectionSaved }) { const [authData, setAuthData] = useState({}) const [isAuthorized, setIsAuthorized] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [errors, setErrors] = useState({ connectionName: '' }) + const [errors, setErrors] = useState({}) const { authType, apiEndpoint, method } = authDetails || {} - const handleChange = e => { - const { name, value } = e.target + const handleChange = useCallback(event => { + const { name, value } = event.target + setAuthData(prev => ({ ...prev, [name]: value })) - } + setErrors(prev => ({ ...prev, [name]: '' })) + }, []) - const resetErrors = () => { - setErrors({ connectionName: '' }) - } + const handleAuthorize = useCallback(async () => { + const validationErrors = getValidationErrors(authType, authData) + setErrors(validationErrors) - const handleAuthorize = () => { - resetErrors() - if (!authData.connectionName) { - setErrors({ connectionName: __('Connection name is required', 'bit-integrations') }) + if (Object.keys(validationErrors).length > 0) { return } - if (authType === AUTH_TYPES.API_KEY && !authData.api_key) { - setErrors(prev => ({ ...prev, api_key: __('API key is required', 'bit-integrations') })) - return - } + const payload = getAuthPayload({ + authType, + apiEndpoint, + method, + authData, + authDetails + }) - if (authType === AUTH_TYPES.BASIC_AUTH && (!authData.username || !authData.password)) { - setErrors(prev => ({ - ...prev, - username: !authData.username ? __('Username is required', 'bit-integrations') : '', - password: !authData.password ? __('Password is required', 'bit-integrations') : '' - })) - return - } + setIsLoading(true) - if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token) { - setErrors(prev => ({ ...prev, token: __('Bearer token is required', 'bit-integrations') })) - return - } + try { + const authorizeRes = await authorizeConnection(payload) - let payload = { - auth_type: authType, - api_endpoint: apiEndpoint || '', - method: method || 'GET', - auth_details: {} - } + if (!authorizeRes?.success) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')}${ + authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` + ) + return + } - if (authType === AUTH_TYPES.API_KEY) { - payload.auth_details = { key: authDetails?.key || 'X-API-Key', value: authData.api_key, addTo: authData.addTo || 'header' } - } else if (authType === AUTH_TYPES.BASIC_AUTH) { - payload.auth_details = { username: authData.username, password: authData.password } - } else if (authType === AUTH_TYPES.BEARER_TOKEN) { - payload.auth_details = { token: authData.token } - } + const saveRes = await saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: authType, + connection_name: authData.connectionName, + account_name: authData.connectionName, + auth_details: payload.auth_details, + encrypt_keys: defaultEncryptKeys[authType] || [] + }) - setIsLoading(true) - authorizeConnection(payload).then(res => { - if (res?.success) { - saveConnection({ - app_slug: config?.app_slug || config?.type, - auth_type: authType, - connection_name: authData.connectionName, - account_name: authData.connectionName, - auth_details: payload.auth_details, - encrypt_keys: defaultEncryptKeys[authType] || [] - }).then(saveRes => { - if (saveRes?.success) { - const connection = saveRes?.data?.data || null - setConfig(prev => ({ ...prev, connection_id: connection?.id })) - setIsAuthorized(true) - setShowNextButton(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else { - toast.error( - `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' - }. ${__('please try again', 'bit-integrations')}` - ) - } - }) - } else { - setIsAuthorized(false) + if (!saveRes?.success) { toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${res?.data?.data || res?.data || 'Unknown error'}. ${__('please try again', 'bit-integrations')}` + `${__('Failed to save connection Cause:', 'bit-integrations')}${ + saveRes?.data?.data || saveRes?.data || '' + }. ${__('please try again', 'bit-integrations')}` ) + return } + + const connection = saveRes?.data?.data || null + setConfig(prev => ({ ...prev, connection_id: connection?.id })) + + if (onConnectionSaved) { + await onConnectionSaved(connection) + } + + setIsAuthorized(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + } catch (error) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')} ${ + error?.message || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` + ) + } finally { setIsLoading(false) - }) - } + } + }, [apiEndpoint, authData, authDetails, authType, config?.app_slug, config?.type, method, onConnectionSaved, setConfig]) return ( <> @@ -113,11 +169,11 @@ export default function AddNewConnection({ className="btcd-paper-inp w-6 mt-1" onChange={handleChange} name="connectionName" - value={authData?.connectionName || ''} + value={authData.connectionName || ''} type="text" placeholder={__('Connection Name...', 'bit-integrations')} /> -
    {errors?.connectionName}
    +
    {errors.connectionName || ''}
    {authType === AUTH_TYPES.API_KEY && ( <> @@ -128,40 +184,12 @@ export default function AddNewConnection({ className="btcd-paper-inp w-6 mt-1" onChange={handleChange} name="api_key" - value={authData.api_key ?? ''} + value={authData.api_key || ''} type="text" placeholder={__('api_key', 'bit-integrations')} disabled={isInfo} /> -
    {errors?.api_key || ''}
    - - {/*
    - {__('API Key Value:', 'bit-integrations')} -
    - -
    {errors.value || ''}
    */} - - {/*
    - {__('Send Key Via:', 'bit-integrations')} -
    - -
    {errors.addTo || ''}
    */} +
    {errors.api_key || ''}
    )} @@ -174,12 +202,12 @@ export default function AddNewConnection({ className="btcd-paper-inp w-6 mt-1" onChange={handleChange} name="username" - value={authData.username ?? ''} + value={authData.username || ''} type="text" placeholder={__('Username...', 'bit-integrations')} disabled={isInfo} /> -
    {errors?.username || ''}
    +
    {errors.username || ''}
    {__('Password:', 'bit-integrations')} @@ -188,12 +216,12 @@ export default function AddNewConnection({ className="btcd-paper-inp w-6 mt-1" onChange={handleChange} name="password" - value={authData.password ?? ''} + value={authData.password || ''} type="password" placeholder={__('Password...', 'bit-integrations')} disabled={isInfo} /> -
    {errors?.password || ''}
    +
    {errors.password || ''}
    )} @@ -206,12 +234,12 @@ export default function AddNewConnection({ className="btcd-paper-inp w-6 mt-1" onChange={handleChange} name="token" - value={authData.token ?? ''} + value={authData.token || ''} type="text" placeholder={__('Bearer token...', 'bit-integrations')} disabled={isInfo} /> -
    {errors?.token || ''}
    +
    {errors.token || ''}
    )} @@ -221,7 +249,7 @@ export default function AddNewConnection({ onClick={handleAuthorize} className="btn btcd-btn-lg purple mt-3 sh-sm flx" type="button" - disabled={isInfo}> + disabled={isInfo || isLoading}> {isAuthorized ? __('Authorized ✔', 'bit-integrations') : __('Authorize', 'bit-integrations')} {isLoading && } diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx index faeee1f20..808c4c9a7 100644 --- a/frontend/src/components/Connections/ConnectionAccountSelect.jsx +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -1,54 +1,74 @@ +import { useCallback, useMemo } from 'react' +import MultiSelect from 'react-multiple-select-dropdown-lite' import { useParams } from 'react-router' import { __ } from '../../Utils/i18nwrap' -import MultiSelect from 'react-multiple-select-dropdown-lite' import 'react-multiple-select-dropdown-lite/dist/index.css' const NEW_VALUE = '__new__' +const buildConnectionOption = conn => { + const accountName = conn.account_name || conn.connection_name + const hasDifferentNames = + conn.connection_name && conn.account_name && conn.connection_name !== conn.account_name + + return { + label: hasDifferentNames + ? `${conn.connection_name} (${accountName})` + : conn.connection_name || accountName, + value: String(conn.id) + } +} + export default function ConnectionAccountSelect({ config, setConfig, connections, setShowNewConnection, isInfo, + onRefresh, + isRefreshing = false }) { const { integUrlName } = useParams() - const handleChange = value => { - - if (value === NEW_VALUE) { - setShowNewConnection(true) - return - } - setShowNewConnection(false) - setConfig(prev => ({ ...prev, connection_id: value })) - } - const dropdownValue = config?.connection_id ? String(config.connection_id) : '' - const options = [ - ...(Array.isArray(connections) ? connections?.map(conn => { - const accountName = conn.account_name || conn.connection_name - const label = - conn.connection_name && conn.account_name && conn.connection_name !== conn.account_name - ? `${conn.connection_name} (${accountName})` - : conn.connection_name || accountName + const options = useMemo( + () => [ + ...(Array.isArray(connections) ? connections.map(buildConnectionOption) : []), + { label: __('+ Add new connection', 'bit-integrations'), value: NEW_VALUE } + ], + [connections] + ) + + const handleChange = useCallback( + value => { + const isNewConnection = value === NEW_VALUE + setShowNewConnection(isNewConnection) - return { - label, - value: String(conn.id) + if (isNewConnection) { + setConfig(prev => ({ ...prev, connection_id: '' })) + return } - }) : []), - { label: __('+ Add new connection', 'bit-integrations'), value: NEW_VALUE } - ] + + setConfig(prev => ({ ...prev, connection_id: value })) + }, + [setConfig, setShowNewConnection] + ) + + const connectionTitle = integUrlName + ? `${integUrlName} ${__('Connections:', 'bit-integrations')}` + : __('Connections:', 'bit-integrations') + const fetchConnections = onRefresh + const isLoading = isRefreshing return (
    - {integUrlName ? `${integUrlName} ${__('Connections:', 'bit-integrations')}` : __('Connections:', 'bit-integrations')} + {connectionTitle}
    + {!isInfo && fetchConnections && ( + + )}
    ) diff --git a/frontend/src/components/Connections/PlatformAuthorization.jsx b/frontend/src/components/Connections/PlatformAuthorization.jsx index 7c564d020..5061c7001 100644 --- a/frontend/src/components/Connections/PlatformAuthorization.jsx +++ b/frontend/src/components/Connections/PlatformAuthorization.jsx @@ -1,6 +1,4 @@ -import { useEffect, useState } from 'react' -import { useParams } from 'react-router' -import useFetch from '../../hooks/useFetch' +import { useCallback, useEffect, useMemo, useState } from 'react' import BackIcn from '../../Icons/BackIcn' import { listConnections } from '../../Utils/connectionApi' import { __ } from '../../Utils/i18nwrap' @@ -9,6 +7,9 @@ import TutorialLink from '../Utilities/TutorialLink' import AddNewConnection from './AddNewConnection' import ConnectionAccountSelect from './ConnectionAccountSelect' +const STEP_ONE_STYLE = { width: 900, height: 'auto' } +const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } + export default function PlatformAuthorization({ config, setConfig, @@ -21,44 +22,87 @@ export default function PlatformAuthorization({ authDetails = {}, customAuthFields }) { - const [showNextButton, setShowNextButton] = useState(false) const [errors, setErrors] = useState({ name: '' }) const [connections, setConnections] = useState([]) const [showNewConnection, setShowNewConnection] = useState(false) - const [isLoadingIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false) - const { integUrlName } = useParams() - // const { data, mutate } = useFetch({ - // action: 'connections/list', - // method: 'GET', - // params: { app_slug: integUrlName }, - // }) + const appSlug = config?.app_slug || config?.type + + const refreshConnections = useCallback(async () => { + if (!appSlug) { + setConnections([]) + setShowNewConnection(true) + return [] + } - useEffect(() => { setIsLoading(true) - listConnections(config?.app_slug || config?.type) - .then(res => { - if (res?.success && Array.isArray(res.data?.data)) { - const savedConnections = res.data.data - setConnections(savedConnections) - } else { - setShowNewConnection(true) - } - }) - .finally(() => setIsLoading(false)) - }, []) - - const handleChange = event => { - const { name, value } = event.target - - setConfig(prev => ({ ...prev, [name]: value })) - } + + try { + const res = await listConnections(appSlug) + const savedConnections = + res?.success && Array.isArray(res?.data?.data) ? res.data.data : [] + + setConnections(savedConnections) + setShowNewConnection(savedConnections.length === 0) + return savedConnections + } catch { + return [] + } finally { + setIsLoading(false) + } + }, [appSlug]) + + useEffect(() => { + refreshConnections() + }, [appSlug]) + + const handleNameChange = useCallback( + event => { + const { name, value } = event.target + setConfig(prev => ({ ...prev, [name]: value })) + + if (name === 'name') { + setErrors(prev => ({ ...prev, name: '' })) + } + }, + [setConfig] + ) + + const handleNext = useCallback(() => { + if (!config?.name?.trim()) { + setErrors({ name: __('Integration name is required', 'bit-integrations') }) + return + } + + setStep(2) + }, [config?.name, setStep]) + + const canGoNext = Boolean(config?.connection_id) + + const pageStyle = useMemo(() => (step === 1 ? STEP_ONE_STYLE : undefined), [step]) + + const handleConnectionSaved = useCallback(async savedConnection => { + const refreshedConnections = await refreshConnections() + const savedConnectionId = savedConnection?.id + + if (savedConnectionId) { + const matchedConnection = refreshedConnections.find( + conn => String(conn.id) === String(savedConnectionId) + ) + const selectedConnectionId = matchedConnection?.id || savedConnectionId + setConfig(prev => ({ ...prev, connection_id: selectedConnectionId })) + setShowNewConnection(false) + return + } + + if (refreshedConnections.length > 0) { + setShowNewConnection(false) + } + }, [refreshConnections, setConfig]) return ( -
    +
    {tutorialTitle && }
    @@ -66,120 +110,41 @@ export default function PlatformAuthorization({
    -
    {errors?.name}
    +
    {errors.name || ''}
    {showNewConnection && !isInfo && ( - <> - - - {/* {showAuthTypeSelector && ( - <> -
    - {__('Authorization Type:', 'bit-integrations')} -
    - - - )} */} - - {/* {shouldShowDefaultCredentialFields && ( - <> -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - {docsUrl && ( - - {docsPrompt}{' '} - - {docsText || docsUrl} - - - )} - -
    - {clientIdLabel} -
    - -
    {clientIdError}
    - -
    - {clientSecretLabel} -
    - -
    {clientSecretError}
    - - )} */} - - - + )} - {showNextButton && ( + {!isInfo && canGoNext && ( @@ -192,9 +157,9 @@ export default function PlatformAuthorization({ note={noteDetails?.note} isInstructional={noteDetails?.isInstructional || false} isHeadingNull={noteDetails?.isHeadingNull || false} - maxWidth={noteDetails?.maxWidth || '450px'} - children={noteDetails?.children || null} - /> + maxWidth={noteDetails?.maxWidth || '450px'}> + {noteDetails?.children || null} + )}
    ) From 7bbef3ebdf8a0f06502b50ad26eb606ce0b97a16 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 16:33:32 +0600 Subject: [PATCH 08/58] feat: refactor SystemeIOAuthorization to use PlatformAuthorization component --- .../SystemeIO/SystemeIOAuthorization.jsx | 130 +++--------------- .../Connections/AddNewConnection.jsx | 9 +- 2 files changed, 24 insertions(+), 115 deletions(-) diff --git a/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOAuthorization.jsx b/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOAuthorization.jsx index 81f8f27d6..be1e27a08 100644 --- a/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOAuthorization.jsx @@ -1,46 +1,17 @@ /* 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 Note from '../../Utilities/Note' -import { systemeIOAuthentication, getAllTags, getAllFields } from './SystemeIOCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function SystemeIOAuthorization({ systemeIOConf, setSystemeIOConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) -const [error, setError] = useState({ api_key: '' }) - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - getAllFields(systemeIOConf, setSystemeIOConf, setLoading) - getAllTags(systemeIOConf, setSystemeIOConf, setLoading) - - !systemeIOConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...systemeIOConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSystemeIOConf(newConf) - } - - const ActiveInstructions = ` + const activeInstructions = `

    ${__('To Get API Key & API Secret', 'bit-integrations')}

    • ${__('First go to your SystemeIO dashboard.', 'bit-integrations')}
    • @@ -54,82 +25,23 @@ const [error, setError] = useState({ api_key: '' })
    ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('API Key:', 'bit-integrations')} -
    - -
    {error.api_key}
    - - - {__('To Get API Key & API Secret, Please Visit', 'bit-integrations')} -   - - {__('SystemeIO API Key & Secret', 'bit-integrations')} - - -
    -
    - - {!isInfo && ( -
    - -
    - -
    - )} - -
    + ) } diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 3d220e77b..832cc6f83 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -114,8 +114,7 @@ export default function AddNewConnection({ if (!authorizeRes?.success) { setIsAuthorized(false) toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' + `${__('Authorization failed Cause:', 'bit-integrations')}${authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' }. ${__('please try again', 'bit-integrations')}` ) return @@ -132,8 +131,7 @@ export default function AddNewConnection({ if (!saveRes?.success) { toast.error( - `${__('Failed to save connection Cause:', 'bit-integrations')}${ - saveRes?.data?.data || saveRes?.data || '' + `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' }. ${__('please try again', 'bit-integrations')}` ) return @@ -151,8 +149,7 @@ export default function AddNewConnection({ } catch (error) { setIsAuthorized(false) toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')} ${ - error?.message || 'Unknown error' + `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error' }. ${__('please try again', 'bit-integrations')}` ) } finally { From ba3acccf84c40836b2729da7dfd88313fc91833f Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Thu, 7 May 2026 18:01:13 +0600 Subject: [PATCH 09/58] feat: enhance authorization handling with SSL verification options and additional headers normalization --- .../AbstractBaseAuthorization.php | 58 +++++++++++++++++-- .../OAuth2/OAuth2Authorization.php | 17 +++++- .../Connections/AddNewConnection.jsx | 31 ++++++++-- .../Connections/PlatformAuthorization.jsx | 4 ++ 4 files changed, 100 insertions(+), 10 deletions(-) diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index e20de6324..c0c94a03c 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -69,7 +69,8 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload $headers = array_merge($headers, $authData); } - $response = $this->sendRequest($url, $method === '' ? 'GET' : $method, $payload, $headers); + $requestOptions = $this->buildRequestOptionsFromAuthDetails(); + $response = $this->sendRequest($url, $method === '' ? 'GET' : $method, $payload, $headers, $requestOptions); if (is_wp_error($response)) { return [ @@ -250,20 +251,67 @@ protected function http() return new HttpHelper(); } - protected function sendRequest(string $url, string $method, $payload, array $headers) + protected function sendRequest(string $url, string $method, $payload, array $headers, array $options = []) { switch ($method) { case 'GET': - return HttpHelper::get($url, $payload, $headers); + return HttpHelper::get($url, $payload, $headers, $options); case 'POST': - return HttpHelper::post($url, $payload, $headers); + return HttpHelper::post($url, $payload, $headers, $options); default: - return HttpHelper::request($url, $method, $payload, $headers); + return HttpHelper::request($url, $method, $payload, $headers, $options); } } + protected function buildRequestOptionsFromAuthDetails(): array + { + $authDetails = $this->getAuthDetails(); + + if (!\is_array($authDetails)) { + return []; + } + + $sslVerify = $this->normalizeSslVerifyOption($authDetails['ssl_verify'] ?? null); + + if ($sslVerify === null) { + return []; + } + + return [ + // WordPress HTTP API option + 'sslverify' => $sslVerify, + // Kept for compatibility with existing code paths using "verify" + 'verify' => $sslVerify, + ]; + } + + protected function normalizeSslVerifyOption($value): ?bool + { + if (\is_bool($value)) { + return $value; + } + + if (\is_int($value)) { + return $value !== 0; + } + + if (\is_string($value)) { + $normalized = strtolower(trim($value)); + + if (\in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { + return true; + } + + if (\in_array($normalized, ['0', 'false', 'no', 'off'], true)) { + return false; + } + } + + return null; + } + protected static function getNestedValue(array $data, string $path) { if ($path === '') { diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index 6e3b87568..9c6a5a488 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -104,7 +104,22 @@ public function refreshAccessToken(array $authDetails) $body = $this->bodyParams ?: $this->buildRefreshBody($authDetails); - $response = HttpHelper::post($url, $body, ['Content-Type' => 'application/x-www-form-urlencoded']); + $requestOptions = []; + $sslVerify = $this->normalizeSslVerifyOption($authDetails['ssl_verify'] ?? null); + + if ($sslVerify !== null) { + $requestOptions = [ + 'sslverify' => $sslVerify, + 'verify' => $sslVerify, + ]; + } + + $response = HttpHelper::post( + $url, + $body, + ['Content-Type' => 'application/x-www-form-urlencoded'], + $requestOptions + ); if (HttpHelper::$responseCode !== 200 || (\is_object($response) && isset($response->error))) { return [ diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 832cc6f83..44393982d 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -7,19 +7,40 @@ import LoaderSm from '../Loaders/LoaderSm' const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } +const normalizeAdditionalHeaders = headers => { + if (!headers || typeof headers !== 'object') { + return {} + } + + return Object.entries(headers).reduce((acc, [key, value]) => { + const normalizedKey = String(key || '').trim() + const normalizedValue = value == null ? '' : String(value).trim() + + if (normalizedKey && normalizedValue) { + acc[normalizedKey] = normalizedValue + } + + return acc + }, {}) +} + const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { + const additionalHeaders = normalizeAdditionalHeaders(authDetails?.headers) + const sslVerify = authDetails?.ssl_verify !== false const basePayload = { auth_type: authType, api_endpoint: apiEndpoint || '', method: method || 'GET', - auth_details: {} + auth_details: {}, + headers: additionalHeaders } if (authType === AUTH_TYPES.API_KEY) { basePayload.auth_details = { key: authDetails?.key || 'X-API-Key', value: authData.api_key, - addTo: authData.addTo || 'header' + addTo: authData.addTo || 'header', + ssl_verify: sslVerify } return basePayload } @@ -27,14 +48,16 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } if (authType === AUTH_TYPES.BASIC_AUTH) { basePayload.auth_details = { username: authData.username, - password: authData.password + password: authData.password, + ssl_verify: sslVerify } return basePayload } if (authType === AUTH_TYPES.BEARER_TOKEN) { basePayload.auth_details = { - token: authData.token + token: authData.token, + ssl_verify: sslVerify } } diff --git a/frontend/src/components/Connections/PlatformAuthorization.jsx b/frontend/src/components/Connections/PlatformAuthorization.jsx index 5061c7001..12ffe7da0 100644 --- a/frontend/src/components/Connections/PlatformAuthorization.jsx +++ b/frontend/src/components/Connections/PlatformAuthorization.jsx @@ -18,6 +18,7 @@ export default function PlatformAuthorization({ isInfo, tutorialTitle, tutorialLinks, + extraFields, noteDetails = undefined, authDetails = {}, customAuthFields @@ -119,6 +120,7 @@ export default function PlatformAuthorization({ />
    {errors.name || ''}
    + + {showNewConnection && !isInfo && (extraFields || null)} + {showNewConnection && !isInfo && ( Date: Fri, 8 May 2026 13:04:23 +0600 Subject: [PATCH 10/58] feat: implement OAuth2 connection flow with token exchange and UI components --- backend/Config.php | 17 +- backend/Routes/ajax.php | 1 + backend/controller/ConnectionController.php | 85 +++++ frontend/src/Utils/connectionApi.js | 2 + frontend/src/Utils/connectionAuth.js | 2 +- frontend/src/Utils/oauthHelper.js | 175 +++++++++ .../Connections/AddNewConnection.jsx | 11 +- .../Connections/ConnectionAccountSelect.jsx | 18 +- .../Connections/Oauth2Connection.jsx | 335 ++++++++++++++++++ frontend/src/pages/AuthResponse.jsx | 11 +- 10 files changed, 637 insertions(+), 20 deletions(-) create mode 100644 frontend/src/Utils/oauthHelper.js create mode 100644 frontend/src/components/Connections/Oauth2Connection.jsx diff --git a/backend/Config.php b/backend/Config.php index 63c077e35..128b32b76 100644 --- a/backend/Config.php +++ b/backend/Config.php @@ -42,6 +42,8 @@ class Config */ public static function get($type, $default = null) { + global $wp_rewrite; + switch ($type) { case 'MAIN_FILE': return BIT_INTEGRATIONS_PLUGIN_FILE; @@ -68,6 +70,12 @@ public static function get($type, $default = null) case 'API_URL': return get_rest_url(null, '/' . self::SLUG . '/v1'); + case 'WP_API_URL': + return [ + 'base' => get_rest_url(), + 'separator' => $wp_rewrite->permalink_structure ? '?' : '&', + ]; + case 'ROOT_URI': return set_url_scheme(plugins_url('', self::get('MAIN_FILE')), wp_parse_url(home_url())['scheme']); @@ -183,14 +191,15 @@ public static function getFrontendConfig() { $frontendConfig = apply_filters( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- hook is prefixed via Config::VAR_PREFIX. - Config::withPrefix('localized_script'), + self::withPrefix('localized_script'), [ - 'nonce' => wp_create_nonce(Config::withPrefix('nonce')), - 'assetsURL' => Config::get('ASSET_URI'), + 'nonce' => wp_create_nonce(self::withPrefix('nonce')), + 'assetsURL' => self::get('ASSET_URI'), 'baseURL' => get_admin_url(null, 'admin.php?page=bit-integrations#'), 'siteURL' => site_url(), 'ajaxURL' => admin_url('admin-ajax.php'), - 'api' => Config::get('API_URL'), + 'api' => self::get('API_URL'), + 'wp_api_url' => self::get('WP_API_URL'), 'dateFormat' => get_option('date_format'), 'timeFormat' => get_option('time_format'), 'timeZone' => DateTimeHelper::wp_timezone_string(), diff --git a/backend/Routes/ajax.php b/backend/Routes/ajax.php index a44be705e..ddb751277 100644 --- a/backend/Routes/ajax.php +++ b/backend/Routes/ajax.php @@ -67,4 +67,5 @@ Route::post('connections/save', [ConnectionController::class, 'save']); Route::post('connections/update', [ConnectionController::class, 'update']); Route::post('connections/reauthorize', [ConnectionController::class, 'reauthorize']); +Route::post('connections/oauth2/exchange', [ConnectionController::class, 'oauth2Exchange']); Route::post('connections/delete', [ConnectionController::class, 'delete']); diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 3df377e5f..ca276637a 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -11,6 +11,7 @@ use BitApps\Integrations\Core\Database\ConnectionModel; use BitApps\Integrations\Core\Util\Capabilities; use BitApps\Integrations\Core\Util\Hash; +use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; final class ConnectionController @@ -287,6 +288,90 @@ public function delete($request) wp_send_json_success(['id' => $id]); } + /** + * Server-side OAuth2 token exchange (auth_code, pkce, client_credentials, refresh_token). + * Browsers cannot reach token endpoints (no CORS) and must not hold client_secret. + */ + public function oauth2Exchange($request) + { + $this->guardWrite(); + + $url = esc_url_raw((string) ($request->url ?? '')); + $method = strtoupper($this->sanitizeScalar($request->method ?? 'POST')); + $bodyParams = $this->normalizeArray($request->body_params ?? []); + $headers = $this->normalizeHeaders($request->headers ?? []); + $sslVerify = $this->normalizeSslVerifyOption($request->ssl_verify ?? null); + + if ($url === '') { + wp_send_json_error(__('Token URL is required', 'bit-integrations')); + } + + if (!isset($headers['Content-Type']) && !isset($headers['content-type'])) { + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + $options = []; + + if ($sslVerify !== null) { + $options['sslverify'] = $sslVerify; + $options['verify'] = $sslVerify; + } + + $contentType = strtolower($headers['Content-Type'] ?? ($headers['content-type'] ?? '')); + $isJson = strpos($contentType, 'application/json') !== false; + // form-urlencoded: pass array, WP will http_build_query. JSON: encode. Default array. + $payload = $isJson ? wp_json_encode($bodyParams) : $bodyParams; + + $response = $method === 'GET' + ? HttpHelper::get($url, $bodyParams, $headers, $options) + : HttpHelper::request($url, $method === '' ? 'POST' : $method, $payload, $headers, $options); + + if (is_wp_error($response)) { + wp_send_json_error($response->get_error_message(), 400); + } + + $responseCode = isset(HttpHelper::$responseCode) ? (int) HttpHelper::$responseCode : 0; + $decoded = \is_object($response) ? json_decode(wp_json_encode($response), true) : $response; + + if ($responseCode < 200 || $responseCode >= 300 || (\is_array($decoded) && isset($decoded['error']))) { + wp_send_json_error( + [ + 'message' => \is_array($decoded) && isset($decoded['error_description']) ? $decoded['error_description'] : (\is_array($decoded) && isset($decoded['error']) ? (\is_string($decoded['error']) ? $decoded['error'] : 'Token exchange failed') : 'Token exchange failed'), + 'response' => $decoded, + 'status' => $responseCode, + ], + 400 + ); + } + + wp_send_json_success(['data' => $decoded]); + } + + private function normalizeSslVerifyOption($value): ?bool + { + if (\is_bool($value)) { + return $value; + } + + if (\is_int($value)) { + return $value !== 0; + } + + if (\is_string($value)) { + $normalized = strtolower(trim($value)); + + if (\in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { + return true; + } + + if (\in_array($normalized, ['0', 'false', 'no', 'off'], true)) { + return false; + } + } + + return null; + } + private function buildPayload($request, bool $isUpdate) { $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index 7b9efa259..0b19694b1 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -12,6 +12,8 @@ export const getConnection = id => bitsFetch(null, 'connections/get', { id }, 'G export const authorizeConnection = payload => bitsFetch(payload, 'connections/authorize') +export const oauthConnectionExchange = payload => bitsFetch(payload, 'connections/oauth2/exchange') + export const saveConnection = payload => bitsFetch(payload, 'connections/save') export const updateConnection = payload => bitsFetch(payload, 'connections/update') diff --git a/frontend/src/Utils/connectionAuth.js b/frontend/src/Utils/connectionAuth.js index 54d52e639..94778929b 100644 --- a/frontend/src/Utils/connectionAuth.js +++ b/frontend/src/Utils/connectionAuth.js @@ -13,7 +13,7 @@ export const defaultEncryptKeys = { [AUTH_TYPES.API_KEY]: ['value'], [AUTH_TYPES.BASIC_AUTH]: ['password'], [AUTH_TYPES.BEARER_TOKEN]: ['token'], - [AUTH_TYPES.OAUTH2]: ['client_id', 'client_secret', 'access_token', 'refresh_token'] + [AUTH_TYPES.OAUTH2]: ['client_secret', 'access_token', 'refresh_token'] } export const isNoAuthType = authType => authType === AUTH_TYPES.NO_AUTH diff --git a/frontend/src/Utils/oauthHelper.js b/frontend/src/Utils/oauthHelper.js new file mode 100644 index 000000000..47a9221e6 --- /dev/null +++ b/frontend/src/Utils/oauthHelper.js @@ -0,0 +1,175 @@ +import { APP_CONFIG } from '../config/app' +import { oauthConnectionExchange } from './connectionApi' + +const OAUTH_CHANNEL = 'bit_integrations_oauth_share' +const PKCE_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~' + +const base64UrlEncode = bytes => { + let str = '' + for (let i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i]) + return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') +} + +export const generateCodeVerifier = (length = 64) => { + let result = '' + for (let i = 0; i < length; i++) { + result += PKCE_CHARSET.charAt(Math.floor(Math.random() * PKCE_CHARSET.length)) + } + return result +} + +export const generateCodeChallengeS256 = async codeVerifier => { + const data = new TextEncoder().encode(codeVerifier) + const digest = await window.crypto.subtle.digest('SHA-256', data) + return base64UrlEncode(new Uint8Array(digest)) +} + +export const getRedirectUri = () => { + const api = (APP_CONFIG?.api || '').replace(/\/+$/, '') + return `${api}/redirect` +} + +export const getCallbackState = () => { + const baseURL = APP_CONFIG?.baseURL || '' + return `${baseURL}/auth-response/` +} + +const appendQueryParam = (url, key, value) => { + url.searchParams.append(key, String(value)) +} + +export const buildAuthUrl = (authCodeEndpoint, { state, redirectUri, extraParams = {} }) => { + const url = new URL(authCodeEndpoint.url) + const queryParams = authCodeEndpoint.queryParams || {} + + Object.entries(queryParams).forEach(([key, value]) => appendQueryParam(url, key, value)) + Object.entries(extraParams).forEach(([key, value]) => appendQueryParam(url, key, value)) + url.searchParams.append('redirect_uri', redirectUri) + url.searchParams.append('state', state) + + return url.toString() +} + +export const openOauthPopup = (authUrl, label = 'OAuth') => + new Promise(resolve => { + const popup = window.open(authUrl, label, 'width=500,height=650,toolbar=off') + + if (!popup) { + resolve({ error: 'popup_blocked' }) + return + } + + let resolved = false + const channel = new BroadcastChannel(OAUTH_CHANNEL) + + const cleanup = () => { + channel.close() + clearInterval(closeTimer) + } + + channel.onmessage = event => { + if (resolved) return + resolved = true + cleanup() + try { popup.close() } catch (_) {} // eslint-disable-line no-unused-vars, no-empty + resolve(event.data || {}) + } + + const closeTimer = setInterval(() => { + if (popup.closed && !resolved) { + resolved = true + cleanup() + resolve({ error: 'popup_closed' }) + } + }, 800) + }) + +export const broadcastAuthCodeResponse = response => { + const channel = new BroadcastChannel(OAUTH_CHANNEL) + channel.postMessage(response) + setTimeout(() => channel.close(), 200) +} + +export const readAuthResponseFromUrl = () => { + const response = {} + const search = new URLSearchParams(window.location.search) + for (const [key, value] of search) if (value) response[key] = value + + // backend redirect appends "&code=...&state=..." after the hash route + const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, '')) + for (const [key, value] of hashParams) if (value) response[key] = value + + return response +} + +const buildClientAuthHeaders = ({ clientId, clientSecret, clientAuthentication }) => { + if (clientAuthentication === 'header') { + return { Authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}` } + } + return {} +} + +const buildClientAuthBody = ({ clientId, clientSecret, clientAuthentication }) => { + if (clientAuthentication === 'header') return {} + return { client_id: clientId, client_secret: clientSecret } +} + +export const exchangeAuthCodeForToken = ({ + tokenEndpoint, + clientId, + clientSecret, + clientAuthentication = 'body', + code, + codeVerifier, + redirectUri, + sslVerify = true +}) => { + const bodyParams = { + grant_type: 'authorization_code', + code: decodeURIComponent(code), + redirect_uri: redirectUri, + ...(tokenEndpoint?.bodyParams || {}), + ...buildClientAuthBody({ clientId, clientSecret, clientAuthentication }) + } + + if (codeVerifier) bodyParams.code_verifier = codeVerifier + + return oauthConnectionExchange({ + url: tokenEndpoint.url, + method: tokenEndpoint.method || 'POST', + body_params: bodyParams, + headers: { + ...(tokenEndpoint?.headers || {}), + ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) + }, + ssl_verify: sslVerify + }) +} + +export const exchangeClientCredentialsForToken = ({ + tokenEndpoint, + clientId, + clientSecret, + clientAuthentication = 'body', + scope, + sslVerify = true +}) => { + const bodyParams = { + grant_type: 'client_credentials', + ...(tokenEndpoint?.bodyParams || {}), + ...buildClientAuthBody({ clientId, clientSecret, clientAuthentication }) + } + + if (scope) bodyParams.scope = scope + + return oauthConnectionExchange({ + url: tokenEndpoint.url, + method: tokenEndpoint.method || 'POST', + body_params: bodyParams, + headers: { + ...(tokenEndpoint?.headers || {}), + ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) + }, + ssl_verify: sslVerify + }) +} diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 44393982d..770fa20c5 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -4,6 +4,7 @@ import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' import { __ } from '../../Utils/i18nwrap' import LoaderSm from '../Loaders/LoaderSm' +import Oauth2Connection from './Oauth2Connection' const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } @@ -92,7 +93,15 @@ const getValidationErrors = (authType, authData) => { return nextErrors } -export default function AddNewConnection({ +export default function AddNewConnection(props) { + if (props?.authDetails?.authType === AUTH_TYPES.OAUTH2) { + return + } + + return +} + +function CredentialAuthorizeForm({ authDetails, config, setConfig, diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx index 808c4c9a7..9e9ee9159 100644 --- a/frontend/src/components/Connections/ConnectionAccountSelect.jsx +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -30,7 +30,7 @@ export default function ConnectionAccountSelect({ }) { const { integUrlName } = useParams() - const dropdownValue = config?.connection_id ? String(config.connection_id) : '' + const dropdownValue = config?.connection_id ? String(config.connection_id) : NEW_VALUE const options = useMemo( () => [ @@ -81,14 +81,14 @@ export default function ConnectionAccountSelect({ /> {!isInfo && fetchConnections && ( + type="button" + className="icn-btn sh-sm tooltip" + style={{ '--tooltip-txt': `'${__('Refresh connections', 'bit-integrations')}'` }} + onClick={fetchConnections} + disabled={isLoading} + aria-label={__('Refresh connections', 'bit-integrations')}> + ↻ + )}
    diff --git a/frontend/src/components/Connections/Oauth2Connection.jsx b/frontend/src/components/Connections/Oauth2Connection.jsx new file mode 100644 index 000000000..6d521d28e --- /dev/null +++ b/frontend/src/components/Connections/Oauth2Connection.jsx @@ -0,0 +1,335 @@ +import { useCallback, useMemo, useState } from 'react' +import toast from 'react-hot-toast' +import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' +import { saveConnection } from '../../Utils/connectionApi' +import { + buildAuthUrl, + exchangeAuthCodeForToken, + exchangeClientCredentialsForToken, + generateCodeChallengeS256, + generateCodeVerifier, + getCallbackState, + getRedirectUri, + openOauthPopup +} from '../../Utils/oauthHelper' +import { __ } from '../../Utils/i18nwrap' +import LoaderSm from '../Loaders/LoaderSm' +import CopyText from '../Utilities/CopyText' +import { APP_CONFIG } from '../../config/app' + +const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } +const READONLY_INPUT_STYLE = { backgroundColor: '#f5f5f5' } + +const GRANT_TYPES = Object.freeze({ + AUTHORIZATION_CODE: 'authorization_code', + AUTHORIZATION_CODE_PKCE: 'authorization_code_pkce', + CLIENT_CREDENTIALS: 'client_credentials' +}) + +const buildSavedAuthDetails = ({ + tokenResponse, + clientId, + clientSecret, + clientAuthentication, + grantType, + refreshTokenUrl, + tokenUrl, + scope, + sslVerify +}) => { + const expiresIn = Number(tokenResponse?.expires_in) || 0 + const persistedGrantType = + grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE ? GRANT_TYPES.AUTHORIZATION_CODE : grantType + + return { + access_token: tokenResponse?.access_token || '', + refresh_token: tokenResponse?.refresh_token || '', + token_type: tokenResponse?.token_type || 'Bearer', + expires_in: expiresIn, + generated_at: Math.floor(Date.now() / 1000), + client_id: clientId, + client_secret: clientSecret, + clientAuthentication, + grant_type: persistedGrantType, + refresh_token_url: refreshTokenUrl || tokenUrl, + scope: scope || '', + ssl_verify: sslVerify !== false, + raw_response: tokenResponse + } +} + +export default function Oauth2Connection({ + authDetails, + config, + setConfig, + isInfo = false, + customAuthFields, + onConnectionSaved +}) { + const [formData, setFormData] = useState({}) + const [errors, setErrors] = useState({}) + const [isLoading, setIsLoading] = useState(false) + const [isAuthorized, setIsAuthorized] = useState(false) + + const { + authCodeEndpoint, + tokenEndpoint, + refreshTokenUrl, + grantType = GRANT_TYPES.AUTHORIZATION_CODE, + clientAuthentication = 'body', + scope, + sslVerify = true + } = authDetails || {} + + const redirectUri = useMemo(() => getRedirectUri(), []) + + const handleChange = useCallback(event => { + const { name, value } = event.target + setFormData(prev => ({ ...prev, [name]: value })) + setErrors(prev => ({ ...prev, [name]: '' })) + }, []) + + const validate = useCallback(() => { + const next = {} + if (!formData.connectionName?.trim()) { + next.connectionName = __('Connection name is required', 'bit-integrations') + } + if (!formData.clientId?.trim()) { + next.clientId = __('Client ID is required', 'bit-integrations') + } + if (!formData.clientSecret?.trim()) { + next.clientSecret = __('Client secret is required', 'bit-integrations') + } + setErrors(next) + return Object.keys(next).length === 0 + }, [formData]) + + const storeConnection = useCallback( + async authPayload => { + const saveRes = await saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: AUTH_TYPES.OAUTH2, + connection_name: formData.connectionName, + account_name: formData.connectionName, + auth_details: authPayload, + encrypt_keys: defaultEncryptKeys[AUTH_TYPES.OAUTH2] || [] + }) + + if (!saveRes?.success) { + const reason = saveRes?.data?.data || saveRes?.data || '' + toast.error(`${__('Failed to save connection Cause:', 'bit-integrations')}${reason}`) + return null + } + + const connection = saveRes?.data?.data || null + setConfig(prev => ({ ...prev, connection_id: connection?.id })) + + if (onConnectionSaved) await onConnectionSaved(connection) + + setIsAuthorized(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + return connection + }, + [config, formData.connectionName, onConnectionSaved, setConfig] + ) + + const handleAuthorizationCodeFlow = useCallback(async () => { + const isPkce = grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE + let codeVerifier + const extraParams = { ...(authCodeEndpoint?.queryParams?.client_id ? {} : { client_id: formData.clientId }) } + + if (!authCodeEndpoint?.queryParams?.response_type) extraParams.response_type = 'code' + if (scope && !authCodeEndpoint?.queryParams?.scope) extraParams.scope = scope + + if (isPkce) { + codeVerifier = generateCodeVerifier() + extraParams.code_challenge = await generateCodeChallengeS256(codeVerifier) + extraParams.code_challenge_method = 'S256' + } + + const populatedAuthCodeEndpoint = { + ...authCodeEndpoint, + queryParams: { + ...(authCodeEndpoint?.queryParams || {}), + ...(authCodeEndpoint?.queryParams?.client_id ? { client_id: formData.clientId } : {}) + } + } + + const state = getCallbackState() + const authUrl = buildAuthUrl(populatedAuthCodeEndpoint, { state, redirectUri, extraParams }) + const popupResponse = await openOauthPopup(authUrl, formData.connectionName || 'OAuth') + + if (popupResponse?.error) { + throw new Error( + popupResponse.error === 'popup_blocked' + ? __('Popup blocked. Please allow popups and try again.', 'bit-integrations') + : __('Authorization window closed before completing.', 'bit-integrations') + ) + } + + if (!popupResponse?.code) { + throw new Error(popupResponse?.error_description || __('Authorization code missing', 'bit-integrations')) + } + + const tokenRes = await exchangeAuthCodeForToken({ + tokenEndpoint, + clientId: formData.clientId, + clientSecret: formData.clientSecret, + clientAuthentication, + code: popupResponse.code, + codeVerifier, + redirectUri, + sslVerify + }) + + if (!tokenRes?.success) { + throw new Error(tokenRes?.data?.message || __('Token exchange failed', 'bit-integrations')) + } + + return tokenRes?.data?.data || {} + }, [authCodeEndpoint, clientAuthentication, formData, grantType, redirectUri, scope, sslVerify, tokenEndpoint]) + + const handleClientCredentialsFlow = useCallback(async () => { + const tokenRes = await exchangeClientCredentialsForToken({ + tokenEndpoint, + clientId: formData.clientId, + clientSecret: formData.clientSecret, + clientAuthentication, + scope, + sslVerify + }) + + if (!tokenRes?.success) { + throw new Error(tokenRes?.data?.message || __('Token exchange failed', 'bit-integrations')) + } + + return tokenRes?.data?.data || {} + }, [clientAuthentication, formData.clientId, formData.clientSecret, scope, sslVerify, tokenEndpoint]) + + const handleAuthorize = useCallback(async () => { + if (!validate()) return + + setIsLoading(true) + try { + let tokenResponse + if (grantType === GRANT_TYPES.CLIENT_CREDENTIALS) { + tokenResponse = await handleClientCredentialsFlow() + } else { + tokenResponse = await handleAuthorizationCodeFlow() + } + + const savedAuthDetails = buildSavedAuthDetails({ + tokenResponse, + clientId: formData.clientId, + clientSecret: formData.clientSecret, + clientAuthentication, + grantType, + refreshTokenUrl, + tokenUrl: tokenEndpoint?.url, + scope, + sslVerify + }) + + await storeConnection(savedAuthDetails) + } catch (error) { + setIsAuthorized(false) + toast.error(`${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}`) + } finally { + setIsLoading(false) + } + }, [ + clientAuthentication, + formData.clientId, + formData.clientSecret, + grantType, + handleAuthorizationCodeFlow, + handleClientCredentialsFlow, + storeConnection, + refreshTokenUrl, + scope, + sslVerify, + tokenEndpoint?.url, + validate + ]) + + const isAuthCodeFlow = + grantType === GRANT_TYPES.AUTHORIZATION_CODE || + grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE + + return ( + <> +
    + {__('Connection Name:', 'bit-integrations')} +
    + +
    {errors.connectionName || ''}
    + + {isAuthCodeFlow && ( + <> +
    + {__('Homepage URL:', 'bit-integrations')} +
    + +
    + {__('Callback / Redirect URL:', 'bit-integrations')} +
    + + + )} + +
    + {__('Client ID:', 'bit-integrations')} +
    + +
    {errors.clientId || ''}
    + +
    + {__('Client Secret:', 'bit-integrations')} +
    + +
    {errors.clientSecret || ''}
    + + {customAuthFields} + + + + ) +} diff --git a/frontend/src/pages/AuthResponse.jsx b/frontend/src/pages/AuthResponse.jsx index f2b195dac..19d45a0b2 100644 --- a/frontend/src/pages/AuthResponse.jsx +++ b/frontend/src/pages/AuthResponse.jsx @@ -1,21 +1,22 @@ import { useEffect } from 'react' import { useSetRecoilState } from 'recoil' import { authInfoAtom } from '../GlobalStates' +import { broadcastAuthCodeResponse, readAuthResponseFromUrl } from '../Utils/oauthHelper' // popup window: render when redirected from oauth to bit-integration with code export default function AuthResponse() { const setAuthInfo = useSetRecoilState(authInfoAtom) useEffect(() => { - const urlParams = new URLSearchParams(window.location.hash) - const code = urlParams.get('code') + const response = readAuthResponseFromUrl() - if (code) { - setAuthInfo({ code: code }) + if (Object.keys(response).length > 0) { + broadcastAuthCodeResponse(response) + if (response.code) setAuthInfo({ code: response.code }) setTimeout(() => { window.close() - }, 100) + }, 200) } }, []) From 497bb133e8aada435fc99138fe4d39f5c34fa68b Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 8 May 2026 14:41:26 +0600 Subject: [PATCH 11/58] feat: refactor error handling and JSON encoding in ConnectionController --- backend/controller/ConnectionController.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index ca276637a..f6dd5b78b 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -6,12 +6,14 @@ exit; } -use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Authorization\AuthorizationFactory; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Database\ConnectionModel; use BitApps\Integrations\Core\Util\Capabilities; use BitApps\Integrations\Core\Util\Hash; +use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; +use Exception; use WP_Error; final class ConnectionController @@ -246,7 +248,7 @@ public function authorize($request) $handler = AuthorizationFactory::getAuthorizationHandler($authType, 0, $appSlug); $handler->setAuthDetailsOverride($authDetails); $result = $handler->authorize($apiEndpoint, $method, $payload, $headers); - } catch (\Exception $e) { + } catch (Exception $e) { wp_send_json_error($e->getMessage()); } @@ -291,6 +293,8 @@ public function delete($request) /** * Server-side OAuth2 token exchange (auth_code, pkce, client_credentials, refresh_token). * Browsers cannot reach token endpoints (no CORS) and must not hold client_secret. + * + * @param mixed $request */ public function oauth2Exchange($request) { @@ -331,7 +335,7 @@ public function oauth2Exchange($request) } $responseCode = isset(HttpHelper::$responseCode) ? (int) HttpHelper::$responseCode : 0; - $decoded = \is_object($response) ? json_decode(wp_json_encode($response), true) : $response; + $decoded = \is_object($response) ? Helper::jsonEncodeDecode($response) : $response; if ($responseCode < 200 || $responseCode >= 300 || (\is_array($decoded) && isset($decoded['error']))) { wp_send_json_error( @@ -595,7 +599,7 @@ private function normalizeArray($value): array } if (\is_object($value)) { - return json_decode(wp_json_encode($value), true) ?: []; + return Helper::jsonEncodeDecode($value) ?: []; } if (\is_string($value) && $value !== '') { @@ -614,7 +618,7 @@ private function normalizePayload($value) } if (\is_object($value)) { - return json_decode(wp_json_encode($value), true) ?: []; + return Helper::jsonEncodeDecode($value) ?: []; } return $value; @@ -623,7 +627,7 @@ private function normalizePayload($value) private function normalizeHeaders($value): array { if (\is_object($value)) { - $value = json_decode(wp_json_encode($value), true) ?: []; + $value = Helper::jsonEncodeDecode($value) ?: []; } if (!\is_array($value)) { From 2facfd1d77ad7577c360627d87fdb216bdb26038 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 8 May 2026 17:21:08 +0600 Subject: [PATCH 12/58] feat: refactor authorization handling with shared AuthDataCodec for encryption, SSL verification, and nested data access --- .../AbstractBaseAuthorization.php | 179 ++++++---------- .../ApiKey/ApiKeyAuthorization.php | 2 +- .../OAuth2/OAuth2Authorization.php | 86 +++++--- .../Authorization/Support/AuthDataCodec.php | 121 +++++++++++ backend/Core/Util/Hash.php | 9 + backend/controller/ConnectionController.php | 199 ++++++------------ frontend/src/Utils/connectionAuth.js | 35 --- frontend/src/Utils/oauthHelper.js | 9 +- .../Connections/ConnectionAccountList.jsx | 2 +- .../Connections/Oauth2Connection.jsx | 21 +- 10 files changed, 333 insertions(+), 330 deletions(-) create mode 100644 backend/Authorization/Support/AuthDataCodec.php diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index c0c94a03c..dfd58083e 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -6,8 +6,8 @@ exit; } +use BitApps\Integrations\Authorization\Support\AuthDataCodec; use BitApps\Integrations\Core\Database\ConnectionModel; -use BitApps\Integrations\Core\Util\Hash; use BitApps\Integrations\Core\Util\HttpHelper; abstract class AbstractBaseAuthorization @@ -18,6 +18,8 @@ abstract class AbstractBaseAuthorization protected $authDetailsOverride; + protected $lastError; + public function __construct($connectionId) { $this->connectionId = (int) $connectionId; @@ -61,12 +63,21 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload $authLocation = $authConfig['authLocation'] ?? 'header'; $authData = (isset($authConfig['data']) && \is_array($authConfig['data'])) ? $authConfig['data'] : []; - if ($authLocation === 'query' && !empty($authData)) { - $query = http_build_query($authData); - $separator = strpos($url, '?') !== false ? '&' : '?'; - $url .= $separator . $query; - } elseif (!empty($authData)) { - $headers = array_merge($headers, $authData); + if (!empty($authData)) { + if ($authLocation === 'query') { + if ($method === 'GET') { + // HttpHelper::get appends $payload as query string. Merge auth data + // into $payload so a single, deduplicated query is emitted. Caller + // payload keys win on collision. + $payload = array_merge($authData, \is_array($payload) ? $payload : []); + } else { + $query = http_build_query($authData); + $separator = strpos($url, '?') !== false ? '&' : '?'; + $url .= $separator . $query; + } + } else { + $headers = array_merge($headers, $authData); + } } $requestOptions = $this->buildRequestOptionsFromAuthDetails(); @@ -107,6 +118,48 @@ public function getConnectionId(): int return (int) $this->connectionId; } + /** + * Region-aware providers (Zoho .com/.in/.eu, Salesforce instance_url, MailChimp dc-prefix) + * should persist their resolved API base in auth_details under `endpoint_base` or + * `api_domain`. RecordApiHelper reads it via $handler->getEndpointBase() to avoid + * scattering region logic across the codebase. Subclasses may override. + */ + public function getEndpointBase(): ?string + { + $details = $this->getAuthDetails(); + + if (!\is_array($details)) { + return null; + } + + foreach (['endpoint_base', 'api_domain', 'instance_url', 'apiDomain'] as $key) { + if (!empty($details[$key]) && \is_string($details[$key])) { + return rtrim($details[$key], '/'); + } + } + + return null; + } + + public function getLastError(): ?array + { + return $this->lastError; + } + + protected function setLastError(string $message, $response = null): void + { + $this->lastError = [ + 'error' => true, + 'message' => $message, + 'response' => $response, + ]; + } + + protected function clearLastError(): void + { + $this->lastError = null; + } + public function setAuthDetailsOverride(array $authDetails) { $this->authDetailsOverride = $authDetails; @@ -148,23 +201,9 @@ public function getAuthDetails() return $authDetails; } - $encryptKeys = $this->parseEncryptKeys($connection->encrypt_keys ?? ''); + $encryptKeys = AuthDataCodec::parseEncryptKeys($connection->encrypt_keys ?? ''); - if (empty($encryptKeys)) { - return $authDetails; - } - - foreach ($encryptKeys as $path) { - $value = self::getNestedValue($authDetails, $path); - - if (!\is_string($value) || $value === '') { - continue; - } - - self::setNestedValue($authDetails, $path, Hash::decrypt($value)); - } - - return $authDetails; + return AuthDataCodec::decryptValues($authDetails, $encryptKeys); } public function isTokenExpired($generatedAt, $expiresIn): bool @@ -184,17 +223,8 @@ public function updateAuthDetails(array $authDetails): bool return false; } - $encryptKeys = $this->parseEncryptKeys($connection->encrypt_keys ?? ''); - - foreach ($encryptKeys as $path) { - $value = self::getNestedValue($authDetails, $path); - - if (!\is_string($value) || $value === '') { - continue; - } - - self::setNestedValue($authDetails, $path, Hash::encrypt($value)); - } + $encryptKeys = AuthDataCodec::parseEncryptKeys($connection->encrypt_keys ?? ''); + $authDetails = AuthDataCodec::encryptValues($authDetails, $encryptKeys); $connectionModel = new ConnectionModel(); $result = $connectionModel->update( @@ -233,19 +263,6 @@ protected function decodeAuthDetails($value): array return []; } - protected function parseEncryptKeys($value): array - { - if (\is_array($value)) { - return array_values(array_filter(array_map('strval', $value))); - } - - if (\is_string($value) && $value !== '') { - return array_values(array_filter(array_map('trim', explode(',', $value)))); - } - - return []; - } - protected function http() { return new HttpHelper(); @@ -273,7 +290,7 @@ protected function buildRequestOptionsFromAuthDetails(): array return []; } - $sslVerify = $this->normalizeSslVerifyOption($authDetails['ssl_verify'] ?? null); + $sslVerify = AuthDataCodec::normalizeSslVerify($authDetails['ssl_verify'] ?? null); if ($sslVerify === null) { return []; @@ -287,74 +304,12 @@ protected function buildRequestOptionsFromAuthDetails(): array ]; } - protected function normalizeSslVerifyOption($value): ?bool - { - if (\is_bool($value)) { - return $value; - } - - if (\is_int($value)) { - return $value !== 0; - } - - if (\is_string($value)) { - $normalized = strtolower(trim($value)); - - if (\in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { - return true; - } - - if (\in_array($normalized, ['0', 'false', 'no', 'off'], true)) { - return false; - } - } - - return null; - } - - protected static function getNestedValue(array $data, string $path) - { - if ($path === '') { - return; - } - - $segments = explode('.', $path); - $cursor = $data; - - foreach ($segments as $segment) { - if (!\is_array($cursor) || !\array_key_exists($segment, $cursor)) { - return; - } - - $cursor = $cursor[$segment]; - } - - return $cursor; - } - - protected static function setNestedValue(array &$data, string $path, $value): void + private function loadConnection() { - if ($path === '') { + if ($this->connectionId <= 0) { return; } - $segments = explode('.', $path); - $last = array_pop($segments); - $cursor = &$data; - - foreach ($segments as $segment) { - if (!isset($cursor[$segment]) || !\is_array($cursor[$segment])) { - $cursor[$segment] = []; - } - - $cursor = &$cursor[$segment]; - } - - $cursor[$last] = $value; - } - - private function loadConnection() - { $connectionModel = new ConnectionModel(); $result = $connectionModel->get( ['id', 'app_slug', 'auth_type', 'connection_name', 'account_name', 'encrypt_keys', 'auth_details', 'status'], diff --git a/backend/Authorization/ApiKey/ApiKeyAuthorization.php b/backend/Authorization/ApiKey/ApiKeyAuthorization.php index 57ab5e330..25a21f037 100644 --- a/backend/Authorization/ApiKey/ApiKeyAuthorization.php +++ b/backend/Authorization/ApiKey/ApiKeyAuthorization.php @@ -35,7 +35,7 @@ public function getAuthHeadersOrParams() ]; } - $key = $authDetails['key'] ?? 'api_key'; + $key = $authDetails['key'] ?? 'X-API-Key'; $location = $authDetails['addTo'] ?? 'header'; return [ diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index 9c6a5a488..d1a1f20c1 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -7,6 +7,7 @@ } use BitApps\Integrations\Authorization\AbstractBaseAuthorization; +use BitApps\Integrations\Authorization\Support\AuthDataCodec; use BitApps\Integrations\Core\Util\HttpHelper; class OAuth2Authorization extends AbstractBaseAuthorization @@ -38,15 +39,16 @@ public function setTokenPrefix($prefix) return $this; } - public function getAuthDetails() + public function getAuthDetails(): ?array { + $this->clearLastError(); + $authDetails = parent::getAuthDetails(); if (empty($authDetails)) { - return [ - 'error' => true, - 'message' => 'Connection auth details are missing', - ]; + $this->setLastError(__('Connection auth details are missing', 'bit-integrations')); + + return null; } $generatedAt = $authDetails['generated_at'] ?? null; @@ -63,15 +65,17 @@ public function getAccessToken() { $authDetails = $this->getAuthDetails(); - if (isset($authDetails['error']) && $authDetails['error']) { - return $authDetails; + if ($authDetails === null) { + return $this->getLastError() ?: [ + 'error' => true, + 'message' => 'Connection auth details are missing', + ]; } if (empty($authDetails['access_token'])) { - return [ - 'error' => true, - 'message' => 'Access token is missing', - ]; + $this->setLastError(__('Access token is missing', 'bit-integrations')); + + return $this->getLastError(); } return $this->tokenPrefix . $authDetails['access_token']; @@ -91,21 +95,21 @@ public function getAuthHeadersOrParams() ]; } - public function refreshAccessToken(array $authDetails) + public function refreshAccessToken(array $authDetails): ?array { $url = $this->refreshTokenUrl ?: ($authDetails['refresh_token_url'] ?? ($authDetails['refreshTokenUrl'] ?? '')); if (empty($url)) { - return [ - 'error' => true, - 'message' => 'Refresh token endpoint is missing', - ]; + $this->setLastError(__('Refresh token endpoint is missing', 'bit-integrations')); + + return null; } $body = $this->bodyParams ?: $this->buildRefreshBody($authDetails); + $headers = $this->buildRefreshHeaders($authDetails); $requestOptions = []; - $sslVerify = $this->normalizeSslVerifyOption($authDetails['ssl_verify'] ?? null); + $sslVerify = AuthDataCodec::normalizeSslVerify($authDetails['ssl_verify'] ?? null); if ($sslVerify !== null) { $requestOptions = [ @@ -114,19 +118,13 @@ public function refreshAccessToken(array $authDetails) ]; } - $response = HttpHelper::post( - $url, - $body, - ['Content-Type' => 'application/x-www-form-urlencoded'], - $requestOptions - ); + $response = HttpHelper::post($url, $body, $headers, $requestOptions); if (HttpHelper::$responseCode !== 200 || (\is_object($response) && isset($response->error))) { - return [ - 'error' => true, - 'message' => \is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed', - 'response' => $response, - ]; + $message = \is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed'; + $this->setLastError((string) $message, $response); + + return null; } $response = \is_object($response) ? json_decode(wp_json_encode($response), true) : (array) $response; @@ -151,17 +149,41 @@ public function refreshAccessToken(array $authDetails) private function buildRefreshBody(array $authDetails): array { $grantType = $authDetails['grant_type'] ?? 'authorization_code'; - $body = [ - 'grant_type' => $grantType === 'client_credentials' ? 'client_credentials' : 'refresh_token', - 'client_id' => $authDetails['client_id'] ?? ($authDetails['clientId'] ?? ''), - 'client_secret' => $authDetails['client_secret'] ?? ($authDetails['clientSecret'] ?? ''), + 'grant_type' => $grantType === 'client_credentials' ? 'client_credentials' : 'refresh_token', ]; + // Body auth is the default. Header auth puts client credentials in Authorization + // header instead — see buildRefreshHeaders. + if ($this->resolveClientAuthMode($authDetails) !== 'header') { + $body['client_id'] = $authDetails['client_id'] ?? ($authDetails['clientId'] ?? ''); + $body['client_secret'] = $authDetails['client_secret'] ?? ($authDetails['clientSecret'] ?? ''); + } + if (!empty($authDetails['refresh_token'])) { $body['refresh_token'] = $authDetails['refresh_token']; } return $body; } + + private function buildRefreshHeaders(array $authDetails): array + { + $headers = ['Content-Type' => 'application/x-www-form-urlencoded']; + + if ($this->resolveClientAuthMode($authDetails) === 'header') { + $clientId = $authDetails['client_id'] ?? ($authDetails['clientId'] ?? ''); + $clientSecret = $authDetails['client_secret'] ?? ($authDetails['clientSecret'] ?? ''); + $headers['Authorization'] = 'Basic ' . base64_encode($clientId . ':' . $clientSecret); + } + + return $headers; + } + + private function resolveClientAuthMode(array $authDetails): string + { + $mode = $authDetails['clientAuthentication'] ?? ($authDetails['client_authentication'] ?? 'body'); + + return $mode === 'header' ? 'header' : 'body'; + } } diff --git a/backend/Authorization/Support/AuthDataCodec.php b/backend/Authorization/Support/AuthDataCodec.php new file mode 100644 index 000000000..6f05f6b1d --- /dev/null +++ b/backend/Authorization/Support/AuthDataCodec.php @@ -0,0 +1,121 @@ +guardRead(); + $this->guard(); $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); $condition = ['status' => ConnectionModel::STATUS_VERIFIED]; @@ -68,7 +68,7 @@ public function index($request) public function getById($request) { - $this->guardRead(); + $this->guard(); $id = $this->normalizeId($request); @@ -87,7 +87,7 @@ public function getById($request) public function save($request) { - $this->guardWrite(); + $this->guard(); $payload = $this->buildPayload($request, false); @@ -95,19 +95,20 @@ public function save($request) wp_send_json_error($payload->get_error_message()); } - // $existingId = $this->findExistingIdForAccount($payload['app_slug'], $payload['account_name']); + // Upsert policy: same (app_slug, account_name) is treated as the same connection + // and gets refreshed. Prevents accidental duplicates when re-authorizing the same + // account from a flow builder. Skipped when account_name is empty. + $existingId = $this->findExistingIdForAccount($payload['app_slug'], $payload['account_name']); - // if ($existingId > 0) { - // $payload['id'] = $existingId; - - // $updated = $this->persist($payload, $existingId); + if ($existingId > 0) { + $updated = $this->persist($payload, $existingId); - // if (is_wp_error($updated)) { - // wp_send_json_error($updated->get_error_message()); - // } + if (is_wp_error($updated)) { + wp_send_json_error($updated->get_error_message()); + } - // wp_send_json_success(['data' => $this->formatRow($updated)]); - // } + wp_send_json_success(['data' => $this->formatRow($updated)]); + } $created = $this->persist($payload, 0); @@ -120,7 +121,7 @@ public function save($request) public function update($request) { - $this->guardWrite(); + $this->guard(); $id = $this->normalizeId($request); @@ -166,7 +167,7 @@ public function update($request) public function reauthorize($request) { - $this->guardWrite(); + $this->guard(); $id = $this->normalizeId($request); @@ -213,7 +214,7 @@ public function reauthorize($request) public function authorize($request) { - $this->guardWrite(); + $this->guard(); $authType = $this->sanitizeScalar($request->auth_type ?? ''); $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); @@ -273,7 +274,7 @@ public function authorize($request) public function delete($request) { - $this->guardWrite(); + $this->guard(); $id = $this->normalizeId($request); @@ -298,18 +299,22 @@ public function delete($request) */ public function oauth2Exchange($request) { - $this->guardWrite(); + $this->guard(); $url = esc_url_raw((string) ($request->url ?? '')); $method = strtoupper($this->sanitizeScalar($request->method ?? 'POST')); $bodyParams = $this->normalizeArray($request->body_params ?? []); $headers = $this->normalizeHeaders($request->headers ?? []); - $sslVerify = $this->normalizeSslVerifyOption($request->ssl_verify ?? null); + $sslVerify = AuthDataCodec::normalizeSslVerify($request->ssl_verify ?? null); if ($url === '') { wp_send_json_error(__('Token URL is required', 'bit-integrations')); } + if (!$this->isPublicHttpsUrl($url)) { + wp_send_json_error(__('Token URL must be a public https endpoint', 'bit-integrations'), 400); + } + if (!isset($headers['Content-Type']) && !isset($headers['content-type'])) { $headers['Content-Type'] = 'application/x-www-form-urlencoded'; } @@ -351,31 +356,6 @@ public function oauth2Exchange($request) wp_send_json_success(['data' => $decoded]); } - private function normalizeSslVerifyOption($value): ?bool - { - if (\is_bool($value)) { - return $value; - } - - if (\is_int($value)) { - return $value !== 0; - } - - if (\is_string($value)) { - $normalized = strtolower(trim($value)); - - if (\in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { - return true; - } - - if (\in_array($normalized, ['0', 'false', 'no', 'off'], true)) { - return false; - } - } - - return null; - } - private function buildPayload($request, bool $isUpdate) { $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); @@ -406,6 +386,12 @@ private function buildPayload($request, bool $isUpdate) $connectionName = $accountName !== '' ? $accountName : $appSlug; } + // Backfill account_name so findExistingIdForAccount upsert key is never empty — + // otherwise re-authorize creates duplicate rows for the same logical account. + if ($accountName === '') { + $accountName = $connectionName; + } + return [ 'app_slug' => $appSlug, 'auth_type' => $authType !== '' ? $authType : AuthorizationType::OAUTH2, @@ -426,7 +412,7 @@ private function persist(array $payload, int $existingId) $authDetails['generated_at'] = time(); } - $authDetails = $this->encryptValues($authDetails, $encryptKeys); + $authDetails = AuthDataCodec::encryptValues($authDetails, $encryptKeys); $now = current_time('mysql'); @@ -465,34 +451,43 @@ private function persist(array $payload, int $existingId) return $this->findById((int) $insertId); } - private function encryptValues(array $authDetails, array $encryptKeys): array + /** + * Reject non-https URLs and hosts that resolve to private / loopback / reserved ranges. + * Token endpoints are always public https in practice; this closes SSRF surface for + * the server-side token exchange. + * + * Known-partial: literal IPs are filtered, but hostnames are NOT DNS-resolved here — + * a public hostname A-recording to a private IP would pass. Acceptable because the + * caller is an authenticated admin and the URL is supplied per-flow (not user-input + * from the public web). Add gethostbyname() screening if that threat model changes. + */ + private function isPublicHttpsUrl(string $url): bool { - foreach ($encryptKeys as $path) { - $value = $this->getNestedValue($authDetails, $path); - - if (!\is_string($value) || $value === '') { - continue; - } + $parts = wp_parse_url($url); - $this->setNestedValue($authDetails, $path, Hash::encrypt($value)); + if (!$parts || empty($parts['scheme']) || empty($parts['host'])) { + return false; } - return $authDetails; - } + if (strtolower($parts['scheme']) !== 'https') { + return false; + } - private function decryptValues(array $authDetails, array $encryptKeys): array - { - foreach ($encryptKeys as $path) { - $value = $this->getNestedValue($authDetails, $path); + $host = strtolower($parts['host']); - if (!\is_string($value) || $value === '') { - continue; - } + if (\in_array($host, ['localhost', 'localhost.localdomain', 'ip6-localhost', 'ip6-loopback'], true)) { + return false; + } - $this->setNestedValue($authDetails, $path, Hash::decrypt($value)); + if (filter_var($host, FILTER_VALIDATE_IP)) { + return (bool) filter_var( + $host, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ); } - return $authDetails; + return true; } private function findById(int $id) @@ -534,9 +529,9 @@ private function findExistingIdForAccount(string $appSlug, string $accountName): private function formatRow($row): array { - $encryptKeys = $this->parseEncryptKeys($row->encrypt_keys ?? ''); + $encryptKeys = AuthDataCodec::parseEncryptKeys($row->encrypt_keys ?? ''); $authDetails = $this->normalizeArray($row->auth_details ?? null); - $authDetails = $this->decryptValues($authDetails, $encryptKeys); + $authDetails = AuthDataCodec::decryptValues($authDetails, $encryptKeys); return [ 'id' => (int) $row->id, @@ -560,7 +555,7 @@ private function resolveEncryptKeys($request): array } if (\is_string($request->encrypt_keys)) { - return $this->parseEncryptKeys($request->encrypt_keys); + return AuthDataCodec::parseEncryptKeys($request->encrypt_keys); } if (\is_array($request->encrypt_keys)) { @@ -579,19 +574,6 @@ private function resolveEncryptKeys($request): array return []; } - private function parseEncryptKeys($value): array - { - if (\is_array($value)) { - $keys = array_filter(array_map([$this, 'sanitizeScalar'], $value)); - } elseif (\is_string($value) && $value !== '') { - $keys = array_filter(array_map('trim', explode(',', $value))); - } else { - return []; - } - - return array_values(array_unique($keys)); - } - private function normalizeArray($value): array { if (\is_array($value)) { @@ -653,47 +635,6 @@ private function normalizeHeaders($value): array return $headers; } - private function getNestedValue(array $data, string $path) - { - if ($path === '') { - return; - } - - $segments = explode('.', $path); - $cursor = $data; - - foreach ($segments as $segment) { - if (!\is_array($cursor) || !\array_key_exists($segment, $cursor)) { - return; - } - - $cursor = $cursor[$segment]; - } - - return $cursor; - } - - private function setNestedValue(array &$data, string $path, $value): void - { - if ($path === '') { - return; - } - - $segments = explode('.', $path); - $last = array_pop($segments); - $cursor = &$data; - - foreach ($segments as $segment) { - if (!isset($cursor[$segment]) || !\is_array($cursor[$segment])) { - $cursor[$segment] = []; - } - - $cursor = &$cursor[$segment]; - } - - $cursor[$last] = $value; - } - private function normalizeId($request): int { if (is_numeric($request)) { @@ -728,19 +669,7 @@ private function sanitizeScalar($value): string return sanitize_text_field((string) $value); } - private function guardRead(): void - { - if ( - !Capabilities::Check('manage_options') - && !Capabilities::Check('bit_integrations_manage_integrations') - && !Capabilities::Check('bit_integrations_create_integrations') - && !Capabilities::Check('bit_integrations_edit_integrations') - ) { - wp_send_json_error(__('You do not have permission to access connections', 'bit-integrations')); - } - } - - private function guardWrite(): void + private function guard(): void { if ( !Capabilities::Check('manage_options') diff --git a/frontend/src/Utils/connectionAuth.js b/frontend/src/Utils/connectionAuth.js index 94778929b..5cf0bc717 100644 --- a/frontend/src/Utils/connectionAuth.js +++ b/frontend/src/Utils/connectionAuth.js @@ -21,38 +21,3 @@ export const isNoAuthType = authType => authType === AUTH_TYPES.NO_AUTH export const normalizeAuthType = authType => Object.values(AUTH_TYPES).includes(authType) ? authType : AUTH_TYPES.OAUTH2 -/** - * Save or reauthorize a reusable connection. - * - * @returns {Promise<{success: boolean, data?: {data?: object}}>} - */ -export const persistConnectionAuthorization = ({ - appSlug, - authType = AUTH_TYPES.OAUTH2, - connectionId = null, - connectionName = '', - accountName = '', - authDetails = {}, - encryptKeys = [], - status -}) => { - const sanitizedConnectionName = connectionName?.trim() || '' - const sanitizedAccountName = accountName?.trim() || '' - const normalizedAuthType = normalizeAuthType(authType) - - const payload = { - auth_type: normalizedAuthType, - auth_details: isNoAuthType(normalizedAuthType) ? {} : authDetails, - encrypt_keys: Array.isArray(encryptKeys) ? encryptKeys : [] - } - - if (sanitizedConnectionName) payload.connection_name = sanitizedConnectionName - if (sanitizedAccountName) payload.account_name = sanitizedAccountName - if (typeof status === 'number') payload.status = status - - if (connectionId) { - return reauthorizeConnection({ id: connectionId, ...payload }) - } - - return saveConnection({ app_slug: appSlug, ...payload }) -} diff --git a/frontend/src/Utils/oauthHelper.js b/frontend/src/Utils/oauthHelper.js index 47a9221e6..53e151571 100644 --- a/frontend/src/Utils/oauthHelper.js +++ b/frontend/src/Utils/oauthHelper.js @@ -11,9 +11,14 @@ const base64UrlEncode = bytes => { } export const generateCodeVerifier = (length = 64) => { + const charsetLen = PKCE_CHARSET.length + // Reject bytes >= max to avoid modulo bias (RFC 7636 wants uniform). + const max = 256 - (256 % charsetLen) + const buf = new Uint8Array(1) let result = '' - for (let i = 0; i < length; i++) { - result += PKCE_CHARSET.charAt(Math.floor(Math.random() * PKCE_CHARSET.length)) + while (result.length < length) { + window.crypto.getRandomValues(buf) + if (buf[0] < max) result += PKCE_CHARSET.charAt(buf[0] % charsetLen) } return result } diff --git a/frontend/src/components/Connections/ConnectionAccountList.jsx b/frontend/src/components/Connections/ConnectionAccountList.jsx index c0b429cbe..59055e7f9 100644 --- a/frontend/src/components/Connections/ConnectionAccountList.jsx +++ b/frontend/src/components/Connections/ConnectionAccountList.jsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import TrashIcn from '../../Icons/TrashIcn' -import { __ } from '../../Utils/i18nwrap' import { deleteConnection } from '../../Utils/connectionApi' +import { __ } from '../../Utils/i18nwrap' export default function ConnectionAccountList({ connections, diff --git a/frontend/src/components/Connections/Oauth2Connection.jsx b/frontend/src/components/Connections/Oauth2Connection.jsx index 6d521d28e..dd476ca89 100644 --- a/frontend/src/components/Connections/Oauth2Connection.jsx +++ b/frontend/src/components/Connections/Oauth2Connection.jsx @@ -53,8 +53,7 @@ const buildSavedAuthDetails = ({ grant_type: persistedGrantType, refresh_token_url: refreshTokenUrl || tokenUrl, scope: scope || '', - ssl_verify: sslVerify !== false, - raw_response: tokenResponse + ssl_verify: sslVerify !== false } } @@ -135,11 +134,12 @@ export default function Oauth2Connection({ const handleAuthorizationCodeFlow = useCallback(async () => { const isPkce = grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE + const declaredQueryParams = authCodeEndpoint?.queryParams || {} let codeVerifier - const extraParams = { ...(authCodeEndpoint?.queryParams?.client_id ? {} : { client_id: formData.clientId }) } - if (!authCodeEndpoint?.queryParams?.response_type) extraParams.response_type = 'code' - if (scope && !authCodeEndpoint?.queryParams?.scope) extraParams.scope = scope + const extraParams = { client_id: formData.clientId } + if (!declaredQueryParams.response_type) extraParams.response_type = 'code' + if (scope && !declaredQueryParams.scope) extraParams.scope = scope if (isPkce) { codeVerifier = generateCodeVerifier() @@ -147,13 +147,10 @@ export default function Oauth2Connection({ extraParams.code_challenge_method = 'S256' } - const populatedAuthCodeEndpoint = { - ...authCodeEndpoint, - queryParams: { - ...(authCodeEndpoint?.queryParams || {}), - ...(authCodeEndpoint?.queryParams?.client_id ? { client_id: formData.clientId } : {}) - } - } + // Drop integration-declared client_id placeholder; URL.searchParams.append does not dedupe, + // so a placeholder + extraParams.client_id would emit two client_id entries. + const { client_id: _ignored, ...queryParams } = declaredQueryParams + const populatedAuthCodeEndpoint = { ...authCodeEndpoint, queryParams } const state = getCallbackState() const authUrl = buildAuthUrl(populatedAuthCodeEndpoint, { state, redirectUri, extraParams }) From 144bad1d3780c477ac8665e563b9f0eac87de40c Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 8 May 2026 17:31:18 +0600 Subject: [PATCH 13/58] refactor: update error messages to use translation functions for better localization support --- backend/Authorization/AbstractBaseAuthorization.php | 10 ++++++---- backend/Authorization/ApiKey/ApiKeyAuthorization.php | 4 ++-- backend/Authorization/AuthorizationFactory.php | 4 ++-- backend/Authorization/Basic/BasicAuthorization.php | 2 +- .../Authorization/Bearer/BearerTokenAuthorization.php | 2 +- backend/Authorization/OAuth2/OAuth2Authorization.php | 6 ++++-- backend/Core/Util/Hash.php | 4 ++-- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index dfd58083e..98a4d7a7a 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -42,7 +42,7 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload if ($apiEndpoint === '') { return [ 'error' => true, - 'message' => 'API endpoint is required', + 'message' => __('API endpoint is required', 'bit-integrations'), ]; } @@ -51,7 +51,7 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload if (!\is_array($authConfig)) { return [ 'error' => true, - 'message' => 'Invalid authorization config', + 'message' => __('Invalid authorization config', 'bit-integrations'), ]; } @@ -92,9 +92,11 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload } if ((\is_object($response) && !empty($response->error)) || (\is_array($response) && !empty($response['error']))) { + $fallback = __('Authorization failed', 'bit-integrations'); + return [ 'error' => true, - 'message' => \is_object($response) ? ($response->error ?? 'Authorization failed') : ($response['error'] ?? 'Authorization failed'), + 'message' => \is_object($response) ? ($response->error ?? $fallback) : ($response['error'] ?? $fallback), 'response' => $response, ]; } @@ -102,7 +104,7 @@ public function authorize(string $apiEndpoint, string $method = 'GET', $payload if (isset(HttpHelper::$responseCode) && ((int) HttpHelper::$responseCode < 200 || (int) HttpHelper::$responseCode >= 300)) { return [ 'error' => true, - 'message' => 'Authorization failed', + 'message' => __('Authorization failed', 'bit-integrations'), 'response' => $response, ]; } diff --git a/backend/Authorization/ApiKey/ApiKeyAuthorization.php b/backend/Authorization/ApiKey/ApiKeyAuthorization.php index 25a21f037..61012b4bb 100644 --- a/backend/Authorization/ApiKey/ApiKeyAuthorization.php +++ b/backend/Authorization/ApiKey/ApiKeyAuthorization.php @@ -17,7 +17,7 @@ public function getAccessToken() if (empty($authDetails) || !isset($authDetails['value']) || $authDetails['value'] === '') { return [ 'error' => true, - 'message' => 'token field is missing', + 'message' => __('token field is missing', 'bit-integrations'), ]; } @@ -31,7 +31,7 @@ public function getAuthHeadersOrParams() if (empty($authDetails) || !isset($authDetails['value'])) { return [ 'error' => true, - 'message' => 'token field is missing', + 'message' => __('token field is missing', 'bit-integrations'), ]; } diff --git a/backend/Authorization/AuthorizationFactory.php b/backend/Authorization/AuthorizationFactory.php index 420426253..780b31f89 100644 --- a/backend/Authorization/AuthorizationFactory.php +++ b/backend/Authorization/AuthorizationFactory.php @@ -42,10 +42,10 @@ public static function getAuthorizationHandler($type, $connectionId, $appSlug = return new $class($connectionId); } - throw new Exception('Authorization class not found'); + throw new Exception(esc_html__('Authorization class not found', 'bit-integrations')); default: - throw new Exception('Invalid authorization type'); + throw new Exception(esc_html__('Invalid authorization type', 'bit-integrations')); } } diff --git a/backend/Authorization/Basic/BasicAuthorization.php b/backend/Authorization/Basic/BasicAuthorization.php index f7bbf9bc4..7d78b2a1a 100644 --- a/backend/Authorization/Basic/BasicAuthorization.php +++ b/backend/Authorization/Basic/BasicAuthorization.php @@ -17,7 +17,7 @@ public function getAccessToken() if (empty($authDetails) || empty($authDetails['username']) || !isset($authDetails['password'])) { return [ 'error' => true, - 'message' => 'username or password field is missing', + 'message' => __('username or password field is missing', 'bit-integrations'), ]; } diff --git a/backend/Authorization/Bearer/BearerTokenAuthorization.php b/backend/Authorization/Bearer/BearerTokenAuthorization.php index 445d8827f..1921aec05 100644 --- a/backend/Authorization/Bearer/BearerTokenAuthorization.php +++ b/backend/Authorization/Bearer/BearerTokenAuthorization.php @@ -17,7 +17,7 @@ public function getAccessToken() if (empty($authDetails) || empty($authDetails['token'])) { return [ 'error' => true, - 'message' => 'access token field is missing', + 'message' => __('access token field is missing', 'bit-integrations'), ]; } diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index d1a1f20c1..1be2b5887 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -68,7 +68,7 @@ public function getAccessToken() if ($authDetails === null) { return $this->getLastError() ?: [ 'error' => true, - 'message' => 'Connection auth details are missing', + 'message' => __('Connection auth details are missing', 'bit-integrations'), ]; } @@ -121,7 +121,9 @@ public function refreshAccessToken(array $authDetails): ?array $response = HttpHelper::post($url, $body, $headers, $requestOptions); if (HttpHelper::$responseCode !== 200 || (\is_object($response) && isset($response->error))) { - $message = \is_object($response) && isset($response->error) ? $response->error : 'Token refresh failed'; + $message = \is_object($response) && isset($response->error) + ? $response->error + : __('Token refresh failed', 'bit-integrations'); $this->setLastError((string) $message, $response); return null; diff --git a/backend/Core/Util/Hash.php b/backend/Core/Util/Hash.php index 3d721750a..4bc1f45b5 100644 --- a/backend/Core/Util/Hash.php +++ b/backend/Core/Util/Hash.php @@ -22,7 +22,7 @@ public static function encrypt($data) $secretKey = self::secretKey(); $ivLength = openssl_cipher_iv_length(self::CIPHER); - $iv = openssl_random_pseudo_bytes($ivLength); + $iv = random_bytes($ivLength); $cipherText = openssl_encrypt((string) $data, self::CIPHER, $secretKey, 0, $iv); return urlencode($iv . $cipherText); @@ -61,7 +61,7 @@ private static function secretKey() if (!$secretKey) { $secretKey = function_exists('wp_generate_password') ? wp_generate_password(64, true, true) - : Config::VAR_PREFIX . bin2hex(openssl_random_pseudo_bytes(32)); + : Config::VAR_PREFIX . bin2hex(random_bytes(32)); // Autoloaded so encrypt/decrypt loops avoid repeated DB hits. Config::addOption('secret_key', $secretKey, true); From 873cd9db7ed6845d2d9822d91882262e5724a16f Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 8 May 2026 19:16:20 +0600 Subject: [PATCH 14/58] feat: implement plugin check functionality and refactor WeDocs authorization flow --- backend/Actions/WeDocs/Routes.php | 1 - backend/Actions/WeDocs/WeDocsController.php | 6 - .../Authorization/AuthorizationFactory.php | 19 +- .../Authorization/Support/AuthDataCodec.php | 17 +- backend/Core/Util/PlatformCheck.php | 180 ++++++++++++++++++ backend/Routes/ajax.php | 3 +- backend/controller/ConnectionController.php | 39 +++- frontend/src/Utils/connectionApi.js | 24 +-- .../WeDocs/WeDocsAuthorization.jsx | 89 +++------ .../WeDocs/WeDocsCommonFunc.js | 41 ---- .../Connections/PlatformAuthorization.jsx | 118 +++++++++--- 11 files changed, 358 insertions(+), 179 deletions(-) create mode 100644 backend/Core/Util/PlatformCheck.php diff --git a/backend/Actions/WeDocs/Routes.php b/backend/Actions/WeDocs/Routes.php index ba462e976..61a1ba7f1 100644 --- a/backend/Actions/WeDocs/Routes.php +++ b/backend/Actions/WeDocs/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\WeDocs\WeDocsController; use BitApps\Integrations\Core\Util\Route; -Route::post('wedocs_authorize', [WeDocsController::class, 'weDocsAuthorize']); Route::post('wedocs_get_documentations', [WeDocsController::class, 'getDocumentations']); Route::post('wedocs_get_sections', [WeDocsController::class, 'getSections']); diff --git a/backend/Actions/WeDocs/WeDocsController.php b/backend/Actions/WeDocs/WeDocsController.php index bb456bb5d..2eea4693f 100644 --- a/backend/Actions/WeDocs/WeDocsController.php +++ b/backend/Actions/WeDocs/WeDocsController.php @@ -13,12 +13,6 @@ class WeDocsController private const ALLOWED_POST_STATUSES = ['publish', 'draft', 'pending', 'private']; - public static function weDocsAuthorize() - { - self::checkPluginExists(); - wp_send_json_success(true); - } - public static function getDocumentations() { self::checkPluginExists(); diff --git a/backend/Authorization/AuthorizationFactory.php b/backend/Authorization/AuthorizationFactory.php index 780b31f89..da0823f30 100644 --- a/backend/Authorization/AuthorizationFactory.php +++ b/backend/Authorization/AuthorizationFactory.php @@ -14,11 +14,7 @@ class AuthorizationFactory { - public const FREE_NAMESPACE = 'BitApps\\Integrations\\Actions\\'; - - public const PRO_NAMESPACE = 'BitApps\\IntegrationsPro\\Actions\\'; - - public const PRO_NAMESPACE_LEGACY = 'BitApps\\BTCBI_PRO\\Actions\\'; + public const ACTION_NAMESPACE = 'BitApps\\Integrations\\Actions\\'; public static function getAuthorizationHandler($type, $connectionId, $appSlug = '') { @@ -52,17 +48,10 @@ public static function getAuthorizationHandler($type, $connectionId, $appSlug = public static function authorizationClassExists($appSlug) { $appSlug = ucfirst((string) $appSlug); + $class = self::ACTION_NAMESPACE . "{$appSlug}\\{$appSlug}Authorization"; - $candidates = [ - self::FREE_NAMESPACE . "{$appSlug}\\{$appSlug}Authorization", - self::PRO_NAMESPACE . "{$appSlug}\\{$appSlug}Authorization", - self::PRO_NAMESPACE_LEGACY . "{$appSlug}\\{$appSlug}Authorization", - ]; - - foreach ($candidates as $class) { - if (class_exists($class)) { - return $class; - } + if (class_exists($class)) { + return $class; } return false; diff --git a/backend/Authorization/Support/AuthDataCodec.php b/backend/Authorization/Support/AuthDataCodec.php index 6f05f6b1d..b3795deab 100644 --- a/backend/Authorization/Support/AuthDataCodec.php +++ b/backend/Authorization/Support/AuthDataCodec.php @@ -57,14 +57,14 @@ public static function parseEncryptKeys($value): array public static function getNested(array $data, string $path) { if ($path === '') { - return null; + return; } $cursor = $data; foreach (explode('.', $path) as $segment) { if (!\is_array($cursor) || !\array_key_exists($segment, $cursor)) { - return null; + return; } $cursor = $cursor[$segment]; @@ -104,6 +104,19 @@ public static function decryptValues(array $data, array $keys): array return self::transformValues($data, $keys, [Hash::class, 'decrypt']); } + public static function toArray($value): array + { + if (\is_array($value)) { + return $value; + } + + if (\is_object($value)) { + return (array) $value; + } + + return []; + } + private static function transformValues(array $data, array $keys, callable $fn): array { foreach ($keys as $path) { diff --git a/backend/Core/Util/PlatformCheck.php b/backend/Core/Util/PlatformCheck.php new file mode 100644 index 000000000..8def9d15f --- /dev/null +++ b/backend/Core/Util/PlatformCheck.php @@ -0,0 +1,180 @@ + 'AND' | 'OR', // outer combiner across groups (default AND) + * 'groups' => [ + * [ + * 'logic' => 'AND' | 'OR', // combines this group's checks (default AND) + * 'checks' => [ + * ['type' => 'class', 'value' => 'Foo'], + * ['type' => 'function', 'value' => 'foo_init'], + * ], + * ], + * ], + * ] + * + * Flat shape (single implicit group): + * ['checks' => [...], 'logic' => 'AND' | 'OR'] + */ +final class PlatformCheck +{ + private const ALLOWED_TYPES = ['class', 'function', 'constant', 'plugin_file']; + + private const ALLOWED_LOGIC = ['AND', 'OR']; + + /** + * @param array $spec accepts groups OR flat checks; nested values may be stdClass + * + * @return array{available:bool,message?:string} + */ + public static function evaluate(array $spec): array + { + $groups = self::normalizeGroups($spec); + + if (empty($groups)) { + return [ + 'available' => false, + 'message' => __('Platform checks are required', 'bit-integrations'), + ]; + } + + $outerLogic = self::normalizeLogic($spec['logic'] ?? null); + $groupResults = []; + + foreach ($groups as $group) { + $checkResults = []; + + foreach ($group['checks'] as $check) { + $check = AuthDataCodec::toArray($check); + + $type = $check['type'] ?? null; + $value = $check['value'] ?? null; + + if (!\in_array($type, self::ALLOWED_TYPES, true) || empty($value)) { + continue; + } + + $checkResults[] = self::matches($type, $value); + } + + if (empty($checkResults)) { + continue; + } + + $groupResults[] = self::combine($checkResults, $group['logic']); + } + + if (empty($groupResults)) { + return [ + 'available' => false, + 'message' => __('No valid platform checks were provided', 'bit-integrations'), + ]; + } + + if (self::combine($groupResults, $outerLogic)) { + return ['available' => true]; + } + + return [ + 'available' => false, + 'message' => __('Platform is not installed or activated', 'bit-integrations'), + ]; + } + + /** + * Normalize spec into a list of groups. A flat `checks` array is treated as + * a single implicit group so callers keep working. + * + * @return array + */ + private static function normalizeGroups(array $spec): array + { + $rawGroups = AuthDataCodec::toArray($spec['groups'] ?? null); + + if (!empty($rawGroups)) { + $groups = []; + + foreach ($rawGroups as $group) { + $group = AuthDataCodec::toArray($group); + $checks = AuthDataCodec::toArray($group['checks'] ?? null); + + if (empty($checks)) { + continue; + } + + $groups[] = [ + 'logic' => self::normalizeLogic($group['logic'] ?? null), + 'checks' => $checks, + ]; + } + + return $groups; + } + + $checks = AuthDataCodec::toArray($spec['checks'] ?? null); + + if (empty($checks)) { + return []; + } + + return [[ + 'logic' => 'AND', + 'checks' => $checks, + ]]; + } + + private static function normalizeLogic($raw): string + { + $normalized = strtoupper($raw); + + return \in_array($normalized, self::ALLOWED_LOGIC, true) ? $normalized : 'AND'; + } + + /** + * @param array $results + */ + private static function combine(array $results, string $logic): bool + { + return $logic === 'OR' + ? \in_array(true, $results, true) + : !\in_array(false, $results, true); + } + + private static function matches(string $type, string $value): bool + { + switch ($type) { + case 'class': + return class_exists($value); + case 'function': + return \function_exists($value); + case 'constant': + return \defined($value); + case 'plugin_file': + if (!\function_exists('is_plugin_active')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return is_plugin_active($value); + default: + return false; + } + } +} diff --git a/backend/Routes/ajax.php b/backend/Routes/ajax.php index ddb751277..774f37612 100644 --- a/backend/Routes/ajax.php +++ b/backend/Routes/ajax.php @@ -64,8 +64,9 @@ Route::get('connections/list', [ConnectionController::class, 'index']); Route::get('connections/get', [ConnectionController::class, 'getById']); Route::post('connections/authorize', [ConnectionController::class, 'authorize']); +Route::post('connections/oauth2/exchange', [ConnectionController::class, 'oauth2Exchange']); +Route::post('connections/platform/check', [ConnectionController::class, 'checkPlatform']); Route::post('connections/save', [ConnectionController::class, 'save']); Route::post('connections/update', [ConnectionController::class, 'update']); Route::post('connections/reauthorize', [ConnectionController::class, 'reauthorize']); -Route::post('connections/oauth2/exchange', [ConnectionController::class, 'oauth2Exchange']); Route::post('connections/delete', [ConnectionController::class, 'delete']); diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 4499e1b96..178e46b0e 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -13,6 +13,7 @@ use BitApps\Integrations\Core\Util\Capabilities; use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Core\Util\PlatformCheck; use Exception; use WP_Error; @@ -223,7 +224,7 @@ public function authorize($request) $payload = isset($request->payload) ? $this->normalizePayload($request->payload) : null; $headers = $this->normalizeHeaders($request->headers ?? []); - if ($authType === '') { + if (empty($authType)) { wp_send_json_error(__('Auth type is required', 'bit-integrations')); } @@ -235,7 +236,7 @@ public function authorize($request) wp_send_json_error(__('Authorization details are required', 'bit-integrations')); } - if ($apiEndpoint === '') { + if (empty($apiEndpoint)) { wp_send_json_error(__('API endpoint is required', 'bit-integrations')); } @@ -291,6 +292,40 @@ public function delete($request) wp_send_json_success(['id' => $id]); } + /** + * Confirm a WordPress-plugin platform is installed/active. No DB persistence — + * caller supplies a check spec (class/function/constant/plugin_file) with + * AND/OR logic (optionally grouped) and PlatformCheck evaluates it. + * + * Spec sanitization lives in PlatformCheck so this controller stays a thin + * adapter from the request shape to the evaluator. + * + * @param mixed $request + */ + public function checkPlatform($request) + { + $this->guard(); + + $spec = ['logic' => $request->logic ?? null]; + + if (isset($request->groups)) { + $spec['groups'] = $request->groups; + } else { + $spec['checks'] = $request->checks ?? null; + } + + $result = PlatformCheck::evaluate($spec); + + if (empty($result['available'])) { + wp_send_json_error( + $result['message'] ?? __('Platform check failed', 'bit-integrations'), + 400 + ); + } + + wp_send_json_success(['available' => true]); + } + /** * Server-side OAuth2 token exchange (auth_code, pkce, client_credentials, refresh_token). * Browsers cannot reach token endpoints (no CORS) and must not hold client_secret. diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index 0b19694b1..beb19a5d5 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -14,6 +14,8 @@ export const authorizeConnection = payload => bitsFetch(payload, 'connections/au export const oauthConnectionExchange = payload => bitsFetch(payload, 'connections/oauth2/exchange') +export const checkPlatform = payload => bitsFetch(payload, 'connections/platform/check') + export const saveConnection = payload => bitsFetch(payload, 'connections/save') export const updateConnection = payload => bitsFetch(payload, 'connections/update') @@ -21,25 +23,3 @@ export const updateConnection = payload => bitsFetch(payload, 'connections/updat export const reauthorizeConnection = payload => bitsFetch(payload, 'connections/reauthorize') export const deleteConnection = id => bitsFetch({ id }, 'connections/delete') - -/** - * Persist auth payload as a reusable connection. - * - * @returns {Promise<{ success: boolean, data: object }>} - */ -// export const persistConnection = ({ -// appSlug, -// authType = 'oauth2', -// connectionName, -// accountName, -// authDetails, -// encryptKeys = [] -// }) => -// saveConnection({ -// app_slug: appSlug, -// auth_type: authType, -// connection_name: connectionName, -// account_name: accountName, -// auth_details: authDetails, -// encrypt_keys: encryptKeys -// }) diff --git a/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx b/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx index 2f31349a3..9db101bc4 100644 --- a/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx @@ -1,81 +1,40 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { weDocsAuthentication } from './WeDocsCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WeDocsAuthorization({ weDocsConf, setWeDocsConf, step, nextPage, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) - const { weDocs } = tutorialLinks - - const handleInput = e => { - const newConf = { ...weDocsConf } - newConf[e.target.name] = e.target.value - setWeDocsConf(newConf) - } + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - {weDocs?.youTubeLink && } - {weDocs?.docLink && } - -
    - {__('Integration Name:', 'bit-integrations')} -
    - -
    {error.name}
    - - - - {!isInfo && ( - <> - -
    - - - )} -
    + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/WeDocs/WeDocsCommonFunc.js b/frontend/src/components/AllIntegrations/WeDocs/WeDocsCommonFunc.js index 3300db200..3cf113c13 100644 --- a/frontend/src/components/AllIntegrations/WeDocs/WeDocsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/WeDocs/WeDocsCommonFunc.js @@ -124,44 +124,3 @@ export const refreshSections = (documentationId, setWeDocsConf, setIsLoading, se }) } -export const weDocsAuthentication = ( - confTmp, - setWeDocsConf, - setError, - setIsAuthorized, - setIsLoading -) => { - if (!confTmp?.name) { - setError({ - name: __("Integration name can't be empty", 'bit-integrations') - }) - return - } - - setError({}) - setIsLoading(true) - - bitsFetch({ name: confTmp.name }, 'wedocs_authorize') - .then(result => { - if (result?.success) { - setIsAuthorized(true) - setWeDocsConf(prevConf => - create(prevConf, draftConf => { - draftConf.name = confTmp.name - }) - ) - } else { - setError({ - name: result?.data || __('Authorization failed', 'bit-integrations') - }) - } - }) - .catch(() => { - setError({ - name: __('Authorization failed', 'bit-integrations') - }) - }) - .finally(() => { - setIsLoading(false) - }) -} diff --git a/frontend/src/components/Connections/PlatformAuthorization.jsx b/frontend/src/components/Connections/PlatformAuthorization.jsx index 12ffe7da0..3a4285760 100644 --- a/frontend/src/components/Connections/PlatformAuthorization.jsx +++ b/frontend/src/components/Connections/PlatformAuthorization.jsx @@ -1,7 +1,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import BackIcn from '../../Icons/BackIcn' -import { listConnections } from '../../Utils/connectionApi' +import { AUTH_TYPES } from '../../Utils/connectionAuth' +import { checkPlatform, listConnections } from '../../Utils/connectionApi' import { __ } from '../../Utils/i18nwrap' +import LoaderSm from '../Loaders/LoaderSm' import Note from '../Utilities/Note' import TutorialLink from '../Utilities/TutorialLink' import AddNewConnection from './AddNewConnection' @@ -27,8 +29,11 @@ export default function PlatformAuthorization({ const [connections, setConnections] = useState([]) const [showNewConnection, setShowNewConnection] = useState(false) const [isLoading, setIsLoading] = useState(false) + const [isVerifying, setIsVerifying] = useState(false) + const [isVerified, setIsVerified] = useState(false) const appSlug = config?.app_slug || config?.type + const isNoAuth = authDetails?.authType === AUTH_TYPES.NO_AUTH const refreshConnections = useCallback(async () => { if (!appSlug) { @@ -55,8 +60,12 @@ export default function PlatformAuthorization({ }, [appSlug]) useEffect(() => { + if (isNoAuth) { + return + } + refreshConnections() - }, [appSlug]) + }, [appSlug, isNoAuth]) const handleNameChange = useCallback( event => { @@ -70,6 +79,51 @@ export default function PlatformAuthorization({ [setConfig] ) + const platformCheck = authDetails?.platformCheck + + const handleVerifyPlatform = useCallback(async () => { + if (!config?.name?.trim()) { + setErrors({ name: __('Integration name is required', 'bit-integrations') }) + return + } + + const hasGroups = Array.isArray(platformCheck?.groups) && platformCheck.groups.length > 0 + const hasChecks = Array.isArray(platformCheck?.checks) && platformCheck.checks.length > 0 + + if (!hasGroups && !hasChecks) { + setErrors({ + name: __('Platform checks are not defined for this integration', 'bit-integrations') + }) + return + } + + setIsVerifying(true) + setErrors({}) + + try { + const res = await checkPlatform({ + logic: platformCheck.logic || 'AND', + ...(hasGroups ? { groups: platformCheck.groups } : { checks: platformCheck.checks }) + }) + + if (res?.success) { + setIsVerified(true) + } else { + setIsVerified(false) + setErrors({ + name: res?.data || __('Platform is not installed or activated', 'bit-integrations') + }) + } + } catch (error) { + setIsVerified(false) + setErrors({ + name: error?.message || __('Platform check failed', 'bit-integrations') + }) + } finally { + setIsVerifying(false) + } + }, [config?.name, platformCheck]) + const handleNext = useCallback(() => { if (!config?.name?.trim()) { setErrors({ name: __('Integration name is required', 'bit-integrations') }) @@ -79,7 +133,7 @@ export default function PlatformAuthorization({ setStep(2) }, [config?.name, setStep]) - const canGoNext = Boolean(config?.connection_id) + const canGoNext = isNoAuth ? isVerified : Boolean(config?.connection_id) const pageStyle = useMemo(() => (step === 1 ? STEP_ONE_STYLE : undefined), [step]) @@ -120,28 +174,44 @@ export default function PlatformAuthorization({ />
    {errors.name || ''}
    + {!isNoAuth && ( + <> + + + {showNewConnection && !isInfo && (extraFields || null)} + + {showNewConnection && !isInfo && ( + + )} + + )} - - - {showNewConnection && !isInfo && (extraFields || null)} - - {showNewConnection && !isInfo && ( - + {isNoAuth && !isInfo && ( + )} {!isInfo && canGoNext && ( From 8b9e1e71d390df2311b52dffcc27184172fdc2de Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Sat, 9 May 2026 11:52:53 +0600 Subject: [PATCH 15/58] Refactor integration authorization components to use PlatformAuthorization - Replaced individual authorization logic in Voxel, WCAffiliate, WPCafe, WPCourseware, WPForo, WishlistMember, WooCommerce, and WP ERP components with a unified PlatformAuthorization component. - Removed unnecessary state management and input handling from the individual components. - Updated the styling of the app to improve layout consistency. - Ensured that each integration checks for the required plugins and provides appropriate notes for users. --- .../AcademyLms/AcademyLmsController.php | 16 -- backend/Actions/AcademyLms/Routes.php | 1 - .../Actions/Affiliate/AffiliateController.php | 10 -- backend/Actions/Affiliate/Routes.php | 1 - .../AsgarosForum/AsgarosForumController.php | 13 -- backend/Actions/AsgarosForum/Routes.php | 1 - .../Actions/Autonami/AutonamiController.php | 9 -- backend/Actions/Autonami/Routes.php | 1 - .../Actions/BuddyBoss/BuddyBossController.php | 10 -- backend/Actions/BuddyBoss/Routes.php | 1 - .../CreatorLms/CreatorLmsController.php | 6 - backend/Actions/CreatorLms/Routes.php | 1 - backend/Actions/Dokan/DokanController.php | 15 -- backend/Actions/Dokan/Routes.php | 1 - .../FluentCart/FluentCartController.php | 6 - backend/Actions/FluentCart/Routes.php | 1 - .../Actions/FluentCrm/FluentCrmController.php | 18 --- backend/Actions/FluentCrm/Routes.php | 1 - .../Actions/GamiPress/GamiPressController.php | 10 -- backend/Actions/GamiPress/Routes.php | 1 - backend/Actions/GiveWp/GiveWpController.php | 10 -- backend/Actions/GiveWp/Routes.php | 1 - .../Actions/JetEngine/JetEngineController.php | 15 -- backend/Actions/JetEngine/Routes.php | 1 - .../Actions/LearnDash/LearnDashController.php | 11 -- backend/Actions/LearnDash/Routes.php | 1 - .../Actions/LifterLms/LifterLmsController.php | 9 -- backend/Actions/LifterLms/Routes.php | 1 - .../Actions/MailMint/MailMintController.php | 10 -- backend/Actions/MailMint/Routes.php | 1 - .../Actions/MailPoet/MailPoetController.php | 11 -- backend/Actions/MailPoet/Routes.php | 1 - .../MailerPress/MailerPressController.php | 11 -- backend/Actions/MailerPress/Routes.php | 1 - .../Actions/Mailster/MailsterController.php | 15 -- backend/Actions/Mailster/Routes.php | 1 - .../MasterStudyLmsController.php | 10 -- backend/Actions/MasterStudyLms/Routes.php | 1 - .../Memberpress/MemberpressController.php | 11 -- backend/Actions/Memberpress/Routes.php | 1 - .../Newsletter/NewsletterController.php | 15 -- backend/Actions/Newsletter/Routes.php | 1 - .../NotificationX/NotificationXController.php | 6 - backend/Actions/NotificationX/Routes.php | 1 - .../PaidMembershipProController.php | 9 -- backend/Actions/PaidMembershipPro/Routes.php | 1 - backend/Actions/PeepSo/PeepSoController.php | 6 - backend/Actions/PeepSo/Routes.php | 1 - .../PropovoiceCRM/PropovoiceCRMController.php | 10 -- backend/Actions/PropovoiceCRM/Routes.php | 1 - .../RestrictContentController.php | 11 -- backend/Actions/RestrictContent/Routes.php | 1 - backend/Actions/SliceWp/Routes.php | 1 - backend/Actions/SliceWp/SliceWpController.php | 10 -- backend/Actions/SureMembers/Routes.php | 1 - .../SureMembers/SureMembersController.php | 15 -- .../TeamsForWooCommerceMemberships/Routes.php | 1 - ...amsForWooCommerceMembershipsController.php | 6 - backend/Actions/TheEventsCalendar/Routes.php | 1 - .../TheEventsCalendarController.php | 9 -- backend/Actions/TutorLms/Routes.php | 1 - .../Actions/TutorLms/TutorLmsController.php | 16 -- .../Actions/UltimateAffiliatePro/Routes.php | 1 - .../UltimateAffiliateProController.php | 7 - .../UserRegistrationMembership/Routes.php | 1 - .../UserRegistrationMembershipController.php | 6 - backend/Actions/Voxel/Routes.php | 1 - backend/Actions/Voxel/VoxelController.php | 5 - backend/Actions/WCAffiliate/Routes.php | 1 - .../WCAffiliate/WCAffiliateController.php | 19 --- backend/Actions/WPCafe/Routes.php | 1 - backend/Actions/WPCafe/WPCafeController.php | 6 - backend/Actions/WPCourseware/Routes.php | 1 - .../WPCourseware/WPCoursewareController.php | 9 -- backend/Actions/WPForo/Routes.php | 1 - backend/Actions/WPForo/WPForoController.php | 15 -- backend/Actions/WishlistMember/Routes.php | 1 - .../WishlistMemberController.php | 18 --- backend/Actions/WooCommerce/Routes.php | 1 - .../WooCommerce/WooCommerceController.php | 12 -- backend/Actions/WpErp/Routes.php | 1 - backend/Actions/WpErp/WpErpController.php | 6 - .../AcademyLms/AcademyLmsAuthorization.jsx | 116 +++----------- .../Affiliate/AffiliateAuthorization.jsx | 117 +++----------- .../AsgarosForumAuthorization.jsx | 86 +++-------- .../AsgarosForum/AsgarosForumCommonFunc.js | 44 ------ .../Autonami/AutonamiAuthorization.jsx | 122 +++------------ .../BuddyBoss/BuddyBossAuthorization.jsx | 118 +++----------- .../CreatorLms/CreatorLmsAuthorization.jsx | 136 ++++------------ .../Dokan/DokanAuthorization.jsx | 96 +++--------- .../Dokan/dokanCommonFunctions.js | 18 --- .../FluentCRM/FluentCrmAuthorization.jsx | 119 +++----------- .../FluentCart/FluentCartAuthorization.jsx | 125 +++------------ .../GamiPress/GamiPressAuthorization.jsx | 117 +++----------- .../GiveWp/GiveWpAuthorization.jsx | 117 +++----------- .../JetEngine/JetEngineAuthorization.jsx | 96 +++--------- .../JetEngine/jetEngineCommonFunctions.js | 18 --- .../LearnDash/LearnDashAuthorization.jsx | 125 ++++----------- .../LifterLms/LifterLmsAuthorization.jsx | 117 +++----------- .../MailMint/MailMintAuthorization.jsx | 122 ++++----------- .../MailPoet/MailPoetAuthorization.jsx | 104 +++---------- .../MailerPress/MailerPressAuthorization.jsx | 124 +++------------ .../Mailster/MailsterAuthorization.jsx | 106 +++---------- .../Mailster/MailsterCommonFunc.js | 26 ---- .../MasterStudyLmsAuthorization.jsx | 123 ++++----------- .../Memberpress/MemberpressAuthorization.jsx | 122 ++++----------- .../Newsletter/NewsletterAuthorization.jsx | 96 +++--------- .../Newsletter/NewsletterCommonFunc.js | 22 --- .../NotificationXAuthorization.jsx | 97 +++--------- .../NotificationX/NotificationXCommonFunc.js | 33 ---- .../PaidMembershipProAuthorization.jsx | 117 +++----------- .../PeepSo/PeepSoAuthorization.jsx | 130 +++------------- .../PropovoiceCrmAuthorization.jsx | 117 +++----------- .../RestrictContentAuthorization.jsx | 138 +++++------------ .../SliceWp/SliceWpAuthorization.jsx | 117 +++----------- .../SureMembers/SureMembersAuthorization.jsx | 96 +++--------- .../SureMembers/SureMembersCommonFunc.js | 20 --- ...ForWooCommerceMembershipsAuthorization.jsx | 128 +++------------ .../TheEventsCalendarAuthorization.jsx | 108 ++++--------- .../theEventsCalendarCommonFunctions.js | 24 --- .../TutorLms/TutorLmsAuthorization.jsx | 115 +++----------- .../UltimateAffiliateProAuthorization.jsx | 146 ++++-------------- ...serRegistrationMembershipAuthorization.jsx | 85 +++------- .../UserRegistrationMembershipCommonFunc.js | 38 ----- .../Voxel/VoxelAuthorization.jsx | 96 +++--------- .../Voxel/VoxelCommonFunctions.js | 18 --- .../WCAffiliate/WCAffiliateAuthorization.jsx | 136 ++++------------ .../WPCafe/WPCafeAuthorization.jsx | 87 +++-------- .../WPCafe/WPCafeCommonFunc.js | 21 --- .../WPCoursewareAuthorization.jsx | 122 +++------------ .../WPForo/WPForoAuthorization.jsx | 96 +++--------- .../WPForo/WPForoCommonFunc.js | 18 --- .../WishlistMemberAuthorization.jsx | 76 ++++----- .../WishlistMemberCommonFunc.js | 18 --- .../WooCommerce/WooCommerceAuthorization.jsx | 116 +++----------- .../WpErp/WpErpAuthorization.jsx | 121 +++------------ frontend/src/resource/sass/app.scss | 2 +- 137 files changed, 1022 insertions(+), 4429 deletions(-) diff --git a/backend/Actions/AcademyLms/AcademyLmsController.php b/backend/Actions/AcademyLms/AcademyLmsController.php index ed7a0f31a..eabb978bc 100644 --- a/backend/Actions/AcademyLms/AcademyLmsController.php +++ b/backend/Actions/AcademyLms/AcademyLmsController.php @@ -20,22 +20,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @return JSON zoho crm api response and status - */ - public static function Authorization() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (is_plugin_active('academy/academy.php')) { - wp_send_json_success(true, 200); - } - - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Academy Lms')); - } - public static function getAllLesson() { if (!class_exists('Academy')) { diff --git a/backend/Actions/AcademyLms/Routes.php b/backend/Actions/AcademyLms/Routes.php index a655d3623..ff94a62a9 100644 --- a/backend/Actions/AcademyLms/Routes.php +++ b/backend/Actions/AcademyLms/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\AcademyLms\AcademyLmsController; use BitApps\Integrations\Core\Util\Route; -Route::post('academy_lms_authorize', [AcademyLmsController::class, 'Authorization']); Route::get('academy_lms_all_course', [AcademyLmsController::class, 'getAllCourse']); Route::get('academy_lms_all_lesson', [AcademyLmsController::class, 'getAllLesson']); diff --git a/backend/Actions/Affiliate/AffiliateController.php b/backend/Actions/Affiliate/AffiliateController.php index 320e10ded..40c17c99e 100644 --- a/backend/Actions/Affiliate/AffiliateController.php +++ b/backend/Actions/Affiliate/AffiliateController.php @@ -30,16 +30,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeAffiliate() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Affiliate')); - } - public static function getAllAffiliate() { $cache_key = Config::withPrefix('affiliate_wp_all_affiliates'); diff --git a/backend/Actions/Affiliate/Routes.php b/backend/Actions/Affiliate/Routes.php index 4f72f66f6..b2fdb9317 100644 --- a/backend/Actions/Affiliate/Routes.php +++ b/backend/Actions/Affiliate/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Affiliate\AffiliateController; use BitApps\Integrations\Core\Util\Route; -Route::post('affiliate_authorize', [AffiliateController::class, 'authorizeAffiliate']); Route::post('affiliate_fetch_all_affiliate', [AffiliateController::class, 'getAllAffiliate']); diff --git a/backend/Actions/AsgarosForum/AsgarosForumController.php b/backend/Actions/AsgarosForum/AsgarosForumController.php index 051948f25..8f6ee88a6 100644 --- a/backend/Actions/AsgarosForum/AsgarosForumController.php +++ b/backend/Actions/AsgarosForum/AsgarosForumController.php @@ -7,12 +7,6 @@ */ class AsgarosForumController { - public static function asgarosForumAuthorize() - { - self::checkPluginExists(); - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; @@ -22,11 +16,4 @@ public function execute($integrationData, $fieldValues) return $recordApiHelper->execute($fieldValues, $fieldMap); } - - private static function checkPluginExists() - { - if (!class_exists('AsgarosForum')) { - wp_send_json_error(__('Asgaros Forum is not activated or not installed', 'bit-integrations'), 400); - } - } } diff --git a/backend/Actions/AsgarosForum/Routes.php b/backend/Actions/AsgarosForum/Routes.php index 6eeca47c5..41ed4c653 100644 --- a/backend/Actions/AsgarosForum/Routes.php +++ b/backend/Actions/AsgarosForum/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\AsgarosForum\AsgarosForumController; use BitApps\Integrations\Core\Util\Route; -Route::post('asgaros_forum_authorize', [AsgarosForumController::class, 'asgarosForumAuthorize']); diff --git a/backend/Actions/Autonami/AutonamiController.php b/backend/Actions/Autonami/AutonamiController.php index 8d857640a..7800cfa20 100644 --- a/backend/Actions/Autonami/AutonamiController.php +++ b/backend/Actions/Autonami/AutonamiController.php @@ -83,15 +83,6 @@ public static function autonamiFields() wp_send_json_success($response, 200); } - public static function autonamiAuthorize() - { - if (self::checkedExistsAutonami()) { - wp_send_json_success(true); - } else { - wp_send_json_error(__('Autonami Pro Plugin is not active or installed', 'bit-integrations'), 400); - } - } - public function execute($integrationData, $fieldValues) { if (!class_exists('BWFCRM_Contact')) { diff --git a/backend/Actions/Autonami/Routes.php b/backend/Actions/Autonami/Routes.php index 11e1136b2..af5c0f557 100644 --- a/backend/Actions/Autonami/Routes.php +++ b/backend/Actions/Autonami/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Autonami\AutonamiController; use BitApps\Integrations\Core\Util\Route; -Route::post('autonami_authorize', [AutonamiController::class, 'autonamiAuthorize']); Route::post('autonami_lists_and_tags', [AutonamiController::class, 'autonamiListsAndTags']); Route::post('autonami_fields', [AutonamiController::class, 'autonamiFields']); diff --git a/backend/Actions/BuddyBoss/BuddyBossController.php b/backend/Actions/BuddyBoss/BuddyBossController.php index 2e3362a2b..6177c14bb 100644 --- a/backend/Actions/BuddyBoss/BuddyBossController.php +++ b/backend/Actions/BuddyBoss/BuddyBossController.php @@ -19,16 +19,6 @@ public static function pluginActive($option = null) return (bool) (class_exists('BuddyPress')); } - public static function authorizeBuddyBoss() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'BuddyBoss')); - } - public static function getAllGroups() { include_once ABSPATH . 'wp-admin/includes/plugin.php'; diff --git a/backend/Actions/BuddyBoss/Routes.php b/backend/Actions/BuddyBoss/Routes.php index 25f1b9535..0ac3fd4dd 100644 --- a/backend/Actions/BuddyBoss/Routes.php +++ b/backend/Actions/BuddyBoss/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\BuddyBoss\BuddyBossController; use BitApps\Integrations\Core\Util\Route; -Route::post('buddyBoss_authorize', [BuddyBossController::class, 'authorizeBuddyBoss']); Route::post('fetch_all_group', [BuddyBossController::class, 'getAllGroups']); Route::post('fetch_all_user', [BuddyBossController::class, 'getAllUser']); Route::post('fetch_all_forum', [BuddyBossController::class, 'getAllForums']); diff --git a/backend/Actions/CreatorLms/CreatorLmsController.php b/backend/Actions/CreatorLms/CreatorLmsController.php index 06d7b0864..eb68a87d6 100644 --- a/backend/Actions/CreatorLms/CreatorLmsController.php +++ b/backend/Actions/CreatorLms/CreatorLmsController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function creatorLmsAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/CreatorLms/Routes.php b/backend/Actions/CreatorLms/Routes.php index 044f1b6da..2b167253f 100644 --- a/backend/Actions/CreatorLms/Routes.php +++ b/backend/Actions/CreatorLms/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\CreatorLms\CreatorLmsController; use BitApps\Integrations\Core\Util\Route; -Route::post('creator_lms_authorize', [CreatorLmsController::class, 'creatorLmsAuthorize']); diff --git a/backend/Actions/Dokan/DokanController.php b/backend/Actions/Dokan/DokanController.php index b02fbfed4..751e440c8 100644 --- a/backend/Actions/Dokan/DokanController.php +++ b/backend/Actions/Dokan/DokanController.php @@ -14,21 +14,6 @@ */ class DokanController { - public function authentication() - { - if (self::checkedDokanExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install Dokan', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedDokanExists() { if (!is_plugin_active('dokan-lite/dokan.php')) { diff --git a/backend/Actions/Dokan/Routes.php b/backend/Actions/Dokan/Routes.php index c9d963de7..ff2383f2f 100644 --- a/backend/Actions/Dokan/Routes.php +++ b/backend/Actions/Dokan/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Dokan\DokanController; use BitApps\Integrations\Core\Util\Route; -Route::post('dokan_authentication', [DokanController::class, 'authentication']); Route::post('dokan_fetch_eu_fields', [DokanController::class, 'getEUFields']); Route::post('dokan_fetch_vendors', [DokanController::class, 'getAllVendors']); diff --git a/backend/Actions/FluentCart/FluentCartController.php b/backend/Actions/FluentCart/FluentCartController.php index 4988b2c66..6d03b4862 100644 --- a/backend/Actions/FluentCart/FluentCartController.php +++ b/backend/Actions/FluentCart/FluentCartController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function fluentCartAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function refreshProducts() { self::isExists(); diff --git a/backend/Actions/FluentCart/Routes.php b/backend/Actions/FluentCart/Routes.php index 3dbcf1a05..74089b256 100644 --- a/backend/Actions/FluentCart/Routes.php +++ b/backend/Actions/FluentCart/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\FluentCart\FluentCartController; use BitApps\Integrations\Core\Util\Route; -Route::post('fluent_cart_authorize', [FluentCartController::class, 'fluentCartAuthorize']); Route::post('refresh_fluent_cart_products', [FluentCartController::class, 'refreshProducts']); Route::post('refresh_fluent_cart_products_categories', [FluentCartController::class, 'refreshProductCategories']); Route::post('refresh_fluent_cart_customers', [FluentCartController::class, 'refreshCustomers']); diff --git a/backend/Actions/FluentCrm/FluentCrmController.php b/backend/Actions/FluentCrm/FluentCrmController.php index f7c450a9c..984c74000 100644 --- a/backend/Actions/FluentCrm/FluentCrmController.php +++ b/backend/Actions/FluentCrm/FluentCrmController.php @@ -147,24 +147,6 @@ public static function fluentCrmFields() wp_send_json_success($response, 200); } - /** - * @return true Fluent crm are exists - */ - public static function fluentCrmAuthorize() - { - if (self::checkedExistsFluentCRM()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install Fluent CRM', - 'bit-integrations' - ), - 400 - ); - } - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/FluentCrm/Routes.php b/backend/Actions/FluentCrm/Routes.php index a125716dc..391959259 100644 --- a/backend/Actions/FluentCrm/Routes.php +++ b/backend/Actions/FluentCrm/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\FluentCrm\FluentCrmController; use BitApps\Integrations\Core\Util\Route; -Route::post('fluent_crm_authorize', [FluentCrmController::class, 'fluentCrmAuthorize']); Route::post('refresh_fluent_crm_lists', [FluentCrmController::class, 'fluentCrmLists']); Route::post('refresh_fluent_crm_tags', [FluentCrmController::class, 'fluentCrmTags']); Route::post('fluent_crm_headers', [FluentCrmController::class, 'fluentCrmFields']); diff --git a/backend/Actions/GamiPress/GamiPressController.php b/backend/Actions/GamiPress/GamiPressController.php index 2cecde79e..a0d0febc0 100644 --- a/backend/Actions/GamiPress/GamiPressController.php +++ b/backend/Actions/GamiPress/GamiPressController.php @@ -31,16 +31,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeGamiPress() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'GamiPress')); - } - public static function getCourses() { $courses = []; diff --git a/backend/Actions/GamiPress/Routes.php b/backend/Actions/GamiPress/Routes.php index d5bec37ec..1bc67ad84 100644 --- a/backend/Actions/GamiPress/Routes.php +++ b/backend/Actions/GamiPress/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\GamiPress\GamiPressController; use BitApps\Integrations\Core\Util\Route; -Route::post('gamiPress_authorize', [GamiPressController::class, 'authorizeGamiPress']); Route::post('gamiPress_fetch_all_rank_type', [GamiPressController::class, 'fetchAllRankType']); Route::post('gamiPress_fetch_all_rank_by_type', [GamiPressController::class, 'fetchAllRankBYType']); Route::post('gamiPress_fetch_all_achievement_type', [GamiPressController::class, 'fetchAllAchievementType']); diff --git a/backend/Actions/GiveWp/GiveWpController.php b/backend/Actions/GiveWp/GiveWpController.php index eb456e4dd..2b3388ccb 100644 --- a/backend/Actions/GiveWp/GiveWpController.php +++ b/backend/Actions/GiveWp/GiveWpController.php @@ -15,16 +15,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeGiveWp() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'GiveWp')); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/GiveWp/Routes.php b/backend/Actions/GiveWp/Routes.php index e03dd9e81..dada9b0b5 100644 --- a/backend/Actions/GiveWp/Routes.php +++ b/backend/Actions/GiveWp/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\GiveWp\GiveWpController; use BitApps\Integrations\Core\Util\Route; -Route::post('giveWp_authorize', [GiveWpController::class, 'authorizeGiveWp']); diff --git a/backend/Actions/JetEngine/JetEngineController.php b/backend/Actions/JetEngine/JetEngineController.php index 8b2c6458a..62fe2841a 100644 --- a/backend/Actions/JetEngine/JetEngineController.php +++ b/backend/Actions/JetEngine/JetEngineController.php @@ -16,21 +16,6 @@ */ class JetEngineController { - public function authentication() - { - if (self::checkedJetEngineExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install JetEngine', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedJetEngineExists() { if (!is_plugin_active('jet-engine/jet-engine.php')) { diff --git a/backend/Actions/JetEngine/Routes.php b/backend/Actions/JetEngine/Routes.php index 4025eecaa..27c9cc005 100644 --- a/backend/Actions/JetEngine/Routes.php +++ b/backend/Actions/JetEngine/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\JetEngine\JetEngineController; use BitApps\Integrations\Core\Util\Route; -Route::post('jetEngine_authentication', [JetEngineController::class, 'authentication']); Route::post('jetEngine_menu_positions', [JetEngineController::class, 'getMenuPosition']); Route::post('jetEngine_menu_icons', [JetEngineController::class, 'getMenuIcons']); Route::post('jetEngine_supports', [JetEngineController::class, 'getSupports']); diff --git a/backend/Actions/LearnDash/LearnDashController.php b/backend/Actions/LearnDash/LearnDashController.php index 205ab5a25..59eef90bd 100644 --- a/backend/Actions/LearnDash/LearnDashController.php +++ b/backend/Actions/LearnDash/LearnDashController.php @@ -34,17 +34,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeRestrictContent() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'LearnDash')); - } - public static function getCourses() { $courses = []; diff --git a/backend/Actions/LearnDash/Routes.php b/backend/Actions/LearnDash/Routes.php index 6bdc7fc26..6e1cfad55 100644 --- a/backend/Actions/LearnDash/Routes.php +++ b/backend/Actions/LearnDash/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\LearnDash\LearnDashController; use BitApps\Integrations\Core\Util\Route; -Route::post('learnDash_authorize', [LearnDashController::class, 'authorizeRestrictContent']); Route::post('learDash_fetch_all_course', [LearnDashController::class, 'getCourses']); Route::post('learDash_fetch_all_group', [LearnDashController::class, 'learDashFetchAllGroup']); Route::post('learDash_fetch_all_course_of_lesson', [LearnDashController::class, 'learDashFetchAllCourseOfLesson']); diff --git a/backend/Actions/LifterLms/LifterLmsController.php b/backend/Actions/LifterLms/LifterLmsController.php index 9c01db46d..8f0c05d2e 100644 --- a/backend/Actions/LifterLms/LifterLmsController.php +++ b/backend/Actions/LifterLms/LifterLmsController.php @@ -17,15 +17,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeLifterLms() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'LifterLms')); - } - public static function getAllLesson() { $lessonParams = [ diff --git a/backend/Actions/LifterLms/Routes.php b/backend/Actions/LifterLms/Routes.php index 7e7ef7eb6..c973451b1 100644 --- a/backend/Actions/LifterLms/Routes.php +++ b/backend/Actions/LifterLms/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\LifterLms\LifterLmsController; use BitApps\Integrations\Core\Util\Route; -Route::post('lifterLms_authorize', [LifterLmsController::class, 'authorizeLifterLms']); Route::post('lifterLms_fetch_all_lesson', [LifterLmsController::class, 'getAllLesson']); Route::post('lifterLms_fetch_all_section', [LifterLmsController::class, 'getAllSection']); Route::post('lifterLms_fetch_all_course', [LifterLmsController::class, 'getAllLifterLmsCourse']); diff --git a/backend/Actions/MailMint/MailMintController.php b/backend/Actions/MailMint/MailMintController.php index 6b42446e8..d3b4c2a11 100644 --- a/backend/Actions/MailMint/MailMintController.php +++ b/backend/Actions/MailMint/MailMintController.php @@ -17,16 +17,6 @@ public static function pluginActive() ; } - public static function authorizeMailMint() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Mail Mint')); - } - public static function allCustomFields() { if (class_exists('Mint\MRM\DataBase\Models\ContactGroupModel')) { diff --git a/backend/Actions/MailMint/Routes.php b/backend/Actions/MailMint/Routes.php index ca2c9bdba..fe4eb915a 100644 --- a/backend/Actions/MailMint/Routes.php +++ b/backend/Actions/MailMint/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\MailMint\MailMintController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailmint_authorize', [MailMintController::class, 'authorizeMailMint']); Route::post('fetch_all_mail_mint_list', [MailMintController::class, 'getAllList']); Route::post('fetch_all_mail_mint_tags', [MailMintController::class, 'getAllTags']); Route::post('fetch_all_mail_mint_custom_fields', [MailMintController::class, 'allCustomFields']); diff --git a/backend/Actions/MailPoet/MailPoetController.php b/backend/Actions/MailPoet/MailPoetController.php index 634600cc2..7e89bb98a 100644 --- a/backend/Actions/MailPoet/MailPoetController.php +++ b/backend/Actions/MailPoet/MailPoetController.php @@ -34,17 +34,6 @@ public static function isExists() } } - /** - * Process ajax request for generate_token - * - * @return JSON zoho crm api response and status - */ - public static function mailPoetAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - /** * Process ajax request for refresh crm modules * diff --git a/backend/Actions/MailPoet/Routes.php b/backend/Actions/MailPoet/Routes.php index 63682515d..4f5930d0b 100644 --- a/backend/Actions/MailPoet/Routes.php +++ b/backend/Actions/MailPoet/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\MailPoet\MailPoetController; use BitApps\Integrations\Core\Util\Route; -Route::post('mail_poet_authorize', [MailPoetController::class, 'mailPoetAuthorize']); Route::post('refresh_news_letter', [MailPoetController::class, 'refreshNeswLetter']); Route::post('mail_poet_list_headers', [MailPoetController::class, 'mailPoetListHeaders']); diff --git a/backend/Actions/MailerPress/MailerPressController.php b/backend/Actions/MailerPress/MailerPressController.php index 3a2610e4e..e3dd87fa6 100644 --- a/backend/Actions/MailerPress/MailerPressController.php +++ b/backend/Actions/MailerPress/MailerPressController.php @@ -33,17 +33,6 @@ public static function isExists() } } - /** - * Process ajax request for authorization - * - * @return JSON response - */ - public static function mailerPressAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - /** * Process ajax request for refresh lists * diff --git a/backend/Actions/MailerPress/Routes.php b/backend/Actions/MailerPress/Routes.php index 19708b49f..186e4bb9f 100644 --- a/backend/Actions/MailerPress/Routes.php +++ b/backend/Actions/MailerPress/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\MailerPress\MailerPressController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailer_press_authorize', [MailerPressController::class, 'mailerPressAuthorize']); Route::post('refresh_mailer_press_lists', [MailerPressController::class, 'refreshLists']); Route::post('refresh_mailer_press_tags', [MailerPressController::class, 'refreshTags']); diff --git a/backend/Actions/Mailster/MailsterController.php b/backend/Actions/Mailster/MailsterController.php index 2aa7396ee..d6c52e4f6 100644 --- a/backend/Actions/Mailster/MailsterController.php +++ b/backend/Actions/Mailster/MailsterController.php @@ -16,21 +16,6 @@ */ class MailsterController { - public function authentication() - { - if (self::checkedMailsterExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install Mailster', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedMailsterExists() { if (!is_plugin_active('mailster/mailster.php')) { diff --git a/backend/Actions/Mailster/Routes.php b/backend/Actions/Mailster/Routes.php index 6ba632c34..16921ed67 100644 --- a/backend/Actions/Mailster/Routes.php +++ b/backend/Actions/Mailster/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Mailster\MailsterController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailster_authentication', [MailsterController::class, 'authentication']); Route::post('mailster_fields', [MailsterController::class, 'getMailsterFields']); Route::post('mailster_lists', [MailsterController::class, 'getMailsterLists']); Route::post('mailster_tags', [MailsterController::class, 'getMailsterTags']); diff --git a/backend/Actions/MasterStudyLms/MasterStudyLmsController.php b/backend/Actions/MasterStudyLms/MasterStudyLmsController.php index 0fc31372a..8b0d7e6d0 100644 --- a/backend/Actions/MasterStudyLms/MasterStudyLmsController.php +++ b/backend/Actions/MasterStudyLms/MasterStudyLmsController.php @@ -16,16 +16,6 @@ public static function pluginActive() ); } - public static function authorizeMasterStudyLms() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'MasterStudyLms')); - } - public static function getAllCourse() { if (self::pluginActive()) { diff --git a/backend/Actions/MasterStudyLms/Routes.php b/backend/Actions/MasterStudyLms/Routes.php index 5b6793625..b52146da8 100644 --- a/backend/Actions/MasterStudyLms/Routes.php +++ b/backend/Actions/MasterStudyLms/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\MasterStudyLms\MasterStudyLmsController; use BitApps\Integrations\Core\Util\Route; -Route::post('MasterStudyLms_authorize', [MasterStudyLmsController::class, 'authorizeMasterStudyLms']); Route::post('mslms_fetch_all_course', [MasterStudyLmsController::class, 'getAllCourse']); Route::post('msLms_fetch_all_lesson', [MasterStudyLmsController::class, 'getAllLesson']); Route::post('msLms_fetch_all_quiz', [MasterStudyLmsController::class, 'getAllQuizByCourse']); diff --git a/backend/Actions/Memberpress/MemberpressController.php b/backend/Actions/Memberpress/MemberpressController.php index 417f1ec1d..38b546c1b 100644 --- a/backend/Actions/Memberpress/MemberpressController.php +++ b/backend/Actions/Memberpress/MemberpressController.php @@ -23,17 +23,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeMemberpress() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Memberpress')); - } - public function getAllMembership($label = null, $option_code = 'MPPRODUCT', $args = []) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; diff --git a/backend/Actions/Memberpress/Routes.php b/backend/Actions/Memberpress/Routes.php index d47ee3130..dbe7b5f0d 100644 --- a/backend/Actions/Memberpress/Routes.php +++ b/backend/Actions/Memberpress/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Memberpress\MemberpressController; use BitApps\Integrations\Core\Util\Route; -Route::post('memberpress_authorize', [MemberpressController::class, 'authorizeMemberpress']); Route::post('fetch_all_membership', [MemberpressController::class, 'getAllMembership']); Route::post('fetch_all_payment_gateway', [MemberpressController::class, 'allPaymentGateway']); diff --git a/backend/Actions/Newsletter/NewsletterController.php b/backend/Actions/Newsletter/NewsletterController.php index 016a6bcd7..8eb043bc5 100644 --- a/backend/Actions/Newsletter/NewsletterController.php +++ b/backend/Actions/Newsletter/NewsletterController.php @@ -13,21 +13,6 @@ */ class NewsletterController { - public function authentication() - { - if (self::checkedNewsletterExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install Newsletter', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedNewsletterExists() { if (!is_plugin_active('newsletter/plugin.php')) { diff --git a/backend/Actions/Newsletter/Routes.php b/backend/Actions/Newsletter/Routes.php index 797f4cea0..50147bf3b 100644 --- a/backend/Actions/Newsletter/Routes.php +++ b/backend/Actions/Newsletter/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\Newsletter\NewsletterController; use BitApps\Integrations\Core\Util\Route; -Route::post('newsletter_authentication', [NewsletterController::class, 'authentication']); diff --git a/backend/Actions/NotificationX/NotificationXController.php b/backend/Actions/NotificationX/NotificationXController.php index 1e428abdf..67ccc1a6e 100644 --- a/backend/Actions/NotificationX/NotificationXController.php +++ b/backend/Actions/NotificationX/NotificationXController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function notificationXAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public static function getNotificationsBySource($requestsParams) { self::isExists(); diff --git a/backend/Actions/NotificationX/Routes.php b/backend/Actions/NotificationX/Routes.php index 340d4dff4..cdc6084bc 100644 --- a/backend/Actions/NotificationX/Routes.php +++ b/backend/Actions/NotificationX/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\NotificationX\NotificationXController; use BitApps\Integrations\Core\Util\Route; -Route::post('notificationx_authorize', [NotificationXController::class, 'notificationXAuthorize']); Route::post('notificationx_get_notifications_by_source', [NotificationXController::class, 'getNotificationsBySource']); diff --git a/backend/Actions/PaidMembershipPro/PaidMembershipProController.php b/backend/Actions/PaidMembershipPro/PaidMembershipProController.php index 03d55232f..fd0571845 100644 --- a/backend/Actions/PaidMembershipPro/PaidMembershipProController.php +++ b/backend/Actions/PaidMembershipPro/PaidMembershipProController.php @@ -16,15 +16,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeMemberpress() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Paid Membership')); - } - public static function getAllPaidMembershipProLevel() { global $wpdb; diff --git a/backend/Actions/PaidMembershipPro/Routes.php b/backend/Actions/PaidMembershipPro/Routes.php index 3d42c0bab..d9a2eb876 100644 --- a/backend/Actions/PaidMembershipPro/Routes.php +++ b/backend/Actions/PaidMembershipPro/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\PaidMembershipPro\PaidMembershipProController; use BitApps\Integrations\Core\Util\Route; -Route::post('paid_membership_pro_authorize', [PaidMembershipProController::class, 'authorizeMemberpress']); Route::post('fetch_all_paid_membership_pro_level', [PaidMembershipProController::class, 'getAllPaidMembershipProLevel']); diff --git a/backend/Actions/PeepSo/PeepSoController.php b/backend/Actions/PeepSo/PeepSoController.php index 77fa89a82..e27348989 100644 --- a/backend/Actions/PeepSo/PeepSoController.php +++ b/backend/Actions/PeepSo/PeepSoController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function peepSoAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/PeepSo/Routes.php b/backend/Actions/PeepSo/Routes.php index 1c41afea4..6d9568b9e 100644 --- a/backend/Actions/PeepSo/Routes.php +++ b/backend/Actions/PeepSo/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\PeepSo\PeepSoController; use BitApps\Integrations\Core\Util\Route; -Route::post('peep_so_authorize', [PeepSoController::class, 'peepSoAuthorize']); diff --git a/backend/Actions/PropovoiceCRM/PropovoiceCRMController.php b/backend/Actions/PropovoiceCRM/PropovoiceCRMController.php index ecbc9807e..ab852a4ab 100644 --- a/backend/Actions/PropovoiceCRM/PropovoiceCRMController.php +++ b/backend/Actions/PropovoiceCRM/PropovoiceCRMController.php @@ -13,16 +13,6 @@ public static function pluginActive() ; } - public static function authorizePropovoiceCrm() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Propovoice CRM')); - } - public static function leadTags() { global $wpdb; diff --git a/backend/Actions/PropovoiceCRM/Routes.php b/backend/Actions/PropovoiceCRM/Routes.php index 1fa0e61eb..db9d0e136 100644 --- a/backend/Actions/PropovoiceCRM/Routes.php +++ b/backend/Actions/PropovoiceCRM/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\PropovoiceCRM\PropovoiceCRMController; use BitApps\Integrations\Core\Util\Route; -Route::post('propovoice_authorize', [PropovoiceCRMController::class, 'authorizePropovoiceCrm']); Route::post('propovoice_crm_lead_tags', [PropovoiceCRMController::class, 'leadTags']); Route::post('propovoice_crm_lead_label', [PropovoiceCRMController::class, 'leadLabel']); diff --git a/backend/Actions/RestrictContent/RestrictContentController.php b/backend/Actions/RestrictContent/RestrictContentController.php index 234b9fcdb..e038e15ca 100644 --- a/backend/Actions/RestrictContent/RestrictContentController.php +++ b/backend/Actions/RestrictContent/RestrictContentController.php @@ -33,17 +33,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeRestrictContent() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Restrict Content')); - } - public static function getAllLevels() { $levels = rcp_get_membership_levels(['number' => 999]); diff --git a/backend/Actions/RestrictContent/Routes.php b/backend/Actions/RestrictContent/Routes.php index aea4a11bb..c6df4993e 100644 --- a/backend/Actions/RestrictContent/Routes.php +++ b/backend/Actions/RestrictContent/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\RestrictContent\RestrictContentController; use BitApps\Integrations\Core\Util\Route; -Route::post('restrict_authorize', [RestrictContentController::class, 'authorizeRestrictContent']); Route::get('restrict_get_all_levels', [RestrictContentController::class, 'getAllLevels']); diff --git a/backend/Actions/SliceWp/Routes.php b/backend/Actions/SliceWp/Routes.php index 9445e363f..a193df6ba 100644 --- a/backend/Actions/SliceWp/Routes.php +++ b/backend/Actions/SliceWp/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\SliceWp\SliceWpController; use BitApps\Integrations\Core\Util\Route; -Route::post('slicewp_authorize', [SliceWpController::class, 'authorizeSliceWp']); diff --git a/backend/Actions/SliceWp/SliceWpController.php b/backend/Actions/SliceWp/SliceWpController.php index ea7a40ccf..00811fdaf 100644 --- a/backend/Actions/SliceWp/SliceWpController.php +++ b/backend/Actions/SliceWp/SliceWpController.php @@ -15,16 +15,6 @@ public static function pluginActive($option = null) return false; } - public static function authorizeSliceWp() - { - if (self::pluginActive()) { - wp_send_json_success(true, 200); - } - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'SliceWp affiliate')); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/SureMembers/Routes.php b/backend/Actions/SureMembers/Routes.php index e8ca8aadb..3e06bde46 100644 --- a/backend/Actions/SureMembers/Routes.php +++ b/backend/Actions/SureMembers/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\SureMembers\SureMembersController; use BitApps\Integrations\Core\Util\Route; -Route::post('sureMembers_authentication', [SureMembersController::class, 'authentication']); Route::post('sureMembers_fetch_groups', [SureMembersController::class, 'getGroups']); diff --git a/backend/Actions/SureMembers/SureMembersController.php b/backend/Actions/SureMembers/SureMembersController.php index a15ce0f12..c57952758 100644 --- a/backend/Actions/SureMembers/SureMembersController.php +++ b/backend/Actions/SureMembers/SureMembersController.php @@ -14,21 +14,6 @@ */ class SureMembersController { - public function authentication() - { - if (self::checkedSureMembersExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install SureMembers', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedSureMembersExists() { if (!is_plugin_active('suremembers/suremembers.php')) { diff --git a/backend/Actions/TeamsForWooCommerceMemberships/Routes.php b/backend/Actions/TeamsForWooCommerceMemberships/Routes.php index 33fae41b4..dd7a5c12d 100644 --- a/backend/Actions/TeamsForWooCommerceMemberships/Routes.php +++ b/backend/Actions/TeamsForWooCommerceMemberships/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\TeamsForWooCommerceMemberships\TeamsForWooCommerceMembershipsController; use BitApps\Integrations\Core\Util\Route; -Route::post('teams_for_wc_memberships_authorize', [TeamsForWooCommerceMembershipsController::class, 'teamsForWooCommerceMembershipsAuthorize']); Route::post('teams_for_wc_memberships_refresh_teams', [TeamsForWooCommerceMembershipsController::class, 'refreshTeams']); Route::post('teams_for_wc_memberships_refresh_member_roles', [TeamsForWooCommerceMembershipsController::class, 'refreshMemberRoles']); diff --git a/backend/Actions/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsController.php b/backend/Actions/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsController.php index cea18a445..f6760ee40 100644 --- a/backend/Actions/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsController.php +++ b/backend/Actions/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function teamsForWooCommerceMembershipsAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function refreshTeams() { self::isExists(); diff --git a/backend/Actions/TheEventsCalendar/Routes.php b/backend/Actions/TheEventsCalendar/Routes.php index 4dea9e749..99e11cb99 100644 --- a/backend/Actions/TheEventsCalendar/Routes.php +++ b/backend/Actions/TheEventsCalendar/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\TheEventsCalendar\TheEventsCalendarController; use BitApps\Integrations\Core\Util\Route; -Route::post('the_events_calendar_authentication', [TheEventsCalendarController::class, 'authentication']); Route::post('get_the_events_calendar_events', [TheEventsCalendarController::class, 'getAllEvents']); diff --git a/backend/Actions/TheEventsCalendar/TheEventsCalendarController.php b/backend/Actions/TheEventsCalendar/TheEventsCalendarController.php index 8f2b10bea..52eb46444 100644 --- a/backend/Actions/TheEventsCalendar/TheEventsCalendarController.php +++ b/backend/Actions/TheEventsCalendar/TheEventsCalendarController.php @@ -13,15 +13,6 @@ */ class TheEventsCalendarController { - public function authentication() - { - if (self::checkedTheEventsCalendarExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error(__('The Events Calendar and/or Event Tickets are not active or installed!', 'bit-integrations'), 400); - } - } - public static function checkedTheEventsCalendarExists() { if (is_plugin_active('the-events-calendar/the-events-calendar.php') && is_plugin_active('event-tickets/event-tickets.php')) { diff --git a/backend/Actions/TutorLms/Routes.php b/backend/Actions/TutorLms/Routes.php index a6f2a3030..66471c1a3 100644 --- a/backend/Actions/TutorLms/Routes.php +++ b/backend/Actions/TutorLms/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\TutorLms\TutorLmsController; use BitApps\Integrations\Core\Util\Route; -Route::post('tutor_authorize', [TutorLmsController::class, 'TutorAuthorize']); Route::get('tutor_all_course', [TutorLmsController::class, 'getAllCourse']); Route::get('tutor_all_lesson', [TutorLmsController::class, 'getAllLesson']); diff --git a/backend/Actions/TutorLms/TutorLmsController.php b/backend/Actions/TutorLms/TutorLmsController.php index 6028add83..4ef65d865 100644 --- a/backend/Actions/TutorLms/TutorLmsController.php +++ b/backend/Actions/TutorLms/TutorLmsController.php @@ -20,22 +20,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @return JSON zoho crm api response and status - */ - public static function TutorAuthorize() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (is_plugin_active('tutor/tutor.php')) { - wp_send_json_success(true, 200); - } - - // translators: %s: Plugin name - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'Tutor LMS')); - } - public static function getAllLesson() { if (!\function_exists('tutor')) { diff --git a/backend/Actions/UltimateAffiliatePro/Routes.php b/backend/Actions/UltimateAffiliatePro/Routes.php index 6af68bddd..24b9afe3c 100644 --- a/backend/Actions/UltimateAffiliatePro/Routes.php +++ b/backend/Actions/UltimateAffiliatePro/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\UltimateAffiliatePro\UltimateAffiliateProController; use BitApps\Integrations\Core\Util\Route; -Route::post('ultimate_affiliate_pro_authorize', [UltimateAffiliateProController::class, 'authorizeUltimateAffiliatePro']); diff --git a/backend/Actions/UltimateAffiliatePro/UltimateAffiliateProController.php b/backend/Actions/UltimateAffiliatePro/UltimateAffiliateProController.php index ef2fca89c..da3da03b3 100644 --- a/backend/Actions/UltimateAffiliatePro/UltimateAffiliateProController.php +++ b/backend/Actions/UltimateAffiliatePro/UltimateAffiliateProController.php @@ -44,13 +44,6 @@ public static function isExists() } } - public static function authorizeUltimateAffiliatePro() - { - self::isExists(); - - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/UserRegistrationMembership/Routes.php b/backend/Actions/UserRegistrationMembership/Routes.php index 0c166eca8..c95db56df 100644 --- a/backend/Actions/UserRegistrationMembership/Routes.php +++ b/backend/Actions/UserRegistrationMembership/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\UserRegistrationMembership\UserRegistrationMembershipController; use BitApps\Integrations\Core\Util\Route; -Route::post('user_registration_authorize', [UserRegistrationMembershipController::class, 'userRegistrationAuthorize']); Route::post('refresh_user_registration_forms', [UserRegistrationMembershipController::class, 'refreshForms']); Route::post('refresh_user_registration_form_fields', [UserRegistrationMembershipController::class, 'refreshFormFields']); diff --git a/backend/Actions/UserRegistrationMembership/UserRegistrationMembershipController.php b/backend/Actions/UserRegistrationMembership/UserRegistrationMembershipController.php index 1551425f3..ec56e7555 100644 --- a/backend/Actions/UserRegistrationMembership/UserRegistrationMembershipController.php +++ b/backend/Actions/UserRegistrationMembership/UserRegistrationMembershipController.php @@ -9,12 +9,6 @@ */ class UserRegistrationMembershipController { - public static function userRegistrationAuthorize() - { - self::checkPluginExists(); - wp_send_json_success(true); - } - public static function refreshForms() { self::checkPluginExists(); diff --git a/backend/Actions/Voxel/Routes.php b/backend/Actions/Voxel/Routes.php index 6021c4d9d..7cddc583f 100644 --- a/backend/Actions/Voxel/Routes.php +++ b/backend/Actions/Voxel/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Voxel\VoxelController; use BitApps\Integrations\Core\Util\Route; -Route::post('voxel_authentication', [VoxelController::class, 'authentication']); Route::post('get_voxel_post_types', [VoxelController::class, 'getPostTypes']); Route::post('get_voxel_post_fields', [VoxelController::class, 'getPostFields']); Route::post('get_voxel_posts', [VoxelController::class, 'getPosts']); diff --git a/backend/Actions/Voxel/VoxelController.php b/backend/Actions/Voxel/VoxelController.php index d4d1c8421..ceb06b01e 100644 --- a/backend/Actions/Voxel/VoxelController.php +++ b/backend/Actions/Voxel/VoxelController.php @@ -14,11 +14,6 @@ */ class VoxelController { - public function authentication() - { - return self::checkIfVoxelExists(); - } - public static function checkIfVoxelExists() { if (wp_get_theme()->get_template() === 'voxel') { diff --git a/backend/Actions/WCAffiliate/Routes.php b/backend/Actions/WCAffiliate/Routes.php index 6e413ab3c..cf98ca247 100644 --- a/backend/Actions/WCAffiliate/Routes.php +++ b/backend/Actions/WCAffiliate/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\WCAffiliate\WCAffiliateController; use BitApps\Integrations\Core\Util\Route; -Route::post('wc_affiliate_authorize', [WCAffiliateController::class, 'authorize']); diff --git a/backend/Actions/WCAffiliate/WCAffiliateController.php b/backend/Actions/WCAffiliate/WCAffiliateController.php index 017ebf8f3..f71e57326 100644 --- a/backend/Actions/WCAffiliate/WCAffiliateController.php +++ b/backend/Actions/WCAffiliate/WCAffiliateController.php @@ -13,18 +13,6 @@ */ class WCAffiliateController { - public static function authorize() - { - if (!self::isPluginReady()) { - wp_send_json_error( - __('WC Affiliate is not activated or not installed', 'bit-integrations'), - 400 - ); - } - - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; @@ -45,11 +33,4 @@ public function execute($integrationData, $fieldValues) return $wcAffiliateResponse; } - - private static function isPluginReady() - { - return class_exists('\WC_Affiliate\Models\Affiliate') - && class_exists('\WC_Affiliate\Models\Referral') - && class_exists('\WC_Affiliate\Models\Transaction'); - } } diff --git a/backend/Actions/WPCafe/Routes.php b/backend/Actions/WPCafe/Routes.php index 8eb32b6b1..799eae194 100644 --- a/backend/Actions/WPCafe/Routes.php +++ b/backend/Actions/WPCafe/Routes.php @@ -7,4 +7,3 @@ use BitApps\Integrations\Actions\WPCafe\WPCafeController; use BitApps\Integrations\Core\Util\Route; -Route::post('wpcafe_authorize', [WPCafeController::class, 'wpcafeAuthorize']); diff --git a/backend/Actions/WPCafe/WPCafeController.php b/backend/Actions/WPCafe/WPCafeController.php index 980fb2a6a..bbe3a9ce8 100644 --- a/backend/Actions/WPCafe/WPCafeController.php +++ b/backend/Actions/WPCafe/WPCafeController.php @@ -26,12 +26,6 @@ public static function isExists() } } - public static function wpcafeAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/WPCourseware/Routes.php b/backend/Actions/WPCourseware/Routes.php index e10ca3417..78341a467 100644 --- a/backend/Actions/WPCourseware/Routes.php +++ b/backend/Actions/WPCourseware/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\WPCourseware\WPCoursewareController; use BitApps\Integrations\Core\Util\Route; -Route::post('wpCourseware_authorize', [WPCoursewareController::class, 'wpCoursewareAuthorize']); Route::post('wpCourseware_courses', [WPCoursewareController::class, 'WPCWCourses']); diff --git a/backend/Actions/WPCourseware/WPCoursewareController.php b/backend/Actions/WPCourseware/WPCoursewareController.php index 5f2929ef6..74a2f99ea 100644 --- a/backend/Actions/WPCourseware/WPCoursewareController.php +++ b/backend/Actions/WPCourseware/WPCoursewareController.php @@ -14,15 +14,6 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function wpCoursewareAuthorize() - { - if (!is_plugin_active('wp-courseware/wp-courseware.php')) { - wp_send_json_error(__('WP Courseware Plugin is not active or installed', 'bit-integrations'), 400); - } else { - wp_send_json_success(true); - } - } - public static function WPCWCourses() { if (!is_plugin_active('wp-courseware/wp-courseware.php')) { diff --git a/backend/Actions/WPForo/Routes.php b/backend/Actions/WPForo/Routes.php index d925f5f93..ce345d0d6 100644 --- a/backend/Actions/WPForo/Routes.php +++ b/backend/Actions/WPForo/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\WPForo\WPForoController; use BitApps\Integrations\Core\Util\Route; -Route::post('wpforo_authentication', [WPForoController::class, 'authentication']); Route::post('wpforo_fetch_reputations', [WPForoController::class, 'getReputations']); Route::post('wpforo_fetch_groups', [WPForoController::class, 'getGroups']); Route::post('wpforo_fetch_forums', [WPForoController::class, 'getForums']); diff --git a/backend/Actions/WPForo/WPForoController.php b/backend/Actions/WPForo/WPForoController.php index 3e5fdafb7..0e006d702 100644 --- a/backend/Actions/WPForo/WPForoController.php +++ b/backend/Actions/WPForo/WPForoController.php @@ -13,21 +13,6 @@ */ class WPForoController { - public function authentication() - { - if (self::checkedWPForoExists()) { - wp_send_json_success(true); - } else { - wp_send_json_error( - __( - 'Please! Install WPForo', - 'bit-integrations' - ), - 400 - ); - } - } - public static function checkedWPForoExists() { if (!is_plugin_active('wpforo/wpforo.php')) { diff --git a/backend/Actions/WishlistMember/Routes.php b/backend/Actions/WishlistMember/Routes.php index 9e467dfe9..64798bded 100644 --- a/backend/Actions/WishlistMember/Routes.php +++ b/backend/Actions/WishlistMember/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\WishlistMember\WishlistMemberController; use BitApps\Integrations\Core\Util\Route; -Route::post('wishlist_authorization', [WishlistMemberController::class, 'authorization']); Route::post('get_wishlist_levels', [WishlistMemberController::class, 'getLevels']); diff --git a/backend/Actions/WishlistMember/WishlistMemberController.php b/backend/Actions/WishlistMember/WishlistMemberController.php index 1196f88fa..83b24fcbe 100644 --- a/backend/Actions/WishlistMember/WishlistMemberController.php +++ b/backend/Actions/WishlistMember/WishlistMemberController.php @@ -23,24 +23,6 @@ public static function isPluginInstalled() return class_exists('WLMAPI') || class_exists('WishListMember'); } - /** - * Validate if WishlistMember plugin exists or not. - */ - public static function authorization() - { - if (!self::isPluginInstalled()) { - wp_send_json_error( - __( - 'WishlistMember is not activated or not installed', - 'bit-integrations' - ), - 400 - ); - } - - wp_send_json_success(true); - } - /** * Get wishlist levels */ diff --git a/backend/Actions/WooCommerce/Routes.php b/backend/Actions/WooCommerce/Routes.php index 5afd5a265..5cdb4abe7 100644 --- a/backend/Actions/WooCommerce/Routes.php +++ b/backend/Actions/WooCommerce/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\WooCommerce\WooCommerceController; use BitApps\Integrations\Core\Util\Route; -Route::post('wc_authorize', [WooCommerceController::class, 'authorizeWC']); Route::post('wc_refresh_fields', [WooCommerceController::class, 'refreshFields']); Route::post('wc_search_products', [WooCommerceController::class, 'searchProjects']); Route::post('wc_get_all_subscriptions_products', [WooCommerceController::class, 'allSubscriptionsProducts']); diff --git a/backend/Actions/WooCommerce/WooCommerceController.php b/backend/Actions/WooCommerce/WooCommerceController.php index ff080981a..e50357338 100644 --- a/backend/Actions/WooCommerce/WooCommerceController.php +++ b/backend/Actions/WooCommerce/WooCommerceController.php @@ -19,18 +19,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - public static function authorizeWC() - { - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - if (is_plugin_active('woocommerce/woocommerce.php')) { - wp_send_json_success(true, 200); - } - - // translators: %s: Plugin name - // translators: %s: Placeholder value - wp_send_json_error(wp_sprintf(__('%s must be activated!', 'bit-integrations'), 'WooCommerce')); - } - public static function refreshFields($queryParams) { if (empty($queryParams->module)) { diff --git a/backend/Actions/WpErp/Routes.php b/backend/Actions/WpErp/Routes.php index c2e951bc5..2c4b1c0cc 100644 --- a/backend/Actions/WpErp/Routes.php +++ b/backend/Actions/WpErp/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\WpErp\WpErpController; use BitApps\Integrations\Core\Util\Route; -Route::post('wp_erp_authorize', [WpErpController::class, 'wpErpAuthorize']); Route::post('refresh_wp_erp_contact_groups', [WpErpController::class, 'refreshContactGroups']); Route::post('refresh_wp_erp_life_stages', [WpErpController::class, 'refreshLifeStages']); Route::post('refresh_wp_erp_departments', [WpErpController::class, 'refreshDepartments']); diff --git a/backend/Actions/WpErp/WpErpController.php b/backend/Actions/WpErp/WpErpController.php index 5d08cd517..097625f55 100644 --- a/backend/Actions/WpErp/WpErpController.php +++ b/backend/Actions/WpErp/WpErpController.php @@ -17,12 +17,6 @@ public static function isExists() } } - public static function wpErpAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - public function refreshContactGroups() { self::isExists(); diff --git a/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx index 4e674f68a..0fbbdbe96 100644 --- a/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx @@ -1,103 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import { deepCopy } from '../../../Utils/Helpers' -import bitsFetch from '../../../Utils/bitsFetch' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function AcademyLmsAuthorization({ academyLmsConf, setAcademyLmsConf, step, setStep, - setSnackbar + isInfo }) { -const [isAuthorized, setisAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'academy_lms_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Academy Lms Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(academyLmsConf) - newConf[e.target.name] = e.target.value - setAcademyLmsConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Academy Lms is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Academy Lms' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx index 5dcbe7a7b..4a1c505d0 100644 --- a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function AffiliateAuthorization({ - formID, affiliateConf, setAffiliateConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'affiliate_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Affiliate Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(affiliateConf) - newConf[e.target.name] = e.target.value - setAffiliateConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if LearnDash is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Affiliate plugin' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx index 5423524c9..912e3729d 100644 --- a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx @@ -1,81 +1,33 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { asgarosForumAuthentication } from './AsgarosForumCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function AsgarosForumAuthorization({ asgarosForumConf, setAsgarosForumConf, step, nextPage, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) - - const handleInput = e => { - const newConf = { ...asgarosForumConf } - newConf[e.target.name] = e.target.value - setAsgarosForumConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    -
    - {__('Integration Name:', 'bit-integrations')} -
    - -
    {error.name}
    - - - - {!isInfo && ( - <> - -
    - - - )} -
    + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumCommonFunc.js b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumCommonFunc.js index 41ec3ba59..62d4d814a 100644 --- a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumCommonFunc.js +++ b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumCommonFunc.js @@ -1,6 +1,4 @@ import { create } from 'mutative' -import bitsFetch from '../../../Utils/bitsFetch' -import { __ } from '../../../Utils/i18nwrap' import { asgarosForumActionFields } from './staticData' export const handleInput = (e, asgarosForumConf, setAsgarosForumConf) => { @@ -45,45 +43,3 @@ export const checkMappedFields = asgarosForumConf => { ) ) } - -export const asgarosForumAuthentication = ( - confTmp, - setAsgarosForumConf, - setError, - setIsAuthorized, - setIsLoading -) => { - if (!confTmp?.name) { - setError({ - name: __("Integration name can't be empty", 'bit-integrations') - }) - return - } - - setError({}) - setIsLoading(true) - - bitsFetch({ name: confTmp.name }, 'asgaros_forum_authorize') - .then(result => { - if (result?.success) { - setIsAuthorized(true) - setAsgarosForumConf(prevConf => - create(prevConf, draftConf => { - draftConf.name = confTmp.name - }) - ) - } else { - setError({ - name: result?.data || __('Authorization failed', 'bit-integrations') - }) - } - }) - .catch(() => { - setError({ - name: __('Authorization failed', 'bit-integrations') - }) - }) - .finally(() => { - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx b/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx index 51e9e7f21..e429878da 100644 --- a/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx @@ -1,110 +1,36 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -import { useEffect, useState } from 'react' -import { __, sprintf } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' -import LoaderSm from '../../Loaders/LoaderSm' -import BackIcn from '../../../Icons/BackIcn' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function AutonamiAuthorization({ - formID, autonamiConf, setAutonamiConf, step, nextPage, - setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ integrationName: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const [isLoading, setIsLoading] = useState(false) - const [isMounted, setIsMounted] = useState(true) - useEffect( - () => () => { - setIsMounted(false) - }, - [] - ) - - const handleAuthorize = () => { - setIsLoading('auth') - bitsFetch({}, 'autonami_authorize').then(result => { - if (isMounted) { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Connect Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - } - }) - } - const handleInput = e => { - const newConf = { ...autonamiConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setAutonamiConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( - <> -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('Please! First Install or Active %s Plugin', 'bit-integrations'), - 'Autonami Pro' - )} -
    - )} - {!isInfo && ( - <> - -
    - - - )} -
    - + ) } diff --git a/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx b/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx index 6383aa401..d33265f58 100644 --- a/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx @@ -1,105 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function BuddyBossAuthorization({ - formID, buddyBossConf, setBuddyBossConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - // const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'buddyBoss_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with BuddyBoss Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(buddyBossConf) - newConf[e.target.name] = e.target.value - setBuddyBossConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if BuddyBoss is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'BuddyBoss' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx index f39404b7e..bb0fbed07 100644 --- a/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx @@ -1,120 +1,42 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function CreatorLmsAuthorization({ - formID, creatorLmsConf, setCreatorLmsConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const { creatorLms } = tutorialLinks - - const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'creator_lms_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Creator LMS Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...creatorLmsConf } - newConf[e.target.name] = e.target.value - setCreatorLmsConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - {creatorLms?.youTubeLink && ( - - )} - {creatorLms?.docLink && } - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Creator LMS is authorized!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('Creator LMS is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('Creator LMS is activated', 'bit-integrations')}
    -
    - )} - - -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx b/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx index b63102395..81b77cc7a 100644 --- a/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { dokanAuthentication } from './dokanCommonFunctions' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function DokanAuthorization({ dokanConf, setDokanConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !dokanConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...dokanConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setDokanConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Dokan/dokanCommonFunctions.js b/frontend/src/components/AllIntegrations/Dokan/dokanCommonFunctions.js index 9bcdc5a5d..32a5275a2 100644 --- a/frontend/src/components/AllIntegrations/Dokan/dokanCommonFunctions.js +++ b/frontend/src/components/AllIntegrations/Dokan/dokanCommonFunctions.js @@ -34,24 +34,6 @@ export const checkMappedFields = dokanConf => { return true } -export const dokanAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'dokan_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Connection failed: install and active Dokan plugin first!', 'bit-integrations')) - }) -} export const getAllVendors = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, vendors: true }) diff --git a/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx b/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx index ce899ce77..6bae226ca 100644 --- a/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx @@ -1,109 +1,36 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -import { useEffect, useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' -import LoaderSm from '../../Loaders/LoaderSm' -import BackIcn from '../../../Icons/BackIcn' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function FluentCrmAuthorization({ - formID, fluentCrmConf, setFluentCrmConf, step, nextPage, - setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ integrationName: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const [isMounted, setIsMounted] = useState(true) - useEffect( - () => () => { - setIsMounted(false) - }, - [] - ) - - const handleAuthorize = () => { - setIsLoading('auth') - bitsFetch({}, 'fluent_crm_authorize').then(result => { - if (isMounted) { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Connected Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - } - }) - } - const handleInput = e => { - const newConf = { ...fluentCrmConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setFluentCrmConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( - <> -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {isLoading === 'auth' && ( -
    - - {__('Checking if Fluent CRM is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {__('Please! First Install Fluent CRM Plugins', 'bit-integrations')} -
    - )} - -
    - -
    - + ) } diff --git a/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx b/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx index c02163691..9a73d8373 100644 --- a/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx @@ -1,113 +1,36 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function FluentCartAuthorization({ - formID, fluentCartConf, setFluentCartConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'fluent_cart_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with FluentCart Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...fluentCartConf } - newConf[e.target.name] = e.target.value - setFluentCartConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if FluentCart is authorized!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('FluentCart is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('FluentCart is activated', 'bit-integrations')}
    -
    - )} - - -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx b/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx index 5aade949d..b2328f68f 100644 --- a/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function GamiPressAuthorization({ - formID, gamiPressConf, setGamiPressConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'gamiPress_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with GamiPress Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(gamiPressConf) - newConf[e.target.name] = e.target.value - setGamiPressConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if GamiPress is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'GamiPress' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx b/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx index 0df7f4fd7..9f6fb2fff 100644 --- a/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function GiveWpAuthorization({ - formID, giveWpConf, setGiveWpConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'giveWp_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with GiveWp Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(giveWpConf) - newConf[e.target.name] = e.target.value - setGiveWpConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if GiveWp affiliate is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'GiveWp' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx index ca657ff29..3117303fc 100644 --- a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { jetEngineAuthentication } from './jetEngineCommonFunctions' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function JetEngineAuthorization({ jetEngineConf, setJetEngineConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !jetEngineConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...jetEngineConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setJetEngineConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js b/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js index 4f2a0d904..a6115dc48 100644 --- a/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js +++ b/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js @@ -34,24 +34,6 @@ export const checkMappedFields = jetEngineConf => { return true } -export const jetEngineAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'jetEngine_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Connection failed: install and active JetEngine plugin first!', 'bit-integrations')) - }) -} export const getJetEngineOptions = ( route, diff --git a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx index a69f4446d..1ce8cda16 100644 --- a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx @@ -1,105 +1,42 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function LearnDashAuthorization({ - formID, learnDashConf, setLearnDashConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - // const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'learnDash_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with LearnDash Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(learnDashConf) - newConf[e.target.name] = e.target.value - setLearnDashConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if LearnDash is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'LearnDash' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx index c06f80b13..afbb98ed5 100644 --- a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function LifterLmsAuthorization({ - formID, lifterLmsConf, setLifterLmsConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'lifterLms_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with LifterLms Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(lifterLmsConf) - newConf[e.target.name] = e.target.value - setLifterLmsConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if LifterLms is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'LifterLms' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx index 1a145c454..5311ca560 100644 --- a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx @@ -1,108 +1,46 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getAllList, getAllTags, mailMintRefreshFields } from './MailMintCommonFunc' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' +import { getAllList, getAllTags } from './MailMintCommonFunc' export default function MailMintAuthorization({ - formID, mailMintConf, setMailMintConf, step, setStep, - isLoading, + isInfo, setIsLoading, setSnackbar }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'mailmint_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Mail Mint Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - // mailMintRefreshFields(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) + const handleSetStep = useCallback(value => { + if (value === 2) { getAllList(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) getAllTags(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) - }) - } - - const handleInput = e => { - const newConf = deepCopy(mailMintConf) - newConf[e.target.name] = e.target.value - setMailMintConf(newConf) - } + } + setStep(value) + }, [setStep, mailMintConf, setMailMintConf, setIsLoading, setSnackbar]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Mail Mint is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Mail Mint' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx b/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx index 691f0e497..e24940592 100644 --- a/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx @@ -1,94 +1,36 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' -import LoaderSm from '../../Loaders/LoaderSm' -import BackIcn from '../../../Icons/BackIcn' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function MailPoetAuthorization({ - formID, mailPoetConf, setMailPoetConf, step, nextPage, - setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ integrationName: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - setIsLoading('auth') - bitsFetch({}, 'mail_poet_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...mailPoetConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailPoetConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {isLoading === 'auth' && ( -
    - - {__('Checking if MailPoet is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {__('Please! First Install Mailpoet Plugins', 'bit-integrations')} -
    - )} - -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx b/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx index c2d850bc8..a8aaf4742 100644 --- a/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx @@ -1,112 +1,36 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function MailerPressAuthorization({ - formID, mailerPressConf, setMailerPressConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'mailer_press_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with MailerPress Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...mailerPressConf } - newConf[e.target.name] = e.target.value - setMailerPressConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if MailerPress is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('MailerPress is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('MailerPress is activated', 'bit-integrations')}
    -
    - )} - - -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx index 70b340e8e..1169de3e5 100644 --- a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx @@ -1,97 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { mailsterAuthentication } from './MailsterCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function MailsterAuthorization({ mailsterConf, setMailsterConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !mailsterConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...mailsterConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailsterConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Mailster/MailsterCommonFunc.js b/frontend/src/components/AllIntegrations/Mailster/MailsterCommonFunc.js index f4be43b55..82efa49eb 100644 --- a/frontend/src/components/AllIntegrations/Mailster/MailsterCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailster/MailsterCommonFunc.js @@ -40,32 +40,6 @@ export const checkMappedFields = mailsterConf => { return true } -export const mailsterAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'mailster_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - mailsterFields(confTmp, setConf, loading, setLoading) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Connection failed: install and active Mailster plugin first!', 'bit-integrations')) - }) -} export const mailsterFields = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, fields: true }) diff --git a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx index 1248280ee..5af3ff24d 100644 --- a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx @@ -1,104 +1,41 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function MasterStudyLmsAuthorization({ - formID, msLmsConf, setMsLmsConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'MasterStudyLms_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with MasterStudyLMs Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(msLmsConf) - newConf[e.target.name] = e.target.value - setMsLmsConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if MasterStudyLms is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'MasterStudyLms' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx b/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx index 15ec26acb..ed18df98e 100644 --- a/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx @@ -1,108 +1,46 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getAllMemberShip, paymentGateway } from './MemberpressCommonFunc' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' +import { getAllMemberShip, paymentGateway } from './MemberpressCommonFunc' export default function MemberpressAuthorization({ - formID, memberpressConf, setMemberpressConf, step, setStep, - isLoading, + isInfo, setIsLoading, setSnackbar }) { - const [isAuthorized, setisAuthorized] = useState(false) - // const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'memberpress_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Memberpress Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) + const handleSetStep = useCallback(value => { + if (value === 2) { getAllMemberShip(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) paymentGateway(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) - }) - } - - const handleInput = e => { - const newConf = deepCopy(memberpressConf) - newConf[e.target.name] = e.target.value - setMemberpressConf(newConf) - } + } + setStep(value) + }, [setStep, memberpressConf, setMemberpressConf, setIsLoading, setSnackbar]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if Memberpress is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Memberpress' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx b/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx index 344b34891..6e3430cb5 100644 --- a/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { newsletterAuthentication } from './NewsletterCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function NewsletterAuthorization({ newsletterConf, setNewsletterConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !newsletterConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...newsletterConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setNewsletterConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js b/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js index 3ee59c4d1..641caeee1 100644 --- a/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js @@ -1,7 +1,3 @@ -/* 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' @@ -40,24 +36,6 @@ export const checkMappedFields = newsletterConf => { return true } -export const newsletterAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'newsletter_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Connection failed: install and active Newsletter plugin first!', 'bit-integrations')) - }) -} export const staticFields = [ { key: 'email', label: __('Email', 'bit-integrations'), required: true }, diff --git a/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx b/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx index a3764a079..9f853e319 100644 --- a/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx @@ -1,87 +1,36 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { notificationXAuthentication } from './NotificationXCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function NotificationXAuthorization({ - formID, notificationXConf, setNotificationXConf, step, nextPage, - isLoading, - setIsLoading, - isInfo, + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const handleInput = e => { - const newConf = { ...notificationXConf } - newConf[e.target.name] = e.target.value - setNotificationXConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - -
    {error.name}
    - - - - {!isInfo && ( - <> - -
    - - - )} -
    + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js b/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js index 343b1b7f0..7a8bd30d1 100644 --- a/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js +++ b/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js @@ -51,37 +51,4 @@ export const refreshNotificationsBySource = (action, setNotificationXConf, setIs }) } -export const notificationXAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - setIsLoading -) => { - if (!confTmp.name) { - setError({ - name: !confTmp.name ? __("Integration name can't be empty", 'bit-integrations') : '', - }) - return - } - - setError({}) - setIsLoading(true) - - const requestParams = { name: confTmp.name } - - bitsFetch(requestParams, 'notificationx_authorize').then(result => { - if (result && result.success) { - setIsAuthorized(true) - } else { - const errorMsg = typeof result?.data === 'string' ? result.data : result?.message - setError({ name: errorMsg || __('Authorization failed', 'bit-integrations') }) - } - - setIsLoading(false) - }).catch(() => { - setError({ name: __('Authorization failed', 'bit-integrations') }) - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx index 7ecb34270..5bf095030 100644 --- a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function PaidMembershipProAuthorization({ - formID, paidMembershipProConf, setPaidMembershipProConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'paid_membership_pro_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Paid Membership Pro Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(paidMembershipProConf) - newConf[e.target.name] = e.target.value - setPaidMembershipProConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Paid Membership Pro is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Paid Membership Pro' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx index 1acc27882..768ba4f3c 100644 --- a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx @@ -1,118 +1,36 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function PeepSoAuthorization({ peepSoConf, setPeepSoConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, - info + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'peep_so_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with PeepSo Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...peepSoConf } - newConf[e.target.name] = e.target.value - setPeepSoConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if PeepSo is authorized!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('PeepSo is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('PeepSo is activated', 'bit-integrations')}
    -
    - )} - - {!info && - <> - -
    - - - } -
    + ) } diff --git a/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx b/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx index 7e9d18ef6..f9397d171 100644 --- a/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function PropovoiceCrmAuthorization({ - formID, propovoiceCrmConf, setPropovoiceCrmConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'propovoice_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Propovoice CRM Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(propovoiceCrmConf) - newConf[e.target.name] = e.target.value - setPropovoiceCrmConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Propovoice CRM is active!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Propovoice CRM' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx b/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx index 8ebcfd399..e76a6851d 100644 --- a/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx @@ -1,112 +1,50 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getAllLevels } from './RestrictContentCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' +import { getAllLevels } from './RestrictContentCommonFunc' export default function RestrictContentAuthorization({ - formID, restrictConf, setRestrictConf, step, setStep, - setSnackbar + isInfo, + setIsLoading }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'restrict_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Restrict Content Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(restrictConf) - newConf[e.target.name] = e.target.value - setRestrictConf(newConf) - } - - const nextPage = () => { - // setTimeout(() => { - // document.getElementById('btcd-settings-wrp').scrollTop = 0 - // }, 300) - getAllLevels(restrictConf, setRestrictConf, setIsLoading) - setStep(2) - } + const handleSetStep = useCallback(value => { + if (value === 2) { + getAllLevels(restrictConf, setRestrictConf, setIsLoading) + } + setStep(value) + }, [setStep, restrictConf, setRestrictConf, setIsLoading]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if restrict content is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'RestrictContent' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx b/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx index 1c50452d8..91481f99a 100644 --- a/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx @@ -1,104 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function SliceWpAuthorization({ - formID, sliceWpConf, setSliceWpConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'slicewp_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with SliceWp affiliate Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(sliceWpConf) - newConf[e.target.name] = e.target.value - setSliceWpConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if SliceWp affiliate is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'SliceWp affiliate' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx b/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx index 75ab16e77..b425f7688 100644 --- a/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { sureMembersAuthentication } from './SureMembersCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function SureMembersAuthorization({ sureMembersConf, setSureMembersConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !sureMembersConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...sureMembersConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSureMembersConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/SureMembers/SureMembersCommonFunc.js b/frontend/src/components/AllIntegrations/SureMembers/SureMembersCommonFunc.js index 562988478..dede28b0f 100644 --- a/frontend/src/components/AllIntegrations/SureMembers/SureMembersCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SureMembers/SureMembersCommonFunc.js @@ -33,26 +33,6 @@ export const checkMappedFields = sureMembersConf => { return true } -export const sureMembersAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'sureMembers_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - __('Connection failed: install and active SureMembers plugin first!', 'bit-integrations') - ) - }) -} export const getSureMembersGroups = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, groups: true }) diff --git a/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx b/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx index 64806e5e1..f4cf1e8db 100644 --- a/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx @@ -1,116 +1,36 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function TeamsForWooCommerceMembershipsAuthorization({ - formID, teamsForWcConf, setTeamsForWcConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'teams_for_wc_memberships_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Teams for WooCommerce Memberships Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...teamsForWcConf } - newConf[e.target.name] = e.target.value - setTeamsForWcConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Teams for WooCommerce Memberships is authorized!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__( - 'Teams for WooCommerce Memberships is not activated or not installed', - 'bit-integrations' - )} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    - {__('Teams for WooCommerce Memberships is activated', 'bit-integrations')} -
    -
    - )} - - -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx b/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx index 4e31fba0f..59ea7355c 100644 --- a/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx @@ -1,93 +1,41 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { theEventsCalendarAuthentication } from './theEventsCalendarCommonFunctions' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function TheEventsCalendarAuthorization({ theEventsCalendarConf, setTheEventsCalendarConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !theEventsCalendarConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...theEventsCalendarConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setTheEventsCalendarConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/TheEventsCalendar/theEventsCalendarCommonFunctions.js b/frontend/src/components/AllIntegrations/TheEventsCalendar/theEventsCalendarCommonFunctions.js index 7b1972b6c..d682a9858 100644 --- a/frontend/src/components/AllIntegrations/TheEventsCalendar/theEventsCalendarCommonFunctions.js +++ b/frontend/src/components/AllIntegrations/TheEventsCalendar/theEventsCalendarCommonFunctions.js @@ -34,30 +34,6 @@ export const checkMappedFields = theEventsCalendarConf => { return true } -export const theEventsCalendarAuthentication = ( - confTmp, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'the_events_calendar_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__(result?.data ? result.data : 'Something went wrong!', 'bit-integrations')) - }) -} export const getAllEvents = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, events: true }) diff --git a/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx index 77111e67e..669e45407 100644 --- a/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx @@ -1,102 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function TutorLmsAuthorization({ tutorlmsConf, setTutorlmsConf, step, setStep, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'tutor_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Tutor LMS Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(tutorlmsConf) - newConf[e.target.name] = e.target.value - setTutorlmsConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if Tutor LMS is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'Tutor LMS' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx b/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx index fe534aefa..9674df0d2 100644 --- a/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx @@ -1,127 +1,43 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function UltimateAffiliateProAuthorization({ ultimateAffiliateProConf, setUltimateAffiliateProConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, - isInfo, - + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const authorizeHandler = () => { - if (isInfo || typeof setIsLoading !== 'function' || typeof setSnackbar !== 'function') { - return - } - - setIsLoading('auth') - bitsFetch({}, 'ultimate_affiliate_pro_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Ultimate Affiliate Pro Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - if (isInfo || typeof setUltimateAffiliateProConf !== 'function') { - return - } - - const newConf = { ...ultimateAffiliateProConf } - newConf[e.target.name] = e.target.value - setUltimateAffiliateProConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if Ultimate Affiliate Pro is authorized...', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('Ultimate Affiliate Pro is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('Ultimate Affiliate Pro is activated', 'bit-integrations')}
    -
    - )} - - {!isInfo && ( - <> - -
    - - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx index f8069f53a..c3b91c0f6 100644 --- a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx @@ -1,75 +1,36 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { userRegistrationAuthorize } from './UserRegistrationMembershipCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function UserRegistrationMembershipAuthorization({ userRegistrationConf, setUserRegistrationConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const handleAuthorize = () => { - userRegistrationAuthorize(setIsAuthorized, setShowAuthMsg, setIsLoading, setSnackbar, nextPage) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - setUserRegistrationConf({ ...userRegistrationConf, name: e.target.value })} - name="name" - value={userRegistrationConf.name} - type="text" - placeholder={__('Integration Name...', 'bit-integrations')} - disabled={isAuthorized} - /> - - -
    - {showAuthMsg && isAuthorized && ( -
    - -
    - )} - -
    + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js index b5e075301..ffccc0f60 100644 --- a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipCommonFunc.js @@ -8,44 +8,6 @@ export const handleInput = (e, userRegistrationConf, setUserRegistrationConf) => setUserRegistrationConf(newConf) } -export const userRegistrationAuthorize = ( - setIsAuthorized, - setShowAuthMsg, - setIsLoading, - setSnackbar, - nextPage -) => { - setIsLoading(true) - bitsFetch({}, 'user_registration_authorize') - .then(result => { - if (result?.success) { - setIsAuthorized(true) - setShowAuthMsg(true) - setSnackbar({ - show: true, - msg: __('Connected Successfully', 'bit-integrations') - }) - nextPage(2) - } else { - setSnackbar({ - show: true, - msg: __( - result?.data || - 'Connection failed. Please make sure User Registration and Membership plugin is installed and activated.', - 'bit-integrations' - ) - }) - } - setIsLoading(false) - }) - .catch(error => { - setSnackbar({ - show: true, - msg: __('Connection failed', 'bit-integrations') - }) - setIsLoading(false) - }) -} export const refreshForms = ( userRegistrationConf, diff --git a/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx b/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx index d38a18eca..c824ce550 100644 --- a/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { voxelAuthentication } from './VoxelCommonFunctions' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function VoxelAuthorization({ voxelConf, setVoxelConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !voxelConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...voxelConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setVoxelConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Voxel/VoxelCommonFunctions.js b/frontend/src/components/AllIntegrations/Voxel/VoxelCommonFunctions.js index f01e20f21..d4f88b8d4 100644 --- a/frontend/src/components/AllIntegrations/Voxel/VoxelCommonFunctions.js +++ b/frontend/src/components/AllIntegrations/Voxel/VoxelCommonFunctions.js @@ -34,24 +34,6 @@ export const checkMappedFields = voxelConf => { return true } -export const voxelAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'voxel_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__(result?.data ? result.data : 'Something went wrong!', 'bit-integrations')) - }) -} export const getPostTypes = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, postTypes: true }) diff --git a/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx b/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx index 3e9794532..400073f85 100644 --- a/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx @@ -1,120 +1,40 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WCAffiliateAuthorization({ wcAffiliateConf, setWCAffiliateConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const authorizeHandler = () => { - if (isInfo) { - return - } - - setIsLoading('auth') - bitsFetch({}, 'wc_affiliate_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with WC Affiliate successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - if (!setWCAffiliateConf) { - return - } - - const newConf = { ...wcAffiliateConf } - newConf[e.target.name] = e.target.value - setWCAffiliateConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if WC Affiliate is authorized...', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('WC Affiliate is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('WC Affiliate is activated', 'bit-integrations')}
    -
    - )} - - -
    - {!isInfo && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx b/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx index a5e2b0d95..f3875cc82 100644 --- a/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx @@ -1,79 +1,36 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { wpcafeAuthentication } from './WPCafeCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WPCafeAuthorization({ - formID, wpcafeConf, setWpcafeConf, step, nextPage, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const handleInput = e => { - const newConf = { ...wpcafeConf } - newConf[e.target.name] = e.target.value - setWpcafeConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - -
    {error.name}
    - - - - {!isInfo && ( - <> - -
    - - - )} -
    + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/WPCafe/WPCafeCommonFunc.js b/frontend/src/components/AllIntegrations/WPCafe/WPCafeCommonFunc.js index cce22fb53..3653c228d 100644 --- a/frontend/src/components/AllIntegrations/WPCafe/WPCafeCommonFunc.js +++ b/frontend/src/components/AllIntegrations/WPCafe/WPCafeCommonFunc.js @@ -1,6 +1,4 @@ import { create } from 'mutative' -import bitsFetch from '../../../Utils/bitsFetch' -import { __ } from '../../../Utils/i18nwrap' export const handleInput = (e, wpcafeConf, setWpcafeConf) => { const newConf = create(wpcafeConf, draftConf => { @@ -31,22 +29,3 @@ export const checkMappedFields = wpcafeConf => { return true } -export const wpcafeAuthentication = (confTmp, setConf, setError, setIsAuthorized, setIsLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Integration name cann't be empty", 'bit-integrations') : '' }) - return - } - - setError({}) - setIsLoading(true) - - const requestParams = { name: confTmp.name } - - bitsFetch(requestParams, 'wpcafe_authorize').then(result => { - if (result && result.success) { - setIsAuthorized(true) - } - - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx b/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx index 0e2f323c1..98e71de1c 100644 --- a/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx @@ -1,110 +1,36 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -import { useEffect, useState } from 'react' -import { __, sprintf } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' -import LoaderSm from '../../Loaders/LoaderSm' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WPCoursewareAuthorization({ - formID, wpCoursewareConf, setWPCoursewareConf, step, nextPage, - setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ integrationName: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [isMounted, setIsMounted] = useState(true) -useEffect( - () => () => { - setIsMounted(false) - }, - [] - ) - - const handleAuthorize = () => { - setIsLoading('auth') - bitsFetch({}, 'wpCourseware_authorize').then(result => { - if (isMounted) { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Connect Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - } - }) - } - const handleInput = e => { - const newConf = { ...wpCoursewareConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setWPCoursewareConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( - <> -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('Please! First Install or Active %s Plugin', 'bit-integrations'), - 'WP Courseware' - )} -
    - )} - {!isInfo && ( - <> - -
    - - - )} -
    - + ) } diff --git a/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx b/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx index ee22f37bd..4690de9c7 100644 --- a/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx @@ -1,87 +1,35 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { wpforoAuthentication } from './WPForoCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WPForoAuthorization({ wpforoConf, setWPForoConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !wpforoConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...wpforoConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setWPForoConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - {error.name &&
    {error.name}
    } -
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/WPForo/WPForoCommonFunc.js b/frontend/src/components/AllIntegrations/WPForo/WPForoCommonFunc.js index c6a3d6820..67ecccc48 100644 --- a/frontend/src/components/AllIntegrations/WPForo/WPForoCommonFunc.js +++ b/frontend/src/components/AllIntegrations/WPForo/WPForoCommonFunc.js @@ -34,24 +34,6 @@ export const checkMappedFields = wpforoConf => { return true } -export const wpforoAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.name) { - setError({ name: !confTmp.name ? __("Name can't be empty", 'bit-integrations') : '' }) - return - } - - setLoading({ ...loading, auth: true }) - bitsFetch({}, 'wpforo_authentication').then(result => { - if (result.success) { - setIsAuthorized(true) - toast.success(__('Connected Successfully', 'bit-integrations')) - setLoading({ ...loading, auth: false }) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Connection failed: install and active WPForo plugin first!', 'bit-integrations')) - }) -} export const getWPForoReputations = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, reputation: true }) diff --git a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx index 78c95f6b7..239b7e5ea 100644 --- a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx @@ -1,58 +1,42 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' -import { handleAuthorize, setIntegrationName } from './WishlistMemberCommonFunc' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WishlistMemberAuthorization({ wishlistMemberConf, setWishlistMemberConf, step, nextPage, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) -return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - setIntegrationName(e, setWishlistMemberConf)} - name="name" - value={wishlistMemberConf.name} - type="text" - placeholder={__('Integration Name...', 'bit-integrations')} - disabled={isInfo} - /> - - - -
    - -
    + const setStep = useCallback(value => nextPage(value), [nextPage]) + return ( + ) } diff --git a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberCommonFunc.js b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberCommonFunc.js index 801ccc4e4..fce9ff80a 100644 --- a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberCommonFunc.js +++ b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberCommonFunc.js @@ -2,24 +2,6 @@ import { create } from 'mutative' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' -export const handleAuthorize = (setIsAuthorized, setIsLoading, setSnackbar) => { - setIsLoading(true) - - bitsFetch(null, 'wishlist_authorization').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else { - setIsAuthorized(false) - setSnackbar({ - show: true, - msg: result?.data || __('Authorization Failed, Please try again', 'bit-integrations') - }) - } - - setIsLoading(false) - }) -} export const generateMappedField = wishlistFields => { const requiredFields = wishlistFields.filter(fld => fld?.required === true) diff --git a/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx b/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx index 8346579b2..41f8e30a3 100644 --- a/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx @@ -1,103 +1,35 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' +import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WooCommerceAuthorization({ - formID, wcConf, setWcConf, step, setStep, - setSnackbar + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'wc_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with WooCommerce Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = deepCopy(wcConf) - newConf[e.target.name] = e.target.value - setWcConf(newConf) - } - return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - Checking if WooCommerce is active!!! -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    - - × - - {sprintf( - __('%s plugin must be activated to integrate with Bit Integrations', 'bit-integrations'), - 'WooCommerce' - )} -
    - )} - - {!isAuthorized && ( - - )} - - {isAuthorized && ( - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx b/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx index 382e48254..95e672bdf 100644 --- a/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx @@ -1,112 +1,33 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import PlatformAuthorization from '../../Connections/PlatformAuthorization' export default function WpErpAuthorization({ wpErpConf, setWpErpConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'wp_erp_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with WP ERP Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...wpErpConf } - newConf[e.target.name] = e.target.value - setWpErpConf(newConf) - } - + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
    -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - {isLoading === 'auth' && ( -
    - - {__('Checking if WP ERP is authorized!!!', 'bit-integrations')} -
    - )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
    -
    -
    - -
    -
    - {__('WP ERP is not activated or not installed', 'bit-integrations')} -
    -
    -
    - )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
    -
    - -
    -
    {__('WP ERP is activated', 'bit-integrations')}
    -
    - )} - - {!isInfo && ( - <> - -
    - - - )} -
    + ) } diff --git a/frontend/src/resource/sass/app.scss b/frontend/src/resource/sass/app.scss index fdabdafc8..9d1085798 100644 --- a/frontend/src/resource/sass/app.scss +++ b/frontend/src/resource/sass/app.scss @@ -7022,7 +7022,7 @@ _:-ms-fullscreen, justify-content: space-between; align-items: center; gap: 12px; - margin: 0 16px 12px; + margin: 0 5px 10px; } .btcd-table-top .btcd-t-actions { From dbcb5415589598999736117b547bf99588ad619f Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Sat, 9 May 2026 11:59:57 +0600 Subject: [PATCH 16/58] feat: remove unused route files for various integrations --- backend/Actions/AsgarosForum/Routes.php | 9 --------- backend/Actions/CreatorLms/Routes.php | 9 --------- backend/Actions/CustomApi/Routes.php | 5 ----- backend/Actions/GiveWp/Routes.php | 9 --------- backend/Actions/Newsletter/Routes.php | 9 --------- backend/Actions/PeepSo/Routes.php | 9 --------- backend/Actions/SliceWp/Routes.php | 9 --------- backend/Actions/UltimateAffiliatePro/Routes.php | 9 --------- backend/Actions/WCAffiliate/Routes.php | 9 --------- backend/Actions/WPCafe/Routes.php | 9 --------- 10 files changed, 86 deletions(-) delete mode 100644 backend/Actions/AsgarosForum/Routes.php delete mode 100644 backend/Actions/CreatorLms/Routes.php delete mode 100644 backend/Actions/CustomApi/Routes.php delete mode 100644 backend/Actions/GiveWp/Routes.php delete mode 100644 backend/Actions/Newsletter/Routes.php delete mode 100644 backend/Actions/PeepSo/Routes.php delete mode 100644 backend/Actions/SliceWp/Routes.php delete mode 100644 backend/Actions/UltimateAffiliatePro/Routes.php delete mode 100644 backend/Actions/WCAffiliate/Routes.php delete mode 100644 backend/Actions/WPCafe/Routes.php diff --git a/backend/Actions/AsgarosForum/Routes.php b/backend/Actions/AsgarosForum/Routes.php deleted file mode 100644 index 41ed4c653..000000000 --- a/backend/Actions/AsgarosForum/Routes.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Sat, 9 May 2026 12:24:44 +0600 Subject: [PATCH 17/58] feat: add WP_PLUGIN_CHECK authorization type and update integrations - Introduced a new authorization type `WP_PLUGIN_CHECK` in AuthorizationType.php. - Updated ConnectionController to handle `WP_PLUGIN_CHECK` by returning appropriate error messages. - Modified frontend components to use `WP_PLUGIN_CHECK` as the default auth type for various integrations. - Refactored connectionAuth.js to include a function for checking the new auth type. - Adjusted PlatformAuthorization to manage the new auth type logic. --- backend/Authorization/AuthorizationType.php | 2 ++ backend/controller/ConnectionController.php | 30 ++++++++++++++----- frontend/src/Utils/connectionAuth.js | 8 ++--- .../AcademyLms/AcademyLmsAuthorization.jsx | 2 +- .../Affiliate/AffiliateAuthorization.jsx | 2 +- .../AsgarosForumAuthorization.jsx | 2 +- .../Autonami/AutonamiAuthorization.jsx | 2 +- .../BuddyBoss/BuddyBossAuthorization.jsx | 2 +- .../CreatorLms/CreatorLmsAuthorization.jsx | 2 +- .../Dokan/DokanAuthorization.jsx | 2 +- .../FluentCRM/FluentCrmAuthorization.jsx | 2 +- .../FluentCart/FluentCartAuthorization.jsx | 2 +- .../GamiPress/GamiPressAuthorization.jsx | 2 +- .../GiveWp/GiveWpAuthorization.jsx | 2 +- .../JetEngine/JetEngineAuthorization.jsx | 2 +- .../LearnDash/LearnDashAuthorization.jsx | 2 +- .../LifterLms/LifterLmsAuthorization.jsx | 2 +- .../MailMint/MailMintAuthorization.jsx | 2 +- .../MailPoet/MailPoetAuthorization.jsx | 2 +- .../MailerPress/MailerPressAuthorization.jsx | 2 +- .../Mailster/MailsterAuthorization.jsx | 2 +- .../MasterStudyLmsAuthorization.jsx | 2 +- .../Memberpress/MemberpressAuthorization.jsx | 2 +- .../Newsletter/NewsletterAuthorization.jsx | 2 +- .../NotificationXAuthorization.jsx | 2 +- .../PaidMembershipProAuthorization.jsx | 2 +- .../PeepSo/PeepSoAuthorization.jsx | 2 +- .../PropovoiceCrmAuthorization.jsx | 2 +- .../RestrictContentAuthorization.jsx | 2 +- .../SliceWp/SliceWpAuthorization.jsx | 2 +- .../SureMembers/SureMembersAuthorization.jsx | 2 +- ...ForWooCommerceMembershipsAuthorization.jsx | 2 +- .../TheEventsCalendarAuthorization.jsx | 2 +- .../TutorLms/TutorLmsAuthorization.jsx | 2 +- .../UltimateAffiliateProAuthorization.jsx | 2 +- ...serRegistrationMembershipAuthorization.jsx | 2 +- .../Voxel/VoxelAuthorization.jsx | 2 +- .../WCAffiliate/WCAffiliateAuthorization.jsx | 2 +- .../WPCafe/WPCafeAuthorization.jsx | 2 +- .../WPCoursewareAuthorization.jsx | 2 +- .../WPForo/WPForoAuthorization.jsx | 2 +- .../WeDocs/WeDocsAuthorization.jsx | 2 +- .../WishlistMemberAuthorization.jsx | 2 +- .../WooCommerce/WooCommerceAuthorization.jsx | 2 +- .../WpErp/WpErpAuthorization.jsx | 2 +- .../Connections/PlatformAuthorization.jsx | 14 ++++----- 46 files changed, 76 insertions(+), 62 deletions(-) diff --git a/backend/Authorization/AuthorizationType.php b/backend/Authorization/AuthorizationType.php index 1e8f96e30..e3b57bded 100644 --- a/backend/Authorization/AuthorizationType.php +++ b/backend/Authorization/AuthorizationType.php @@ -8,6 +8,8 @@ class AuthorizationType { + public const WP_PLUGIN_CHECK = 'wp_plugin_check'; + public const BASIC_AUTH = 'basic_auth'; public const API_KEY = 'api_key'; diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 178e46b0e..75443f91d 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -20,6 +20,7 @@ final class ConnectionController { private const ALLOWED_AUTH_TYPES = [ + AuthorizationType::WP_PLUGIN_CHECK, AuthorizationType::BASIC_AUTH, AuthorizationType::API_KEY, AuthorizationType::BEARER_TOKEN, @@ -188,7 +189,7 @@ public function reauthorize($request) $payload = [ 'app_slug' => $existing->app_slug, - 'auth_type' => $existing->auth_type, + 'auth_type' => (string) $existing->auth_type, 'connection_name' => $existing->connection_name, 'account_name' => $existing->account_name, 'status' => ConnectionModel::STATUS_VERIFIED, @@ -218,11 +219,6 @@ public function authorize($request) $this->guard(); $authType = $this->sanitizeScalar($request->auth_type ?? ''); - $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); - $apiEndpoint = esc_url_raw((string) ($request->api_endpoint ?? '')); - $method = strtoupper($this->sanitizeScalar($request->method ?? 'GET')); - $payload = isset($request->payload) ? $this->normalizePayload($request->payload) : null; - $headers = $this->normalizeHeaders($request->headers ?? []); if (empty($authType)) { wp_send_json_error(__('Auth type is required', 'bit-integrations')); @@ -232,6 +228,19 @@ public function authorize($request) wp_send_json_error(__('Invalid auth type', 'bit-integrations')); } + if ($authType === AuthorizationType::WP_PLUGIN_CHECK) { + wp_send_json_error( + __('WP Plugin Check does not use credential authorization. Use platform check endpoint instead.', 'bit-integrations'), + 400 + ); + } + + $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); + $apiEndpoint = esc_url_raw((string) ($request->api_endpoint ?? '')); + $method = strtoupper($this->sanitizeScalar($request->method ?? 'GET')); + $payload = isset($request->payload) ? $this->normalizePayload($request->payload) : null; + $headers = $this->normalizeHeaders($request->headers ?? []); + if (empty($request->auth_details)) { wp_send_json_error(__('Authorization details are required', 'bit-integrations')); } @@ -400,6 +409,13 @@ private function buildPayload($request, bool $isUpdate) return new WP_Error('missing_app_slug', __('App slug is required', 'bit-integrations')); } + if ($authType === AuthorizationType::WP_PLUGIN_CHECK) { + return new WP_Error( + 'invalid_auth_type', + __('WP Plugin Check does not require saving a reusable credential connection', 'bit-integrations') + ); + } + if ($authType !== '' && !\in_array($authType, self::ALLOWED_AUTH_TYPES, true)) { return new WP_Error('invalid_auth_type', __('Invalid auth type', 'bit-integrations')); } @@ -571,7 +587,7 @@ private function formatRow($row): array return [ 'id' => (int) $row->id, 'app_slug' => $row->app_slug, - 'auth_type' => $row->auth_type, + 'auth_type' => (string) ($row->auth_type ?? ''), 'connection_name' => $row->connection_name, 'account_name' => $row->account_name, 'encrypt_keys' => $encryptKeys, diff --git a/frontend/src/Utils/connectionAuth.js b/frontend/src/Utils/connectionAuth.js index 5cf0bc717..19c1cdb23 100644 --- a/frontend/src/Utils/connectionAuth.js +++ b/frontend/src/Utils/connectionAuth.js @@ -1,7 +1,7 @@ import { reauthorizeConnection, saveConnection } from './connectionApi' export const AUTH_TYPES = Object.freeze({ - NO_AUTH: 'no_auth', + WP_PLUGIN_CHECK: 'wp_plugin_check', OAUTH2: 'oauth2', API_KEY: 'api_key', BEARER_TOKEN: 'bearer_token', @@ -16,8 +16,4 @@ export const defaultEncryptKeys = { [AUTH_TYPES.OAUTH2]: ['client_secret', 'access_token', 'refresh_token'] } -export const isNoAuthType = authType => authType === AUTH_TYPES.NO_AUTH - -export const normalizeAuthType = authType => - Object.values(AUTH_TYPES).includes(authType) ? authType : AUTH_TYPES.OAUTH2 - +export const isWpPluginCheckType = authType => authType === AUTH_TYPES.WP_PLUGIN_CHECK diff --git a/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx index 0fbbdbe96..559135242 100644 --- a/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/AcademyLms/AcademyLmsAuthorization.jsx @@ -21,7 +21,7 @@ export default function AcademyLmsAuthorization({ tutorialTitle="Academy LMS" tutorialLinks={tutorialLinks?.academyLms || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'academy/academy.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx index 4a1c505d0..bef091138 100644 --- a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx @@ -21,7 +21,7 @@ export default function AffiliateAuthorization({ tutorialTitle="AffiliateWP" tutorialLinks={tutorialLinks?.affiliate || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'affiliate-wp/affiliate-wp.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx index 912e3729d..068a4e744 100644 --- a/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/AsgarosForum/AsgarosForumAuthorization.jsx @@ -19,7 +19,7 @@ export default function AsgarosForumAuthorization({ setStep={setStep} isInfo={isInfo} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'AsgarosForum' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx b/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx index e429878da..686b9eaaf 100644 --- a/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Autonami/AutonamiAuthorization.jsx @@ -22,7 +22,7 @@ export default function AutonamiAuthorization({ tutorialTitle="FunnelKit (Autonami)" tutorialLinks={tutorialLinks?.autonami || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'BWFCRM_Contact' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx b/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx index d33265f58..6998679f7 100644 --- a/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/BuddyBoss/BuddyBossAuthorization.jsx @@ -21,7 +21,7 @@ export default function BuddyBossAuthorization({ tutorialTitle="BuddyBoss" tutorialLinks={tutorialLinks?.buddyBoss || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'BuddyPress' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx index bb0fbed07..f88f726c7 100644 --- a/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CreatorLms/CreatorLmsAuthorization.jsx @@ -22,7 +22,7 @@ export default function CreatorLmsAuthorization({ tutorialTitle="Creator LMS" tutorialLinks={tutorialLinks?.creatorLms || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [ { type: 'class', value: 'CreatorLms' }, diff --git a/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx b/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx index 81b77cc7a..bde1d7055 100644 --- a/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Dokan/DokanAuthorization.jsx @@ -21,7 +21,7 @@ export default function DokanAuthorization({ tutorialTitle="Dokan" tutorialLinks={tutorialLinks?.dokan || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'dokan-lite/dokan.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx b/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx index 6bae226ca..4bbc6a7b5 100644 --- a/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FluentCRM/FluentCrmAuthorization.jsx @@ -22,7 +22,7 @@ export default function FluentCrmAuthorization({ tutorialTitle="FluentCRM" tutorialLinks={tutorialLinks?.fluentCrm || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'fluent-crm/fluent-crm.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx b/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx index 9a73d8373..bd1447115 100644 --- a/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FluentCart/FluentCartAuthorization.jsx @@ -22,7 +22,7 @@ export default function FluentCartAuthorization({ tutorialTitle="FluentCart" tutorialLinks={tutorialLinks?.fluentCart || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'constant', value: 'FLUENTCART_PLUGIN_PATH' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx b/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx index b2328f68f..8107572a7 100644 --- a/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GamiPress/GamiPressAuthorization.jsx @@ -21,7 +21,7 @@ export default function GamiPressAuthorization({ tutorialTitle="GamiPress" tutorialLinks={tutorialLinks?.gamiPress || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'gamipress/gamipress.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx b/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx index 9f6fb2fff..bd7b70f0e 100644 --- a/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GiveWp/GiveWpAuthorization.jsx @@ -21,7 +21,7 @@ export default function GiveWpAuthorization({ tutorialTitle="GiveWP" tutorialLinks={tutorialLinks?.giveWp || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'give/give.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx index 3117303fc..25e824bf9 100644 --- a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx @@ -21,7 +21,7 @@ export default function JetEngineAuthorization({ tutorialTitle="JetEngine" tutorialLinks={tutorialLinks?.jetEngine || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'jet-engine/jet-engine.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx index 1ce8cda16..90d72b29d 100644 --- a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx @@ -21,7 +21,7 @@ export default function LearnDashAuthorization({ tutorialTitle="LearnDash" tutorialLinks={tutorialLinks?.learnDash || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { groups: [ { logic: 'AND', checks: [{ type: 'plugin_file', value: 'learndash-propanel/learndash_propanel.php' }] }, diff --git a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx index afbb98ed5..8df9d71cc 100644 --- a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx @@ -21,7 +21,7 @@ export default function LifterLmsAuthorization({ tutorialTitle="LifterLMS" tutorialLinks={tutorialLinks?.lifterLms || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'lifterlms/lifterlms.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx index 5311ca560..97bfd4dd3 100644 --- a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx @@ -32,7 +32,7 @@ export default function MailMintAuthorization({ tutorialTitle="Mail Mint" tutorialLinks={tutorialLinks?.mailMint || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'MailMint' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx b/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx index e24940592..8d250cf60 100644 --- a/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailPoet/MailPoetAuthorization.jsx @@ -22,7 +22,7 @@ export default function MailPoetAuthorization({ tutorialTitle="MailPoet" tutorialLinks={tutorialLinks?.mailPoet || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'MailPoet\\API\\API' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx b/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx index a8aaf4742..4f896800b 100644 --- a/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailerPress/MailerPressAuthorization.jsx @@ -22,7 +22,7 @@ export default function MailerPressAuthorization({ tutorialTitle="MailerPress" tutorialLinks={tutorialLinks?.mailerPress || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'MailerPress\\Core\\Kernel' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx index 1169de3e5..d06d325ab 100644 --- a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx @@ -21,7 +21,7 @@ export default function MailsterAuthorization({ tutorialTitle="Mailster" tutorialLinks={tutorialLinks?.mailster || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'mailster/mailster.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx index 5af3ff24d..1f6c371ed 100644 --- a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx @@ -21,7 +21,7 @@ export default function MasterStudyLmsAuthorization({ tutorialTitle="MasterStudy LMS" tutorialLinks={tutorialLinks?.masterStudyLMS || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { groups: [ { logic: 'AND', checks: [{ type: 'plugin_file', value: 'masterstudy-lms-learning-management-system/masterstudy-lms-learning-management-system.php' }] }, diff --git a/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx b/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx index ed18df98e..e48014698 100644 --- a/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Memberpress/MemberpressAuthorization.jsx @@ -32,7 +32,7 @@ export default function MemberpressAuthorization({ tutorialTitle="MemberPress" tutorialLinks={tutorialLinks?.memberpress || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'memberpress/memberpress.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx b/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx index 6e3430cb5..45d64dbfb 100644 --- a/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Newsletter/NewsletterAuthorization.jsx @@ -21,7 +21,7 @@ export default function NewsletterAuthorization({ tutorialTitle="Newsletter" tutorialLinks={tutorialLinks?.newsletter || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'newsletter/plugin.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx b/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx index 9f853e319..fea7001ea 100644 --- a/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/NotificationX/NotificationXAuthorization.jsx @@ -22,7 +22,7 @@ export default function NotificationXAuthorization({ tutorialTitle="NotificationX" tutorialLinks={tutorialLinks?.notificationX || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'constant', value: 'NOTIFICATIONX_FILE' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx index 5bf095030..9edce9a1f 100644 --- a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx @@ -21,7 +21,7 @@ export default function PaidMembershipProAuthorization({ tutorialTitle="Paid Memberships Pro" tutorialLinks={tutorialLinks?.paidMembershipPro || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'paid-memberships-pro/paid-memberships-pro.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx index 768ba4f3c..a2eab251a 100644 --- a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx @@ -22,7 +22,7 @@ export default function PeepSoAuthorization({ tutorialTitle="PeepSo" tutorialLinks={tutorialLinks?.peepSo || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'PeepSo' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx b/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx index f9397d171..ceea0360e 100644 --- a/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PropovoiceCRM/PropovoiceCrmAuthorization.jsx @@ -21,7 +21,7 @@ export default function PropovoiceCrmAuthorization({ tutorialTitle="Propovoice CRM" tutorialLinks={tutorialLinks?.propovoiceCrm || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'Ndpv' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx b/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx index e76a6851d..c6c89305a 100644 --- a/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/RestrictContent/RestrictContentAuthorization.jsx @@ -30,7 +30,7 @@ export default function RestrictContentAuthorization({ tutorialTitle="Restrict Content" tutorialLinks={tutorialLinks?.restrictContent || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { groups: [ { logic: 'AND', checks: [{ type: 'plugin_file', value: 'restrict-content-pro/restrict-content-pro.php' }] }, diff --git a/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx b/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx index 91481f99a..509426599 100644 --- a/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SliceWp/SliceWpAuthorization.jsx @@ -21,7 +21,7 @@ export default function SliceWpAuthorization({ tutorialTitle="SliceWP" tutorialLinks={tutorialLinks?.sliceWp || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'slicewp/index.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx b/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx index b425f7688..e96fb0474 100644 --- a/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SureMembers/SureMembersAuthorization.jsx @@ -21,7 +21,7 @@ export default function SureMembersAuthorization({ tutorialTitle="SureMembers" tutorialLinks={tutorialLinks?.sureMembers || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'suremembers/suremembers.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx b/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx index f4cf1e8db..735dbeb53 100644 --- a/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TeamsForWooCommerceMemberships/TeamsForWooCommerceMembershipsAuthorization.jsx @@ -22,7 +22,7 @@ export default function TeamsForWooCommerceMembershipsAuthorization({ tutorialTitle="Teams for WooCommerce Memberships" tutorialLinks={tutorialLinks?.teamsForWooCommerceMemberships || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'function', value: 'wc_memberships_for_teams' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx b/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx index 59ea7355c..f8fae5844 100644 --- a/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TheEventsCalendar/TheEventsCalendarAuthorization.jsx @@ -21,7 +21,7 @@ export default function TheEventsCalendarAuthorization({ tutorialTitle="The Events Calendar" tutorialLinks={tutorialLinks?.theEventsCalendar || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [ { type: 'plugin_file', value: 'the-events-calendar/the-events-calendar.php' }, diff --git a/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx index 669e45407..74eb2a91f 100644 --- a/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/TutorLms/TutorLmsAuthorization.jsx @@ -21,7 +21,7 @@ export default function TutorLmsAuthorization({ tutorialTitle="Tutor LMS" tutorialLinks={tutorialLinks?.tutorlms || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'tutor/tutor.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx b/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx index 9674df0d2..3ec72284f 100644 --- a/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/UltimateAffiliatePro/UltimateAffiliateProAuthorization.jsx @@ -22,7 +22,7 @@ export default function UltimateAffiliateProAuthorization({ tutorialTitle="Ultimate Affiliate Pro" tutorialLinks={tutorialLinks?.ultimateAffiliatePro || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { groups: [ { logic: 'AND', checks: [{ type: 'plugin_file', value: 'ultimate-affiliate/ultimate-affiliate.php' }] }, diff --git a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx index c3b91c0f6..255ea66b4 100644 --- a/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/UserRegistrationMembership/UserRegistrationMembershipAuthorization.jsx @@ -22,7 +22,7 @@ export default function UserRegistrationMembershipAuthorization({ tutorialTitle="User Registration Membership" tutorialLinks={tutorialLinks?.userRegistrationMembership || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'UserRegistration' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx b/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx index c824ce550..7858ad6e6 100644 --- a/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Voxel/VoxelAuthorization.jsx @@ -21,7 +21,7 @@ export default function VoxelAuthorization({ tutorialTitle="Voxel" tutorialLinks={tutorialLinks?.voxel || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'Voxel\\Post_Type' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx b/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx index 400073f85..8f0ce1cd3 100644 --- a/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WCAffiliate/WCAffiliateAuthorization.jsx @@ -19,7 +19,7 @@ export default function WCAffiliateAuthorization({ setStep={setStep} isInfo={isInfo} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [ { type: 'class', value: 'WC_Affiliate\\Models\\Affiliate' }, diff --git a/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx b/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx index f3875cc82..62aa2dbba 100644 --- a/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPCafe/WPCafeAuthorization.jsx @@ -22,7 +22,7 @@ export default function WPCafeAuthorization({ tutorialTitle="WPCafe" tutorialLinks={tutorialLinks?.wpcafe || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'WpCafe\\Init' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx b/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx index 98e71de1c..694a1b3b8 100644 --- a/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPCourseware/WPCoursewareAuthorization.jsx @@ -22,7 +22,7 @@ export default function WPCoursewareAuthorization({ tutorialTitle="WP Courseware" tutorialLinks={tutorialLinks?.wpCourseware || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'wp-courseware/wp-courseware.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx b/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx index 4690de9c7..154090f4c 100644 --- a/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WPForo/WPForoAuthorization.jsx @@ -21,7 +21,7 @@ export default function WPForoAuthorization({ tutorialTitle="wpforo" tutorialLinks={tutorialLinks?.wpforo || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'wpforo/wpforo.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx b/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx index 9db101bc4..92163fb77 100644 --- a/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WeDocs/WeDocsAuthorization.jsx @@ -23,7 +23,7 @@ export default function WeDocsAuthorization({ tutorialTitle="weDocs" tutorialLinks={tutorialLinks?.weDocs || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'class', value: 'WeDocs' }], logic: 'AND' diff --git a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx index 239b7e5ea..6ed12f390 100644 --- a/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WishlistMember/WishlistMemberAuthorization.jsx @@ -22,7 +22,7 @@ export default function WishlistMemberAuthorization({ tutorialTitle="WishlistMember" tutorialLinks={tutorialLinks?.wishlistMember || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { groups: [ { logic: 'AND', checks: [{ type: 'class', value: 'WLMAPI' }] }, diff --git a/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx b/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx index 41f8e30a3..0bb7b3240 100644 --- a/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WooCommerce/WooCommerceAuthorization.jsx @@ -21,7 +21,7 @@ export default function WooCommerceAuthorization({ tutorialTitle="WooCommerce" tutorialLinks={tutorialLinks?.wooCommerce || {}} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'plugin_file', value: 'woocommerce/woocommerce.php' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx b/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx index 95e672bdf..fd995de5e 100644 --- a/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WpErp/WpErpAuthorization.jsx @@ -19,7 +19,7 @@ export default function WpErpAuthorization({ setStep={setStep} isInfo={isInfo} authDetails={{ - authType: AUTH_TYPES.NO_AUTH, + authType: AUTH_TYPES.WP_PLUGIN_CHECK, platformCheck: { checks: [{ type: 'function', value: 'erp_insert_people' }], logic: 'AND' } }} noteDetails={{ diff --git a/frontend/src/components/Connections/PlatformAuthorization.jsx b/frontend/src/components/Connections/PlatformAuthorization.jsx index 3a4285760..e00ce0d3f 100644 --- a/frontend/src/components/Connections/PlatformAuthorization.jsx +++ b/frontend/src/components/Connections/PlatformAuthorization.jsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import BackIcn from '../../Icons/BackIcn' -import { AUTH_TYPES } from '../../Utils/connectionAuth' +import { isWpPluginCheckType } from '../../Utils/connectionAuth' import { checkPlatform, listConnections } from '../../Utils/connectionApi' import { __ } from '../../Utils/i18nwrap' import LoaderSm from '../Loaders/LoaderSm' @@ -33,7 +33,7 @@ export default function PlatformAuthorization({ const [isVerified, setIsVerified] = useState(false) const appSlug = config?.app_slug || config?.type - const isNoAuth = authDetails?.authType === AUTH_TYPES.NO_AUTH + const isWpPluginCheck = isWpPluginCheckType(authDetails?.authType) const refreshConnections = useCallback(async () => { if (!appSlug) { @@ -60,12 +60,12 @@ export default function PlatformAuthorization({ }, [appSlug]) useEffect(() => { - if (isNoAuth) { + if (isWpPluginCheck) { return } refreshConnections() - }, [appSlug, isNoAuth]) + }, [appSlug, isWpPluginCheck]) const handleNameChange = useCallback( event => { @@ -133,7 +133,7 @@ export default function PlatformAuthorization({ setStep(2) }, [config?.name, setStep]) - const canGoNext = isNoAuth ? isVerified : Boolean(config?.connection_id) + const canGoNext = isWpPluginCheck ? isVerified : Boolean(config?.connection_id) const pageStyle = useMemo(() => (step === 1 ? STEP_ONE_STYLE : undefined), [step]) @@ -174,7 +174,7 @@ export default function PlatformAuthorization({ />
    {errors.name || ''}
    - {!isNoAuth && ( + {!isWpPluginCheck && ( <> )} - {isNoAuth && !isInfo && ( + {isWpPluginCheck && !isInfo && ( - - // - // - // )} - // - // - // ) } const fabmanApiKeyNote = `

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

    diff --git a/frontend/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js b/frontend/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js index 8eaa0f1c7..297e5a8f6 100644 --- a/frontend/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Fabman/FabmanCommonFunc.js @@ -6,7 +6,7 @@ import { __ } from '../../../Utils/i18nwrap' import { create } from 'mutative' export const fetchFabmanAccountId = async (connectionId, setConf) => { - const result = await bitsFetch({ connection_id: connectionId }, 'fabman_authorization') + const result = await bitsFetch({ connection_id: connectionId }, 'fabman_fetch_account_id') if (result?.success && result.data?.accountId) { setConf(prev => ({ ...prev, accountId: result.data.accountId })) } From f4d032e69f26b7fc35dbb64e113e84e50d074ab9 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 13:03:35 +0600 Subject: [PATCH 22/58] feat: enhance Oauth2Connection and AddNewConnection components to support extra fields and template resolution --- .../Connections/AddNewConnection.jsx | 53 +++++++++- .../Connections/Oauth2Connection.jsx | 99 ++++++++++++++++--- 2 files changed, 133 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 770fa20c5..046a03d69 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -25,12 +25,32 @@ const normalizeAdditionalHeaders = headers => { }, {}) } +// Resolves {fieldName} placeholders in URL templates using authData values. +// Strips trailing slashes from substituted values to avoid double-slash in paths. +// Unknown tokens are replaced with '' (empty required fields caught by validation). +const resolveTemplate = (template, data) => { + if (!template) return '' + return template.replace(/\{(\w+)\}/g, (_, key) => { + const val = data[key] + if (val == null) return '' + return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) + }) +} + const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { const additionalHeaders = normalizeAdditionalHeaders(authDetails?.headers) const sslVerify = authDetails?.ssl_verify !== false + + // Extra fields captured first; standard auth keys below always win on collision. + // Reserved auth_details keys: value, token, key, addTo, username, password, ssl_verify + const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (authData[name] != null) acc[name] = authData[name] + return acc + }, {}) + const basePayload = { auth_type: authType, - api_endpoint: apiEndpoint || '', + api_endpoint: resolveTemplate(apiEndpoint, authData), method: method || 'GET', auth_details: {}, headers: additionalHeaders @@ -38,6 +58,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } if (authType === AUTH_TYPES.API_KEY) { basePayload.auth_details = { + ...extraAuthDetails, key: authDetails?.key || 'X-API-Key', value: authData.api_key, addTo: authData.addTo || 'header', @@ -48,6 +69,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } if (authType === AUTH_TYPES.BASIC_AUTH) { basePayload.auth_details = { + ...extraAuthDetails, username: authData.username, password: authData.password, ssl_verify: sslVerify @@ -57,6 +79,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } if (authType === AUTH_TYPES.BEARER_TOKEN) { basePayload.auth_details = { + ...extraAuthDetails, token: authData.token, ssl_verify: sslVerify } @@ -65,7 +88,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } return basePayload } -const getValidationErrors = (authType, authData) => { +const getValidationErrors = (authType, authData, extraFields = []) => { const nextErrors = {} if (!authData.connectionName?.trim()) { @@ -90,6 +113,12 @@ const getValidationErrors = (authType, authData) => { nextErrors.token = __('Bearer token is required', 'bit-integrations') } + extraFields.forEach(field => { + if (field.required && !authData[field.name]?.trim()) { + nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` + } + }) + return nextErrors } @@ -123,7 +152,7 @@ function CredentialAuthorizeForm({ }, []) const handleAuthorize = useCallback(async () => { - const validationErrors = getValidationErrors(authType, authData) + const validationErrors = getValidationErrors(authType, authData, authDetails?.extraFields) setErrors(validationErrors) if (Object.keys(validationErrors).length > 0) { @@ -272,6 +301,24 @@ function CredentialAuthorizeForm({ )} + {authDetails?.extraFields?.map(field => ( +
    +
    + {field.label}: +
    + +
    {errors[field.name] || ''}
    +
    + ))} + {customAuthFields} -
    - - - )} - - + ) } diff --git a/frontend/src/components/AllIntegrations/Rapidmail/RapidmailCommonFunc.js b/frontend/src/components/AllIntegrations/Rapidmail/RapidmailCommonFunc.js index ef4c66014..84cda171d 100644 --- a/frontend/src/components/AllIntegrations/Rapidmail/RapidmailCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Rapidmail/RapidmailCommonFunc.js @@ -25,10 +25,12 @@ export const handleInput = ( export const getAllRecipient = (rapidmailConf, setRapidmailConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const queryParams = { - username: rapidmailConf.username, - password: rapidmailConf.password - } + const queryParams = rapidmailConf?.connection_id + ? { connection_id: rapidmailConf.connection_id } + : { + username: rapidmailConf.username, + password: rapidmailConf.password + } const loadPostTypes = bitsFetch(null, 'rapidmail_get_all_recipients', queryParams, 'GET').then( result => { if (result && result.success) { @@ -75,55 +77,3 @@ export const checkMappedFields = rapidmailConf => { } return true } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.username || !confTmp.password) { - setError({ - username: !confTmp.username ? __("Username can't be empty", 'bit-integrations') : '', - password: !confTmp.password ? __("Password can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setIsLoading(true) - - const tokenRequestParams = { - username: confTmp.username, - password: confTmp.password - } - - bitsFetch(tokenRequestParams, 'rapidmail_authorization') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/Smaily/Smaily.jsx b/frontend/src/components/AllIntegrations/Smaily/Smaily.jsx index 68e67543d..48ede5a47 100644 --- a/frontend/src/components/AllIntegrations/Smaily/Smaily.jsx +++ b/frontend/src/components/AllIntegrations/Smaily/Smaily.jsx @@ -74,9 +74,6 @@ function Smaily({ formFields, setFlow, flow, allIntegURL }) { setSmailyConf={setSmailyConf} step={step} setStep={setStep} - loading={loading} - setLoading={setLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/Smaily/SmailyAuthorization.jsx b/frontend/src/components/AllIntegrations/Smaily/SmailyAuthorization.jsx index 063739427..007e4b4bc 100644 --- a/frontend/src/components/AllIntegrations/Smaily/SmailyAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Smaily/SmailyAuthorization.jsx @@ -1,47 +1,10 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { smailyAuthentication } from './SmailyCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' - -export default function SmailyAuthorization({ - smailyConf, - setSmailyConf, - step, - setStep, - loading, - setLoading, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ - name: '', - subdomain: '', - api_user_name: '', - api_user_password: '' - }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !smailyConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...smailyConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSmailyConf(newConf) - } +import Authorization from '../../Connections/Authorization' +export default function SmailyAuthorization({ smailyConf, setSmailyConf, step, setStep, isInfo }) { const note = `

    ${__( 'To create API username and password, do the following.', 'bit-integrations' @@ -60,111 +23,28 @@ const nextPage = () => { ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - -
    {error.name}
    -
    - {__('Subdomain Name:', 'bit-integrations')} -
    - -
    {error.subdomain}
    -
    - {__('API User Name:', 'bit-integrations')} -
    - -
    {error.api_user_name}
    -
    - {__('API User Password:', 'bit-integrations')} -
    - -
    {error.api_user_password}
    - {smailyConf.subdomain && ( - - {__('To Get subdomain, API user name and password Please Visit', 'bit-integrations')} -   - - {__('Smaily API Token', 'bit-integrations')} - - - )} -
    -
    - - {!isInfo && ( -
    - -
    - -
    - )} - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Smaily/SmailyCommonFunc.js b/frontend/src/components/AllIntegrations/Smaily/SmailyCommonFunc.js index 18779d2fd..3ad4cda5a 100644 --- a/frontend/src/components/AllIntegrations/Smaily/SmailyCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Smaily/SmailyCommonFunc.js @@ -1,9 +1,3 @@ -/* 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, smailyConf, setSmailyConf) => { const newConf = { ...smailyConf } const { name } = e.target @@ -37,48 +31,3 @@ export const checkMappedFields = smailyConf => { } return true } - -export const smailyAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.name || !confTmp.subdomain || !confTmp.api_user_name || !confTmp.api_user_password) { - setError({ - name: !confTmp.name ? __("Integration Name can't be empty", 'bit-integrations') : '', - subdomain: !confTmp.subdomain ? __("Subdomain can't be empty", 'bit-integrations') : '', - api_user_name: !confTmp.api_user_name - ? __("Api user name can't be empty", 'bit-integrations') - : '', - api_user_password: !confTmp.api_user_password - ? __("Api user password can't be empty", 'bit-integrations') - : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - subdomain: confTmp.subdomain, - api_user_name: confTmp.api_user_name, - api_user_password: confTmp.api_user_password - } - - bitsFetch(requestParams, 'smaily_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - __('Authorized failed, Please enter valid subdomain name & API credentials', 'bit-integrations') - ) - }) -} diff --git a/frontend/src/components/AllIntegrations/Twilio/Twilio.jsx b/frontend/src/components/AllIntegrations/Twilio/Twilio.jsx index 16dd8b18b..951ce2acb 100644 --- a/frontend/src/components/AllIntegrations/Twilio/Twilio.jsx +++ b/frontend/src/components/AllIntegrations/Twilio/Twilio.jsx @@ -72,9 +72,6 @@ function Twilio({ formFields, setFlow, flow, allIntegURL }) { setTwilioConf={setTwilioConf} step={step} setstep={setstep} - isLoading={isLoading} - setIsLoading={setIsLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/Twilio/TwilioAuthorization.jsx b/frontend/src/components/AllIntegrations/Twilio/TwilioAuthorization.jsx index 004b2ca07..8a7a53df1 100644 --- a/frontend/src/components/AllIntegrations/Twilio/TwilioAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Twilio/TwilioAuthorization.jsx @@ -1,137 +1,45 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize } from './TwilioCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' - -export default function TwilioAuthorization({ - twilioConf, - setTwilioConf, - step, - setstep, - isLoading, - setIsLoading, - setSnackbar, - isInfo -}) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ username: '', password: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setstep(2) - } - const handleInput = e => { - const newConf = { ...twilioConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setTwilioConf(newConf) - } +import Authorization from '../../Connections/Authorization' + +export default function TwilioAuthorization({ twilioConf, setTwilioConf, step, setstep, isInfo }) { + const note = `

    ${__('To get Account SID and Auth Token:', 'bit-integrations')}

    +
      +
    • ${__( + 'Visit your', + 'bit-integrations' + )} Twilio Console.
    • +
    • ${__('Copy your Account SID and use it as Username.', 'bit-integrations')}
    • +
    • ${__('Copy your Auth Token and use it as Password.', 'bit-integrations')}
    • +
    • ${__('Use your Twilio sender number in the From Number field.', 'bit-integrations')}
    • +
    ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Account SID:', 'bit-integrations')} -
    - -
    {error.sid}
    - - - {__('To get Account SID and Auth Token , Please Visit', 'bit-integrations')}{' '} - - {__('Twilio Console', 'bit-integrations')} - - - -
    - {__('Auth Token:', 'bit-integrations')} -
    - -
    {error.token}
    - -
    - {__('From:', 'bit-integrations')} -
    - -
    {error.from_num}
    - - {!isInfo && ( -
    - -
    - -
    - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Twilio/TwilioCommonFunc.js b/frontend/src/components/AllIntegrations/Twilio/TwilioCommonFunc.js index 82ad9280b..161261b6e 100644 --- a/frontend/src/components/AllIntegrations/Twilio/TwilioCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Twilio/TwilioCommonFunc.js @@ -1,8 +1,3 @@ -/* eslint-disable no-else-return */ -import toast from 'react-hot-toast' -import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' - export const handleInput = ( e, twilioConf, @@ -37,57 +32,3 @@ export const checkMappedFields = twilioConf => { } return true } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.sid || !confTmp.token || !confTmp.from_num) { - setError({ - sid: !confTmp.sid ? __("Account SID can't be empty", 'bit-integrations') : '', - token: !confTmp.token ? __("Auth Token can't be empty", 'bit-integrations') : '', - from_num: !confTmp.from_num ? __("Phone number can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setIsLoading(true) - - const tokenRequestParams = { - sid: confTmp.sid, - token: confTmp.token, - from_num: confTmp.from_num - } - - bitsFetch(tokenRequestParams, 'twilio_authorization') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} From f62ef0231820ad53462b7bb95972f7ace88d2a08 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 16:12:14 +0600 Subject: [PATCH 24/58] refactor: Slack, SmartSuite, Vbout, and WhatsApp integrations to improve authorization handling and channel fetching - Updated Slack integration to use toast notifications for error handling and channel fetching. - Refactored SmartSuite integration to streamline authentication and solution fetching processes. - Simplified Vbout integration by removing unnecessary state management and using a more modular approach for authorization. - Enhanced WhatsApp integration to utilize a centralized Authorization component for better code organization and maintainability. - Improved header template resolution in AddNewConnection component to dynamically handle additional headers based on provided data. --- .../Actions/AgiledCRM/AgiledCRMController.php | 32 +-- backend/Actions/AgiledCRM/Routes.php | 1 - .../Actions/Airtable/AirtableController.php | 14 +- backend/Actions/Airtable/Routes.php | 2 +- backend/Actions/Discord/DiscordController.php | 44 +---- backend/Actions/Discord/Routes.php | 1 - .../EmailOctopus/EmailOctopusController.php | 14 +- backend/Actions/EmailOctopus/Routes.php | 2 +- .../GetResponse/GetResponseController.php | 21 +- backend/Actions/GetResponse/Routes.php | 1 - .../Groundhogg/GroundhoggController.php | 44 ++--- backend/Actions/Groundhogg/Routes.php | 1 - backend/Actions/Line/LineController.php | 25 +-- backend/Actions/Line/Routes.php | 5 - .../MailBluster/MailBlusterController.php | 11 +- backend/Actions/MailBluster/Routes.php | 2 +- backend/Actions/Salesmate/Routes.php | 1 - .../Actions/Salesmate/SalesmateController.php | 27 +-- backend/Actions/Slack/Routes.php | 2 +- backend/Actions/Slack/SlackController.php | 27 ++- backend/Actions/SmartSuite/Routes.php | 1 - .../SmartSuite/SmartSuiteController.php | 23 +-- backend/Actions/Vbout/Routes.php | 1 - backend/Actions/Vbout/VboutController.php | 35 +--- backend/Actions/WhatsApp/Routes.php | 1 - .../Actions/WhatsApp/WhatsAppController.php | 28 ++- .../AllIntegrations/AgiledCRM/Agiled.jsx | 3 - .../AgiledCRM/AgiledActions.jsx | 20 +- .../AgiledCRM/AgiledAuthorization.jsx | 159 ++++----------- .../AgiledCRM/AgiledCommonFunc.js | 128 ++++++------ .../AgiledCRM/AgiledIntegLayout.jsx | 10 +- .../AllIntegrations/Airtable/Airtable.jsx | 1 - .../Airtable/AirtableAuthorization.jsx | 134 ++++--------- .../Airtable/AirtableCommonFunc.js | 77 +++----- .../Airtable/AirtableIntegLayout.jsx | 17 +- .../AllIntegrations/Discord/Discord.jsx | 6 +- .../Discord/DiscordAuthorization.jsx | 163 ++++++--------- .../Discord/DiscordCommonFunc.js | 113 ++--------- .../EmailOctopus/EmailOctopus.jsx | 1 - .../EmailOctopus/EmailOctopusActions.jsx | 4 +- .../EmailOctopusAuthorization.jsx | 132 ++++--------- .../EmailOctopus/EmailOctopusCommonFunc.js | 80 +++----- .../EmailOctopus/EmailOctopusIntegLayout.jsx | 23 +-- .../GetResponse/GetResponseAuthorization.jsx | 168 ++++++---------- .../GetResponse/GetResponseCommonFunc.js | 34 ++-- .../GetResponse/GetResponseIntegLayout.jsx | 12 +- .../AllIntegrations/Groundhogg/Groundhogg.jsx | 10 +- .../Groundhogg/GroundhoggActions.jsx | 2 +- .../Groundhogg/GroundhoggAuthorization.jsx | 186 +++++++----------- .../Groundhogg/GroundhoggCommonFunc.js | 65 ++---- .../Groundhogg/GroundhoggIntegLayout.jsx | 2 +- .../components/AllIntegrations/Line/Line.jsx | 9 +- .../Line/LineAuthorization.jsx | 134 ++----------- .../AllIntegrations/Line/LineCommonFunc.js | 53 ----- .../MailBluster/MailBlusterAuthorization.jsx | 165 ++++++---------- .../MailBluster/MailBlusterCommonFunc.js | 37 ++-- .../MailBluster/MailBlusterIntegLayout.jsx | 12 +- .../AllIntegrations/Salesmate/Salesmate.jsx | 3 - .../Salesmate/SalesmateAuthorization.jsx | 168 +++------------- .../Salesmate/SalesmateCommonFunc.js | 88 ++------- .../AllIntegrations/Slack/Slack.jsx | 9 +- .../Slack/SlackAuthorization.jsx | 156 +++++---------- .../AllIntegrations/Slack/SlackCommonFunc.js | 63 +++--- .../Slack/SlackIntegLayout.jsx | 35 ++-- .../AllIntegrations/SmartSuite/SmartSuite.jsx | 2 - .../SmartSuite/SmartSuiteAuthorization.jsx | 184 ++++++----------- .../SmartSuite/SmartSuiteCommonFunc.js | 64 +----- .../AllIntegrations/Vbout/Vbout.jsx | 1 - .../Vbout/VboutAuthorization.jsx | 176 ++++++----------- .../AllIntegrations/Vbout/VboutCommonFunc.js | 32 +-- .../AllIntegrations/WhatsApp/WhatsApp.jsx | 12 +- .../WhatsApp/WhatsAppAuthorization.jsx | 166 ++++------------ .../WhatsApp/WhatsAppCommonFunc.js | 64 ++---- .../WhatsApp/WhatsAppIntegLayout.jsx | 1 - .../Connections/AddNewConnection.jsx | 18 +- 75 files changed, 1097 insertions(+), 2471 deletions(-) diff --git a/backend/Actions/AgiledCRM/AgiledCRMController.php b/backend/Actions/AgiledCRM/AgiledCRMController.php index d9965882f..86346b741 100644 --- a/backend/Actions/AgiledCRM/AgiledCRMController.php +++ b/backend/Actions/AgiledCRM/AgiledCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\AgiledCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,29 +15,16 @@ */ class AgiledCRMController { - protected $_defaultHeader; - - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->auth_token) || empty($fieldsRequestParams->brand)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'agiledcrm', + 'fields' => [ + 'auth_token' => 'value', + 'brand' => 'brand', + ], + ]; - $brand = $fieldsRequestParams->brand; - $apiKey = $fieldsRequestParams->auth_token; - $apiEndpoint = "https://my.agiled.app/api/v1/users?api_token={$apiKey}"; - $header = [ - 'Brand' => $brand - ]; - - $response = HttpHelper::get($apiEndpoint, null, $header); - - if (isset($response->data[0]->id)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Brand name & API key', 'bit-integrations'), 400); - } - } + protected $_defaultHeader; public function getAllOwners($fieldsRequestParams) { diff --git a/backend/Actions/AgiledCRM/Routes.php b/backend/Actions/AgiledCRM/Routes.php index 7d60da9fa..6972919eb 100644 --- a/backend/Actions/AgiledCRM/Routes.php +++ b/backend/Actions/AgiledCRM/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\AgiledCRM\AgiledCRMController; use BitApps\Integrations\Core\Util\Route; -Route::post('agiled_authentication', [AgiledCRMController::class, 'authentication']); Route::post('agiled_fetch_all_owners', [AgiledCRMController::class, 'getAllOwners']); Route::post('agiled_fetch_all_accounts', [AgiledCRMController::class, 'getAllAccounts']); Route::post('agiled_fetch_all_sources', [AgiledCRMController::class, 'getAllSources']); diff --git a/backend/Actions/Airtable/AirtableController.php b/backend/Actions/Airtable/AirtableController.php index 5961cb54e..848866f32 100644 --- a/backend/Actions/Airtable/AirtableController.php +++ b/backend/Actions/Airtable/AirtableController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Airtable; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,9 +15,17 @@ */ class AirtableController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'airtable', + 'fields' => [ + 'auth_token' => 'token', + ], + ]; + protected $_defaultHeader; - public function authentication($fieldsRequestParams) + public function fetchAllBases($fieldsRequestParams) { if (empty($fieldsRequestParams->auth_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); @@ -31,6 +40,7 @@ public function authentication($fieldsRequestParams) $response = HttpHelper::get($apiEndpoint, null, $header); if (isset($response->bases)) { + $bases = []; foreach ($response->bases as $base) { if ($base->permissionLevel === 'create') { $bases[] = [ @@ -61,6 +71,7 @@ public function getAllTables($fieldsRequestParams) $response = HttpHelper::get($apiEndpoint, null, $header); if (isset($response->tables)) { + $tables = []; foreach ($response->tables as $table) { $tables[] = [ 'id' => $table->id, @@ -91,6 +102,7 @@ public function getAllFields($fieldsRequestParams) $acceptedTypes = ['singleLineText', 'multilineText', 'singleSelect', 'multipleSelects', 'multipleAttachments', 'date', 'phoneNumber', 'email', 'url', 'number', 'currency', 'percent', 'duration', 'rating', 'barcode']; if (isset($response->tables)) { + $fields = []; foreach ($response->tables as $table) { if ($table->id === $tableId) { foreach ($table->fields as $field) { diff --git a/backend/Actions/Airtable/Routes.php b/backend/Actions/Airtable/Routes.php index 5993a390b..38e18d854 100644 --- a/backend/Actions/Airtable/Routes.php +++ b/backend/Actions/Airtable/Routes.php @@ -7,6 +7,6 @@ use BitApps\Integrations\Actions\Airtable\AirtableController; use BitApps\Integrations\Core\Util\Route; -Route::post('airtable_authentication', [AirtableController::class, 'authentication']); +Route::post('airtable_fetch_all_bases', [AirtableController::class, 'fetchAllBases']); Route::post('airtable_fetch_all_tables', [AirtableController::class, 'getAllTables']); Route::post('airtable_fetch_all_fields', [AirtableController::class, 'getAllFields']); diff --git a/backend/Actions/Discord/DiscordController.php b/backend/Actions/Discord/DiscordController.php index 0062cbdef..9256d7474 100644 --- a/backend/Actions/Discord/DiscordController.php +++ b/backend/Actions/Discord/DiscordController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Discord; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -16,42 +17,13 @@ class DiscordController { public const APIENDPOINT = 'https://discord.com/api/v10'; - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to authorize - * @param mixed $tokenRequestParams - * - * @return JSON discord api response and status - */ - public static function handleAuthorize($tokenRequestParams) - { - if ( - empty($tokenRequestParams->accessToken) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $header = [ - 'Authorization' => 'Bot ' . $tokenRequestParams->accessToken, - ]; - $apiEndpoint = self::APIENDPOINT . '/users/@me'; - - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (!isset($apiResponse->id)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - wp_send_json_success($apiResponse, 200); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'discord', + 'fields' => [ + 'accessToken' => 'value', + ], + ]; public static function fetchServers($tokenRequestParams) { diff --git a/backend/Actions/Discord/Routes.php b/backend/Actions/Discord/Routes.php index 90262fe94..c5be6e01e 100644 --- a/backend/Actions/Discord/Routes.php +++ b/backend/Actions/Discord/Routes.php @@ -8,6 +8,5 @@ use BitApps\Integrations\Core\Util\Route; // Discord -Route::post('handle_authorize', [DiscordController::class, 'handleAuthorize']); Route::post('discord_fetch_servers', [DiscordController::class, 'fetchServers']); Route::post('discord_fetch_channels', [DiscordController::class, 'fetchChannels']); diff --git a/backend/Actions/EmailOctopus/EmailOctopusController.php b/backend/Actions/EmailOctopus/EmailOctopusController.php index 71e946069..3005f5804 100644 --- a/backend/Actions/EmailOctopus/EmailOctopusController.php +++ b/backend/Actions/EmailOctopus/EmailOctopusController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\EmailOctopus; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,9 +15,17 @@ */ class EmailOctopusController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'emailoctopus', + 'fields' => [ + 'auth_token' => 'token', + ], + ]; + protected $_defaultHeader; - public function authentication($fieldsRequestParams) + public function fetchAllLists($fieldsRequestParams) { if (empty($fieldsRequestParams->auth_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); @@ -27,6 +36,7 @@ public function authentication($fieldsRequestParams) $response = HttpHelper::get($apiEndpoint, null, null); if (!isset($response->error)) { + $lists = []; foreach ($response->data as $list) { $lists[] = [ 'id' => $list->id, @@ -51,6 +61,7 @@ public function getAllFields($fieldsRequestParams) $response = HttpHelper::get($apiEndpoint, null, null); if (!isset($response->error)) { + $fields = []; foreach ($response->fields as $field) { $fields[] = [ 'key' => $field->tag, @@ -75,6 +86,7 @@ public function getAllTags($fieldsRequestParams) $apiEndpoint = 'https://emailoctopus.com/api/1.6/lists/' . $listId . '/tags?api_key=' . $apiKey; $response = HttpHelper::get($apiEndpoint, null, null); + $tags = []; foreach ($response->data as $tag) { $tags[] = [ 'name' => $tag->tag diff --git a/backend/Actions/EmailOctopus/Routes.php b/backend/Actions/EmailOctopus/Routes.php index 47f9f169e..3a8a3d305 100644 --- a/backend/Actions/EmailOctopus/Routes.php +++ b/backend/Actions/EmailOctopus/Routes.php @@ -7,6 +7,6 @@ use BitApps\Integrations\Actions\EmailOctopus\EmailOctopusController; use BitApps\Integrations\Core\Util\Route; -Route::post('emailOctopus_authentication', [EmailOctopusController::class, 'authentication']); +Route::post('emailOctopus_fetch_all_lists', [EmailOctopusController::class, 'fetchAllLists']); Route::post('emailOctopus_fetch_all_tags', [EmailOctopusController::class, 'getAllTags']); Route::post('emailOctopus_fetch_all_fields', [EmailOctopusController::class, 'getAllFields']); diff --git a/backend/Actions/GetResponse/GetResponseController.php b/backend/Actions/GetResponse/GetResponseController.php index 9770d13bc..6dd474f56 100644 --- a/backend/Actions/GetResponse/GetResponseController.php +++ b/backend/Actions/GetResponse/GetResponseController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\GetResponse; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class GetResponseController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'getresponse', + 'fields' => [ + 'auth_token' => 'value', + ], + ]; + protected $_defaultHeader; private $baseUrl = 'https://api.getresponse.com/v3/'; @@ -97,9 +106,9 @@ public function fetchAllTags($requestParams) } } - public function authentication($refreshFieldsRequestParams) + public function fetchAllList($requestParams) { - if (empty($refreshFieldsRequestParams->auth_token)) { + if (empty($requestParams->auth_token)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -108,16 +117,14 @@ public function authentication($refreshFieldsRequestParams) 400 ); } - $apiEndpoints = $this->baseUrl . 'campaigns'; - - $apiKey = $refreshFieldsRequestParams->auth_token; + $apiEndpoints = $this->baseUrl . 'campaigns'; + $apiKey = $requestParams->auth_token; $header = [ 'X-Auth-Token' => 'api-key ' . $apiKey, ]; $response = HttpHelper::get($apiEndpoints, null, $header); - $campaigns = []; foreach ($response as $campaign) { @@ -127,7 +134,7 @@ public function authentication($refreshFieldsRequestParams) ]; } - if (property_exists($response[0], 'campaignId')) { + if (!empty($response) && isset($response[0]) && property_exists($response[0], 'campaignId')) { wp_send_json_success($campaigns, 200); } else { wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); diff --git a/backend/Actions/GetResponse/Routes.php b/backend/Actions/GetResponse/Routes.php index 258f2169e..3eb8e8499 100644 --- a/backend/Actions/GetResponse/Routes.php +++ b/backend/Actions/GetResponse/Routes.php @@ -8,6 +8,5 @@ use BitApps\Integrations\Core\Util\Route; Route::post('getresponse_fetch_all_tags', [GetResponseController::class, 'fetchAllTags']); -Route::post('getresponse_authentication', [GetResponseController::class, 'authentication']); Route::post('getresponse_fetch_all_list', [GetResponseController::class, 'fetchAllList']); Route::post('getresponse_fetch_custom_fields', [GetResponseController::class, 'fetchCustomFields']); diff --git a/backend/Actions/Groundhogg/GroundhoggController.php b/backend/Actions/Groundhogg/GroundhoggController.php index 0488e49e7..33a19f801 100644 --- a/backend/Actions/Groundhogg/GroundhoggController.php +++ b/backend/Actions/Groundhogg/GroundhoggController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Groundhogg; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,16 @@ */ class GroundhoggController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'groundhogg', + 'fields' => [ + 'token' => 'value', + 'public_key' => 'public_key', + 'domainName' => 'domainName', + ], + ]; + public static function groundhoggFetchAllTags($requestParams) { if ( @@ -47,39 +58,6 @@ public static function groundhoggFetchAllTags($requestParams) } } - public static function fetchAllContacts($requestParams) - { - if ( - empty($requestParams->public_key) || empty($requestParams->token) - || empty($requestParams->domainName) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $authorizationHeader = [ - 'Gh-Token' => $requestParams->token, - 'Gh-Public-Key' => $requestParams->public_key - ]; - $apiEndpoint = $requestParams->domainName . '/index.php?rest_route=/gh/v4/contacts'; - - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if ($apiResponse->status === 'success') { - wp_send_json_success($apiResponse, 200); - } else { - wp_send_json_error( - 'There is an error .', - 400 - ); - } - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/Groundhogg/Routes.php b/backend/Actions/Groundhogg/Routes.php index dedef3fd1..f25e27f5a 100644 --- a/backend/Actions/Groundhogg/Routes.php +++ b/backend/Actions/Groundhogg/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Groundhogg\GroundhoggController; use BitApps\Integrations\Core\Util\Route; -Route::post('groundhogg_authorization_and_fetch_contacts', [GroundhoggController::class, 'fetchAllContacts']); Route::post('groundhogg_fetch_all_tags', [GroundhoggController::class, 'groundhoggFetchAllTags']); diff --git a/backend/Actions/Line/LineController.php b/backend/Actions/Line/LineController.php index a2b03359f..8746011a9 100644 --- a/backend/Actions/Line/LineController.php +++ b/backend/Actions/Line/LineController.php @@ -2,28 +2,19 @@ namespace BitApps\Integrations\Actions\Line; -use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Authorization\AuthorizationType; class LineController { public const APIENDPOINT = 'https://api.line.me/v2/bot'; - public static function authorization($tokenRequestParams) - { - if (empty($tokenRequestParams->accessToken)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $header = ['Authorization' => 'Bearer ' . $tokenRequestParams->accessToken]; - $apiEndpoint = self::APIENDPOINT . '/info'; - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (is_wp_error($apiResponse) || empty($apiResponse->userId)) { - wp_send_json_error(empty($apiResponse->message) ? 'Unknown' : $apiResponse->message, 400); - } - - wp_send_json_success($apiResponse, 200); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'line', + 'fields' => [ + 'accessToken' => 'token', + ], + ]; public function execute($integrationData, $fieldValues) { diff --git a/backend/Actions/Line/Routes.php b/backend/Actions/Line/Routes.php index b4beb193d..5c539fc24 100644 --- a/backend/Actions/Line/Routes.php +++ b/backend/Actions/Line/Routes.php @@ -3,8 +3,3 @@ if (!defined('ABSPATH')) { exit; } - -use BitApps\Integrations\Actions\Line\LineController; -use BitApps\Integrations\Core\Util\Route; - -Route::post('line_authorization', [LineController::class, 'authorization']); diff --git a/backend/Actions/MailBluster/MailBlusterController.php b/backend/Actions/MailBluster/MailBlusterController.php index a17d13e91..654dc24af 100644 --- a/backend/Actions/MailBluster/MailBlusterController.php +++ b/backend/Actions/MailBluster/MailBlusterController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\MailBluster; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,11 +15,19 @@ */ class MailBlusterController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'mailbluster', + 'fields' => [ + 'auth_token' => 'value', + ], + ]; + protected $_defaultHeader; private $baseUrl = 'https://api.mailbluster.com/api/'; - public function authentication($fieldsRequestParams) + public function fetchCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->auth_token)) { wp_send_json_error( diff --git a/backend/Actions/MailBluster/Routes.php b/backend/Actions/MailBluster/Routes.php index 9a5b84170..fb07b8645 100644 --- a/backend/Actions/MailBluster/Routes.php +++ b/backend/Actions/MailBluster/Routes.php @@ -7,4 +7,4 @@ use BitApps\Integrations\Actions\MailBluster\MailBlusterController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailBluster_authentication', [MailBlusterController::class, 'authentication']); +Route::post('mailBluster_fetch_custom_fields', [MailBlusterController::class, 'fetchCustomFields']); diff --git a/backend/Actions/Salesmate/Routes.php b/backend/Actions/Salesmate/Routes.php index a39b62e9d..ea7d91c98 100644 --- a/backend/Actions/Salesmate/Routes.php +++ b/backend/Actions/Salesmate/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Salesmate\SalesmateController; use BitApps\Integrations\Core\Util\Route; -Route::post('salesmate_authentication', [SalesmateController::class, 'authentication']); Route::post('Salesmate_fields', [SalesmateController::class, 'getAllFields']); Route::post('salesmate_fetch_all_CRMSources', [SalesmateController::class, 'getAllCRMSources']); Route::post('salesmate_fetch_all_currencies', [SalesmateController::class, 'getAllCurrencies']); diff --git a/backend/Actions/Salesmate/SalesmateController.php b/backend/Actions/Salesmate/SalesmateController.php index e487d2fb4..8aa05b993 100644 --- a/backend/Actions/Salesmate/SalesmateController.php +++ b/backend/Actions/Salesmate/SalesmateController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Salesmate; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,29 +15,21 @@ */ class SalesmateController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'salesmate', + 'fields' => [ + 'session_token' => 'value', + 'link_name' => 'link_name', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; protected $linkName; - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $sessionToken = $fieldsRequestParams->session_token; - $this->linkName = $fieldsRequestParams->link_name; - $apiEndpoint = $this->setApiEndpoint() . 'v1/users/active'; - - $headers = $this->setHeaders($sessionToken); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->Status) && $response->Status === 'success') { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Session Token or Link Name', 'bit-integrations'), 400); - } - } - public function getAllFields($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams, $fieldsRequestParams->action_id); diff --git a/backend/Actions/Slack/Routes.php b/backend/Actions/Slack/Routes.php index 56e7ea290..03639e133 100644 --- a/backend/Actions/Slack/Routes.php +++ b/backend/Actions/Slack/Routes.php @@ -8,4 +8,4 @@ use BitApps\Integrations\Core\Util\Route; // Slack -Route::post('slack_authorization_and_fetch_channels', [SlackController::class, 'checkAuthorizationAndFetchChannels']); +Route::post('slack_fetch_channels', [SlackController::class, 'fetchChannels']); diff --git a/backend/Actions/Slack/SlackController.php b/backend/Actions/Slack/SlackController.php index bdcfe403a..526c82de4 100644 --- a/backend/Actions/Slack/SlackController.php +++ b/backend/Actions/Slack/SlackController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Slack; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -16,6 +17,14 @@ class SlackController { public const APIENDPOINT = 'https://slack.com/api'; + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'slack', + 'fields' => [ + 'accessToken' => 'token', + ], + ]; + /** * Process ajax request for generate_token * @@ -24,7 +33,7 @@ class SlackController * * @return JSON slack api response and status */ - public static function checkAuthorizationAndFetchChannels($tokenRequestParams) + public static function fetchChannels($tokenRequestParams) { if ( empty($tokenRequestParams->accessToken) @@ -52,8 +61,20 @@ public static function checkAuthorizationAndFetchChannels($tokenRequestParams) 400 ); } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); + + $channels = []; + if (!empty($apiResponse->channels) && \is_array($apiResponse->channels)) { + foreach ($apiResponse->channels as $channel) { + if (!empty($channel->id) && !empty($channel->name)) { + $channels[] = [ + 'id' => $channel->id, + 'name' => $channel->name, + ]; + } + } + } + + wp_send_json_success(['channels' => $channels], 200); } public function execute($integrationData, $fieldValues) diff --git a/backend/Actions/SmartSuite/Routes.php b/backend/Actions/SmartSuite/Routes.php index 94c16791d..a90864c4f 100644 --- a/backend/Actions/SmartSuite/Routes.php +++ b/backend/Actions/SmartSuite/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\SmartSuite\SmartSuiteController; use BitApps\Integrations\Core\Util\Route; -Route::post('smartSuite_authentication', [SmartSuiteController::class, 'authentication']); Route::post('smartSuite_fetch_all_solutions', [SmartSuiteController::class, 'getAllSolutions']); Route::post('smartSuite_fetch_all_tables', [SmartSuiteController::class, 'getAllTables']); Route::post('smartSuite_fetch_all_user', [SmartSuiteController::class, 'getAllUser']); diff --git a/backend/Actions/SmartSuite/SmartSuiteController.php b/backend/Actions/SmartSuite/SmartSuiteController.php index 8bbc6367d..1d25e2528 100644 --- a/backend/Actions/SmartSuite/SmartSuiteController.php +++ b/backend/Actions/SmartSuite/SmartSuiteController.php @@ -6,10 +6,20 @@ namespace BitApps\Integrations\Actions\SmartSuite; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; class SmartSuiteController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'smartsuite', + 'fields' => [ + 'apiToken' => 'value', + 'workspaceId' => 'workspaceId', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -108,19 +118,6 @@ public function execute($integrationData, $fieldValues) return $smartSuiteApiResponse; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->workspaceId, $fieldsRequestParams->apiToken); - $apiEndpoint = $this->apiEndpoint . 'solutions/'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - if (\is_array($response)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error($response, 400); - } - } - private function checkValidation($fieldsRequestParams) { if (empty($fieldsRequestParams->workspaceId) || empty($fieldsRequestParams->apiToken)) { diff --git a/backend/Actions/Vbout/Routes.php b/backend/Actions/Vbout/Routes.php index 9bfcdc00f..058e99686 100644 --- a/backend/Actions/Vbout/Routes.php +++ b/backend/Actions/Vbout/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Vbout\VboutController; use BitApps\Integrations\Core\Util\Route; -Route::post('vbout_handle_authorize', [VboutController::class, 'handleAuthorize']); Route::post('vbout_fetch_all_lists', [VboutController::class, 'fetchAllLists']); Route::post('vbout_refresh_fields', [VboutController::class, 'vboutRefreshFields']); diff --git a/backend/Actions/Vbout/VboutController.php b/backend/Actions/Vbout/VboutController.php index 91fc1fe64..d226e30f6 100644 --- a/backend/Actions/Vbout/VboutController.php +++ b/backend/Actions/Vbout/VboutController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Vbout; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,33 +15,15 @@ */ class VboutController { - private $baseUrl = 'https://api.vbout.com/1/'; - - public function handleAuthorize($requestParams) - { - if (empty($requestParams->auth_token)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoints = $this->baseUrl . 'app/me.json?key=' . $requestParams->auth_token; + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'vbout', + 'fields' => [ + 'auth_token' => 'value', + ], + ]; - $response = HttpHelper::post($apiEndpoints, null); - if ($response->response->header->status !== 'ok') { - wp_send_json_error( - __( - 'Invalid token', - 'bit-integrations' - ), - 400 - ); - } - wp_send_json_success($response, 200); - } + private $baseUrl = 'https://api.vbout.com/1/'; public function fetchAllLists($requestParams) { diff --git a/backend/Actions/WhatsApp/Routes.php b/backend/Actions/WhatsApp/Routes.php index 8a0846131..59098539b 100644 --- a/backend/Actions/WhatsApp/Routes.php +++ b/backend/Actions/WhatsApp/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\WhatsApp\WhatsAppController; use BitApps\Integrations\Core\Util\Route; -Route::post('whats_app_authorization', [WhatsAppController::class, 'authorization']); Route::post('whats_app_all_template', [WhatsAppController::class, 'getAllTemplate']); diff --git a/backend/Actions/WhatsApp/WhatsAppController.php b/backend/Actions/WhatsApp/WhatsAppController.php index 6e5dc9bfd..0d8d888f1 100644 --- a/backend/Actions/WhatsApp/WhatsAppController.php +++ b/backend/Actions/WhatsApp/WhatsAppController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\WhatsApp; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,22 +15,17 @@ */ class WhatsAppController { - private $baseUrl = 'https://graph.facebook.com/v20.0/'; - - public function authorization($requestParams) - { - static::checkValidation($requestParams); - - $headers = static::setHeaders($requestParams->token); - $apiEndpoint = "{$this->baseUrl}{$requestParams->businessAccountID}"; - $response = HttpHelper::get($apiEndpoint, null, $headers); + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'whatsapp', + 'fields' => [ + 'token' => 'token', + 'numberID' => 'numberID', + 'businessAccountID' => 'businessAccountID', + ], + ]; - if (is_wp_error($response) || !isset($response->id)) { - wp_send_json_error(isset($response->error->message) ? $response->error->message : 'Authentication failed', 400); - } else { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } - } + private $baseUrl = 'https://graph.facebook.com/v20.0/'; public function getAllTemplate($requestParams) { @@ -101,7 +97,7 @@ private static function getTemplate($apiEndpoint, $token) private static function checkValidation($requestParams) { - if (empty($requestParams->numberID) || empty($requestParams->businessAccountID || empty($requestParams->token))) { + if (empty($requestParams->numberID) || empty($requestParams->businessAccountID) || empty($requestParams->token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } } diff --git a/frontend/src/components/AllIntegrations/AgiledCRM/Agiled.jsx b/frontend/src/components/AllIntegrations/AgiledCRM/Agiled.jsx index 1d6af93b8..a4d81c939 100644 --- a/frontend/src/components/AllIntegrations/AgiledCRM/Agiled.jsx +++ b/frontend/src/components/AllIntegrations/AgiledCRM/Agiled.jsx @@ -120,9 +120,6 @@ function Agiled({ formFields, setFlow, flow, allIntegURL }) { setAgiledConf={setAgiledConf} step={step} setStep={setStep} - loading={loading} - setLoading={setLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledActions.jsx b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledActions.jsx index 623597d05..241e5bf1f 100644 --- a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledActions.jsx +++ b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledActions.jsx @@ -33,7 +33,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL if (type === 'owner') { if (e.target?.checked) { - getAllOwners(agiledConf, setAgiledConf, setLoading) + getAllOwners(agiledConf, setAgiledConf, loading, setLoading) newConf.actions.owner = true } else { setActionMdl({ show: false }) @@ -41,7 +41,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL } } else if (type === 'account') { if (e.target?.checked) { - getAllAccounts(agiledConf, setAgiledConf, setLoading) + getAllAccounts(agiledConf, setAgiledConf, loading, setLoading) newConf.actions.account = true } else { setActionMdl({ show: false }) @@ -49,7 +49,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL } } else if (type === 'source') { if (e.target?.checked) { - getAllSources(agiledConf, setAgiledConf, setLoading) + getAllSources(agiledConf, setAgiledConf, loading, setLoading) newConf.actions.source = true } else { setActionMdl({ show: false }) @@ -57,7 +57,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL } } else if (type === 'status') { if (e.target?.checked) { - getAllStatuses(agiledConf, setAgiledConf, setLoading) + getAllStatuses(agiledConf, setAgiledConf, loading, setLoading) newConf.actions.status = true } else { setActionMdl({ show: false }) @@ -65,7 +65,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL } } else if (type === 'lifeCycleStage') { if (e.target?.checked) { - getAllLifeCycleStage(agiledConf, setAgiledConf, setLoading) + getAllLifeCycleStage(agiledConf, setAgiledConf, loading, setLoading) newConf.actions.lifeCycleStage = true } else { setActionMdl({ show: false }) @@ -169,7 +169,7 @@ export default function AgiledActions({ agiledConf, setAgiledConf, loading, setL singleSelect /> -
    - - - )} - + ) } + +const note = `${__('Example: name.agiled.app', 'bit-integrations')}` diff --git a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledCommonFunc.js b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledCommonFunc.js index 37fd5ba08..828999dd4 100644 --- a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledCommonFunc.js +++ b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledCommonFunc.js @@ -46,43 +46,12 @@ export const checkMappedFields = agiledConf => { return true } -export const agiledAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.brand || !confTmp.auth_token) { - setError({ - brand: !confTmp.brand ? __("Brand Name (Account URL) can't be empty", 'bit-integrations') : '', - auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) +export const getAllOwners = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, owners: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } - - bitsFetch(requestParams, 'agiled_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid brand name & API key', 'bit-integrations')) - }) -} - -export const getAllOwners = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, owners: true }) - - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_owners').then(result => { if (result && result.success) { @@ -91,20 +60,22 @@ export const getAllOwners = (confTmp, setConf, setLoading) => { newConf.owners = result.data } setConf(newConf) - setLoading({ ...setLoading, owners: false }) + setLoading({ ...loading, owners: false }) toast.success(__('Owners fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, owners: false }) + setLoading({ ...loading, owners: false }) toast.error(__('Owners fetching failed', 'bit-integrations')) }) } -export const getAllAccounts = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, accounts: true }) +export const getAllAccounts = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, accounts: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_accounts').then(result => { if (result && result.success) { @@ -113,20 +84,22 @@ export const getAllAccounts = (confTmp, setConf, setLoading) => { newConf.accounts = result.data } setConf(newConf) - setLoading({ ...setLoading, accounts: false }) + setLoading({ ...loading, accounts: false }) toast.success(__('Accounts fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, accounts: false }) + setLoading({ ...loading, accounts: false }) toast.error(__('Accounts fetching failed', 'bit-integrations')) }) } -export const getAllSources = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, sources: true }) +export const getAllSources = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, sources: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_sources').then(result => { if (result && result.success) { @@ -135,20 +108,22 @@ export const getAllSources = (confTmp, setConf, setLoading) => { newConf.sources = result.data } setConf(newConf) - setLoading({ ...setLoading, sources: false }) + setLoading({ ...loading, sources: false }) toast.success(__('Sources fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, sources: false }) + setLoading({ ...loading, sources: false }) toast.error(__('Sources fetching failed', 'bit-integrations')) }) } -export const getAllStatuses = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, statuses: true }) +export const getAllStatuses = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, statuses: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_statuses').then(result => { if (result && result.success) { @@ -157,20 +132,22 @@ export const getAllStatuses = (confTmp, setConf, setLoading) => { newConf.statuses = result.data } setConf(newConf) - setLoading({ ...setLoading, statuses: false }) + setLoading({ ...loading, statuses: false }) toast.success(__('Status fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, statuses: false }) + setLoading({ ...loading, statuses: false }) toast.error(__('Status fetching failed', 'bit-integrations')) }) } -export const getAllLifeCycleStage = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, lifeCycleStages: true }) +export const getAllLifeCycleStage = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, lifeCycleStages: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_lifeCycleStages').then(result => { if (result && result.success) { @@ -179,20 +156,22 @@ export const getAllLifeCycleStage = (confTmp, setConf, setLoading) => { newConf.lifeCycleStages = result.data } setConf(newConf) - setLoading({ ...setLoading, lifeCycleStages: false }) + setLoading({ ...loading, lifeCycleStages: false }) toast.success(__('Life cycle stages fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, lifeCycleStages: false }) + setLoading({ ...loading, lifeCycleStages: false }) toast.error(__('Life cycle stages fetching failed', 'bit-integrations')) }) } -export const getAllCRMPipelines = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, CRMPipelines: true }) +export const getAllCRMPipelines = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, CRMPipelines: true }) - const requestParams = { auth_token: confTmp.auth_token, brand: confTmp.brand } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token, brand: confTmp.brand } bitsFetch(requestParams, 'agiled_fetch_all_CRMPipelines').then(result => { if (result && result.success) { @@ -201,24 +180,29 @@ export const getAllCRMPipelines = (confTmp, setConf, setLoading) => { newConf.CRMPipelines = result.data } setConf(newConf) - setLoading({ ...setLoading, CRMPipelines: false }) + setLoading({ ...loading, CRMPipelines: false }) toast.success(__('Pipelines fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, CRMPipelines: false }) + setLoading({ ...loading, CRMPipelines: false }) toast.error(__('Pipelines fetching failed', 'bit-integrations')) }) } -export const getAllCRMPipelineStages = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, CRMPipelineStages: true }) +export const getAllCRMPipelineStages = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, CRMPipelineStages: true }) - const requestParams = { - auth_token: confTmp.auth_token, - brand: confTmp.brand, - selectedCRMPipeline: confTmp.selectedCRMPipeline - } + const requestParams = confTmp.connection_id + ? { + connection_id: confTmp.connection_id, + selectedCRMPipeline: confTmp.selectedCRMPipeline + } + : { + auth_token: confTmp.auth_token, + brand: confTmp.brand, + selectedCRMPipeline: confTmp.selectedCRMPipeline + } bitsFetch(requestParams, 'agiled_fetch_all_CRMPipelineStages').then(result => { if (result && result.success) { @@ -227,12 +211,12 @@ export const getAllCRMPipelineStages = (confTmp, setConf, setLoading) => { newConf.CRMPipelineStages = result.data } setConf(newConf) - setLoading({ ...setLoading, CRMPipelineStages: false }) + setLoading({ ...loading, CRMPipelineStages: false }) toast.success(__('Pipeline stages fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, CRMPipelineStages: false }) + setLoading({ ...loading, CRMPipelineStages: false }) toast.error(__('Pipeline stages fetching failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx index a04a46837..b93c45bf6 100644 --- a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx @@ -24,7 +24,7 @@ export default function AgiledIntegLayout({ if (e.target.value !== '') { newConf[name] = e.target.value if (e.target.value === 'deal') { - getAllCRMPipelines(newConf, setAgiledConf, setLoading) + getAllCRMPipelines(newConf, setAgiledConf, loading, setLoading) } } else { delete newConf[name] @@ -37,7 +37,7 @@ export default function AgiledIntegLayout({ newConf[name] = val if (name === 'selectedCRMPipeline' && val !== '') { newConf.selectedCRMPipelineStages = '' - getAllCRMPipelineStages(newConf, setAgiledConf, setLoading) + getAllCRMPipelineStages(newConf, setAgiledConf, loading, setLoading) } setAgiledConf({ ...newConf }) } @@ -103,7 +103,7 @@ export default function AgiledIntegLayout({ singleSelect /> -
    - - - )} - + ) } diff --git a/frontend/src/components/AllIntegrations/Airtable/AirtableCommonFunc.js b/frontend/src/components/AllIntegrations/Airtable/AirtableCommonFunc.js index 166c32515..dbb0a3769 100644 --- a/frontend/src/components/AllIntegrations/Airtable/AirtableCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Airtable/AirtableCommonFunc.js @@ -30,62 +30,43 @@ export const checkMappedFields = airtableConf => { return true } -export const airtableAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading, - type -) => { - if (!confTmp.auth_token) { - setError({ - auth_token: !confTmp.auth_token - ? __("Personal access token can't be empty", 'bit-integrations') - : '' - }) +export const fetchAllBases = (confTmp, setConf, loading, setLoading, type = 'fetch') => { + if (!confTmp.connection_id && !confTmp.auth_token) { + toast.error(__("Personal access token can't be empty", 'bit-integrations')) return } - setError({}) + setLoading({ ...loading, bases: true }) - if (type === 'authentication') { - setLoading({ ...loading, auth: true }) - } - if (type === 'refreshBases') { - setLoading({ ...loading, bases: true }) - } - const requestParams = { auth_token: confTmp.auth_token } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token } - bitsFetch(requestParams, 'airtable_authentication').then(result => { + bitsFetch(requestParams, 'airtable_fetch_all_bases').then(result => { if (result && result.success) { const newConf = { ...confTmp } - setIsAuthorized(true) - if (type === 'authentication') { - if (result.data) { - newConf.bases = result.data - } - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if (type === 'refreshBases') { - if (result.data) { - newConf.bases = result.data - } - setLoading({ ...loading, bases: false }) - toast.success(__('All bases fectched successfully', 'bit-integrations')) + if (result.data) { + newConf.bases = result.data } setConf(newConf) + setLoading({ ...loading, bases: false }) + toast.success( + type === 'refresh' + ? __('All bases fetched successfully', 'bit-integrations') + : __('Bases fetched successfully', 'bit-integrations') + ) return } - setLoading({ ...loading, auth: false, bases: false }) - toast.error(__('Authorized failed!', 'bit-integrations')) + setLoading({ ...loading, bases: false }) + toast.error(__('Bases fetching failed', 'bit-integrations')) }) } export const getAllTables = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, tables: true }) - const requestParams = { auth_token: confTmp.auth_token, baseId: confTmp.selectedBase } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id, baseId: confTmp.selectedBase } + : { auth_token: confTmp.auth_token, baseId: confTmp.selectedBase } bitsFetch(requestParams, 'airtable_fetch_all_tables').then(result => { if (result && result.success) { @@ -109,11 +90,17 @@ export const getAllFields = (confTmp, setConf, loading, setLoading, type) => { } else if (type === 'refresh') { setLoading({ ...loading, customFields: true }) } - const requestParams = { - auth_token: confTmp.auth_token, - baseId: confTmp.selectedBase, - tableId: confTmp.selectedTable - } + const requestParams = confTmp.connection_id + ? { + connection_id: confTmp.connection_id, + baseId: confTmp.selectedBase, + tableId: confTmp.selectedTable + } + : { + auth_token: confTmp.auth_token, + baseId: confTmp.selectedBase, + tableId: confTmp.selectedTable + } bitsFetch(requestParams, 'airtable_fetch_all_fields').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/Airtable/AirtableIntegLayout.jsx b/frontend/src/components/AllIntegrations/Airtable/AirtableIntegLayout.jsx index 972f51dce..4e3157362 100644 --- a/frontend/src/components/AllIntegrations/Airtable/AirtableIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Airtable/AirtableIntegLayout.jsx @@ -1,12 +1,10 @@ /* eslint-disable no-console */ -/* eslint-disable no-unused-vars */ -import { useState } from 'react' import MultiSelect from 'react-multiple-select-dropdown-lite' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import 'react-multiple-select-dropdown-lite/dist/index.css' import AirtableActions from './AirtableActions' -import { airtableAuthentication, getAllFields, getAllTables } from './AirtableCommonFunc' +import { fetchAllBases, getAllFields, getAllTables } from './AirtableCommonFunc' import AirtableFieldMap from './AirtableFieldMap' import { addFieldMap } from './IntegrationHelpers' @@ -19,9 +17,6 @@ export default function AirtableIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setIsAuthorized] = useState(false) - const setChanges = (val, name) => { const newConf = { ...airtableConf } newConf[name] = val @@ -50,15 +45,7 @@ export default function AirtableIntegLayout({ /> -
    - - - )} - - - + ) } diff --git a/frontend/src/components/AllIntegrations/Discord/DiscordCommonFunc.js b/frontend/src/components/AllIntegrations/Discord/DiscordCommonFunc.js index bd115e8b1..ed5f6dbe4 100644 --- a/frontend/src/components/AllIntegrations/Discord/DiscordCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Discord/DiscordCommonFunc.js @@ -14,19 +14,18 @@ export const handleInput = (e, discordConf, setDiscordConf) => { setDiscordConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { accessToken: confTmp.accessToken } + export const getAllServers = (confTmp, setConf, setIsLoading) => { - if (!confTmp.accessToken) { - setError({ - accessToken: !confTmp.accessToken ? __("Access Token can't be empty", 'bit-integrations') : '' - }) + if (!confTmp.connection_id && !confTmp.accessToken) { + toast.error(__("Access Token can't be empty", 'bit-integrations')) return } setIsLoading(true) - const tokenRequestParams = { accessToken: confTmp.accessToken } - - bitsFetch(tokenRequestParams, 'discord_fetch_servers').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'discord_fetch_servers').then(result => { if (result && result.success) { setConf(oldConf => { const newConf = { ...oldConf } @@ -40,27 +39,34 @@ export const getAllServers = (confTmp, setConf, setIsLoading) => { }) setIsLoading(false) - toast.success(__('Servers fetched successfully', 'bit-integrations')) return } + setIsLoading(false) toast.error(__('Servers fetching failed', 'bit-integrations')) }) } export const getAllChannels = (confTmp, setConf, setIsLoading) => { - if (!confTmp.accessToken) { - setError({ - accessToken: !confTmp.accessToken ? __("Access Token can't be empty", 'bit-integrations') : '' - }) + if (!confTmp.connection_id && !confTmp.accessToken) { + toast.error(__("Access Token can't be empty", 'bit-integrations')) + return + } + + if (!confTmp.selectedServer) { + toast.error(__('Server is required', 'bit-integrations')) return } + setIsLoading(true) - const tokenRequestParams = { accessToken: confTmp.accessToken, serverId: confTmp.selectedServer } + const requestParams = { + ...buildAuthRequestParams(confTmp), + serverId: confTmp.selectedServer + } - bitsFetch(tokenRequestParams, 'discord_fetch_channels').then(result => { + bitsFetch(requestParams, 'discord_fetch_channels').then(result => { if (result && result.success) { setConf(oldConf => { const newConf = { ...oldConf } @@ -74,88 +80,11 @@ export const getAllChannels = (confTmp, setConf, setIsLoading) => { }) setIsLoading(false) - toast.success(__('Channels fetched successfully', 'bit-integrations')) return } + setIsLoading(false) toast.error(__('Channels fetching failed', 'bit-integrations')) }) } - -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.accessToken) { - setError({ - accessToken: !confTmp.accessToken ? __("Access Token can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setIsLoading(true) - - const tokenRequestParams = { accessToken: confTmp.accessToken } - - bitsFetch(tokenRequestParams, 'handle_authorize') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} -// export const handleAuthorize = (confTmp, setConf, setError, setisAuthorized, setIsLoading, setSnackbar) => { -// if (!confTmp.accessToken) { -// setError({ accessToken: !confTmp.accessToken ? __('Access Token can\'t be empty', 'bit-integrations') : '' }) -// return -// } - -// setError({}) -// setIsLoading(true) - -// const tokenRequestParams = { accessToken: confTmp.accessToken } - -// bitsFetch(tokenRequestParams, 'discord_authorization_and_fetch_servers') -// .then(result => result) -// .then(result => { -// if (result && result.success) { -// const newConf = { ...confTmp } -// newConf.tokenDetails = result.data -// setConf(newConf) -// setisAuthorized(true) -// setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) -// } else if ((result && result.data && result.data.data) || (!result.success && typeof result.data === 'string')) { -// setSnackbar({ show: true, msg: `${__('Authorization failed Cause:', 'bit-integrations')}${result.data.data || result.data}. ${__('please try again', 'bit-integrations')}` }) -// } else { -// setSnackbar({ show: true, msg: __('Authorization failed. please try again', 'bit-integrations') }) -// } -// setIsLoading(false) -// }) -// } diff --git a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopus.jsx b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopus.jsx index ddc223151..96bd722f7 100644 --- a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopus.jsx +++ b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopus.jsx @@ -89,7 +89,6 @@ function EmailOctopus({ formFields, setFlow, flow, allIntegURL }) { setStep={setStep} loading={loading} setLoading={setLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusActions.jsx b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusActions.jsx index d4eb7f54f..3ae459abf 100644 --- a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusActions.jsx +++ b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusActions.jsx @@ -20,7 +20,7 @@ export default function EmailOctopusActions({ const newConf = { ...emailOctopusConf } if (type === 'tag') { if (e.target?.checked) { - getAllTags(emailOctopusConf, setEmailOctopusConf, setLoading) + getAllTags(emailOctopusConf, setEmailOctopusConf, loading, setLoading) newConf.actions.tags = true } else { setActionMdl({ show: false }) @@ -111,7 +111,7 @@ export default function EmailOctopusActions({ onChange={val => setChanges(val)} /> -
    - - - )} - + ) } diff --git a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusCommonFunc.js b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusCommonFunc.js index 9aaabce9d..606f705e1 100644 --- a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusCommonFunc.js +++ b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusCommonFunc.js @@ -37,60 +37,43 @@ export const checkMappedFields = emailOctopusConf => { return true } -export const emailOctopusAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading, - type -) => { - if (!confTmp.auth_token) { - setError({ - auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' - }) +export const fetchAllLists = (confTmp, setConf, loading, setLoading, type = 'fetch') => { + if (!confTmp.connection_id && !confTmp.auth_token) { + toast.error(__("Api Key can't be empty", 'bit-integrations')) return } - setError({}) + setLoading({ ...loading, lists: true }) - if (type === 'authentication') { - setLoading({ ...loading, auth: true }) - } - if (type === 'refreshLists') { - setLoading({ ...loading, lists: true }) - } - const requestParams = { auth_token: confTmp.auth_token } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token } - bitsFetch(requestParams, 'emailOctopus_authentication').then(result => { + bitsFetch(requestParams, 'emailOctopus_fetch_all_lists').then(result => { if (result && result.success) { const newConf = { ...confTmp } - setIsAuthorized(true) - if (type === 'authentication') { - if (result.data) { - newConf.lists = result.data - } - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if (type === 'refreshLists') { - if (result.data) { - newConf.lists = result.data - } - setLoading({ ...loading, lists: false }) - toast.success(__('All lists fectched successfully', 'bit-integrations')) + if (result.data) { + newConf.lists = result.data } setConf(newConf) + setLoading({ ...loading, lists: false }) + toast.success( + type === 'refresh' + ? __('All lists fetched successfully', 'bit-integrations') + : __('Lists fetched successfully', 'bit-integrations') + ) return } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid domain name & API key', 'bit-integrations')) + setLoading({ ...loading, lists: false }) + toast.error(__('Lists fetching failed', 'bit-integrations')) }) } -export const getAllFields = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, customFields: true }) - const requestParams = { auth_token: confTmp.auth_token, listId: confTmp.selectedList } +export const getAllFields = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, customFields: true }) + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id, listId: confTmp.selectedList } + : { auth_token: confTmp.auth_token, listId: confTmp.selectedList } bitsFetch(requestParams, 'emailOctopus_fetch_all_fields').then(result => { if (result && result.success) { @@ -99,21 +82,22 @@ export const getAllFields = (confTmp, setConf, setLoading) => { newConf.emailOctopusFields = result.data } setConf(newConf) - setLoading({ ...setLoading, customFields: false }) - setLoading({ ...setLoading, emailOctopusFields: true }) + setLoading({ ...loading, customFields: false, emailOctopusFields: true }) toast.success(__('Fields fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, customFields: false }) + setLoading({ ...loading, customFields: false }) toast.error(__('Fields fetching failed', 'bit-integrations')) }) } -export const getAllTags = (confTmp, setConf, setLoading) => { - setLoading({ tags: true, emailOctopusFields: true }) +export const getAllTags = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, tags: true, emailOctopusFields: true }) - const requestParams = { auth_token: confTmp.auth_token, listId: confTmp.selectedList } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id, listId: confTmp.selectedList } + : { auth_token: confTmp.auth_token, listId: confTmp.selectedList } bitsFetch(requestParams, 'emailOctopus_fetch_all_tags').then(result => { if (result && result.success) { @@ -122,12 +106,12 @@ export const getAllTags = (confTmp, setConf, setLoading) => { newConf.tags = result.data } setConf(newConf) - setLoading({ tags: false, emailOctopusFields: true }) + setLoading({ ...loading, tags: false, emailOctopusFields: true }) toast.success(__('Tags fetched successfully', 'bit-integrations')) return } - setLoading({ tags: false, emailOctopusFields: true }) + setLoading({ ...loading, tags: false, emailOctopusFields: true }) toast.error(__('Tags fetching failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusIntegLayout.jsx b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusIntegLayout.jsx index 581500437..1fa057209 100644 --- a/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/EmailOctopus/EmailOctopusIntegLayout.jsx @@ -1,12 +1,10 @@ /* eslint-disable no-console */ -/* eslint-disable no-unused-vars */ -import { useState } from 'react' import MultiSelect from 'react-multiple-select-dropdown-lite' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import 'react-multiple-select-dropdown-lite/dist/index.css' import EmailOctopusActions from './EmailOctopusActions' -import { emailOctopusAuthentication, getAllFields } from './EmailOctopusCommonFunc' +import { fetchAllLists, getAllFields } from './EmailOctopusCommonFunc' import EmailOctopusFieldMap from './EmailOctopusFieldMap' import { addFieldMap } from './IntegrationHelpers' @@ -19,14 +17,11 @@ export default function EmailOctopusIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setIsAuthorized] = useState(false) - const setChanges = val => { const newConf = { ...emailOctopusConf } newConf.selectedList = val setEmailOctopusConf({ ...newConf }) - getAllFields(newConf, setEmailOctopusConf, setLoading) + getAllFields(newConf, setEmailOctopusConf, loading, setLoading) } return ( @@ -43,15 +38,7 @@ export default function EmailOctopusIntegLayout({ />

` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Token:', 'bit-integrations')} -
- -
{error.auth_token}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('GetResponse API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/GetResponse/GetResponseCommonFunc.js b/frontend/src/components/AllIntegrations/GetResponse/GetResponseCommonFunc.js index 93c06dd8d..7bca9f249 100644 --- a/frontend/src/components/AllIntegrations/GetResponse/GetResponseCommonFunc.js +++ b/frontend/src/components/AllIntegrations/GetResponse/GetResponseCommonFunc.js @@ -45,57 +45,65 @@ export const checkMappedFields = getResponseConf => { return true } -export const getresponseAuthentication = ( +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { auth_token: confTmp.auth_token } + +export const fetchCampaigns = ( confTmp, setConf, setError, setisAuthorized, loading, setLoading, - type + type = 'authentication' ) => { - if (!confTmp.auth_token) { - setError({ + if (!confTmp.connection_id && !confTmp.auth_token) { + setError?.({ auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' }) return } - setError({}) + setError?.({}) + if (type === 'authentication') { setLoading({ ...loading, auth: true }) } + if (type === 'refreshCampaigns') { setLoading({ ...loading, customFields: true }) } - const requestParams = { auth_token: confTmp.auth_token } - bitsFetch(requestParams, 'getresponse_authentication').then(result => { + const requestParams = buildAuthRequestParams(confTmp) + + bitsFetch(requestParams, 'getresponse_fetch_all_list').then(result => { if (result && result.success) { const newConf = { ...confTmp } if (result.data) { newConf.campaigns = result.data } setConf(newConf) - setisAuthorized(true) + setisAuthorized?.(true) + if (type === 'authentication') { setLoading({ ...loading, auth: false }) toast.success(__('Authorized Successfully', 'bit-integrations')) } else if (type === 'refreshCampaigns') { setLoading({ ...loading, customFields: false }) - toast.success(__('Campaigns fectched successfully', 'bit-integrations')) + toast.success(__('Campaigns fetched successfully', 'bit-integrations')) } return } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed', 'bit-integrations')) + + setLoading({ ...loading, auth: false, customFields: false }) + toast.error(__('Campaigns fetching failed', 'bit-integrations')) }) } export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tags: true }) - const requestParams = { auth_token: confTmp.auth_token } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'getresponse_fetch_all_tags').then(result => { if (result && result.success) { @@ -119,7 +127,7 @@ export const fetchCustomFields = (confTmp, setConf, setLoading, type) => { setLoading({ ...setLoading, field: true }) } - const requestParams = { auth_token: confTmp.auth_token } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'getresponse_fetch_custom_fields').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/GetResponse/GetResponseIntegLayout.jsx b/frontend/src/components/AllIntegrations/GetResponse/GetResponseIntegLayout.jsx index b70e0134d..a7adcf692 100644 --- a/frontend/src/components/AllIntegrations/GetResponse/GetResponseIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/GetResponse/GetResponseIntegLayout.jsx @@ -1,8 +1,7 @@ -import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import GetResponseActions from './GetResponseActions' -import { fetchCustomFields, getresponseAuthentication } from './GetResponseCommonFunc' +import { fetchCampaigns, fetchCustomFields } from './GetResponseCommonFunc' import GetResponseFieldMap from './GetResponseFieldMap' import { addFieldMap } from './IntegrationHelpers' @@ -15,9 +14,6 @@ export default function GetResponseIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setisAuthorized] = useState(false) - return ( <>
@@ -38,11 +34,11 @@ export default function GetResponseIntegLayout({ -
- - - )} - - + ) } diff --git a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js index e0ec0fbea..efad4ed40 100644 --- a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js @@ -52,63 +52,24 @@ export const checkMetaMappedFields = groundhoggConf => { return true } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.public_key) { - setError({ - public_key: !confTmp.public_key ? __("Public Key can't be empty", 'bit-integrations') : '' - }) - return - } - if (!confTmp.token) { - setError({ token: !confTmp.token ? __("token can't be empty", 'bit-integrations') : '' }) - return - } - if (!confTmp.domainName) { - setError({ - domainName: !confTmp.domainName ? __("Domain Name can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setIsLoading(true) +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + public_key: confTmp.public_key, + token: confTmp.token, + domainName: confTmp.domainName + } - const requestParams = { - public_key: confTmp.public_key, - token: confTmp.token, - domainName: confTmp.domainName +export const fetchAllTags = (groundhoggConf, setGroundhoggConf, setIsLoading, setSnackbar) => { + if (!groundhoggConf.connection_id && (!groundhoggConf.public_key || !groundhoggConf.token || !groundhoggConf.domainName)) { + toast.error(__('Authorization data is missing', 'bit-integrations')) + return } - bitsFetch(requestParams, 'groundhogg_authorization_and_fetch_contacts').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setIsLoading(false) - toast.success(__('Authorization Successful', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__('Authorization Failed', 'bit-integrations')) - }) -} - -export const fetchAllTags = (formID, groundhoggConf, setGroundhoggConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - public_key: groundhoggConf.public_key, - token: groundhoggConf.token, - domainName: groundhoggConf.domainName - } - - bitsFetch(requestParams, 'groundhogg_fetch_all_tags') + bitsFetch(buildAuthRequestParams(groundhoggConf), 'groundhogg_fetch_all_tags') .then(result => { if (result && result.success) { const newConf = { ...groundhoggConf } diff --git a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggIntegLayout.jsx b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggIntegLayout.jsx index a3bf2010b..5074faf3f 100644 --- a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggIntegLayout.jsx @@ -207,7 +207,7 @@ export default function GroundhoggIntegLayout({ customValue /> -
- - - )} - - - + ) } diff --git a/frontend/src/components/AllIntegrations/Line/LineCommonFunc.js b/frontend/src/components/AllIntegrations/Line/LineCommonFunc.js index f5b1dc63a..b86bdb70a 100644 --- a/frontend/src/components/AllIntegrations/Line/LineCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Line/LineCommonFunc.js @@ -1,63 +1,10 @@ import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' export const handleInput = (e, lineConf, setLineConf) => { const { name, value } = e.target setLineConf(prev => ({ ...prev, [name]: value })) } -export const handleAuthorize = async ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.accessToken) { - setError({ accessToken: __("Access Token can't be empty", 'bit-integrations') }) - return - } - - setError({}) - setIsLoading(true) - - try { - const result = await bitsFetch({ accessToken: confTmp.accessToken }, 'line_authorization') - - if (result?.success) { - setConf({ ...confTmp, tokenDetails: result.data }) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else { - const msg = result?.data?.data - ? `${__('Authorization failed Cause:', 'bit-integrations')} ${result.data.data}. ${__( - 'Please try again', - 'bit-integrations' - )}` - : typeof result?.data === 'string' - ? `${__('Authorization failed Cause:', 'bit-integrations')} ${result.data}. ${__( - 'Please try again', - 'bit-integrations' - )}` - : __('Authorization failed. Please try again', 'bit-integrations') - - setSnackbar({ show: true, msg }) - } - - setIsLoading(false) - } catch (error) { - setSnackbar({ - show: true, - msg: `${__('An error occurred during authorization:', 'bit-integrations')} ${ - error?.message || error - }` - }) - - setIsLoading(false) - } -} - const updateFieldMap = (prevConf, type, index, updater) => { const newConf = { ...prevConf } diff --git a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterAuthorization.jsx b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterAuthorization.jsx index dbc999f71..347959656 100644 --- a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterAuthorization.jsx @@ -1,11 +1,10 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { mailBlusterAuthentication } from './MailBlusterCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchCustomFields } from './MailBlusterCommonFunc' export default function MailBlusterAuthorization({ mailBlusterConf, @@ -16,28 +15,44 @@ export default function MailBlusterAuthorization({ setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '', auth_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadCustomFields = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...mailBlusterConf, connection_id: connectionId } + : mailBlusterConf - !mailBlusterConf?.default - setStep(2) - } + await fetchCustomFields( + nextConf, + setMailBlusterConf, + undefined, + undefined, + loading, + setLoading, + 'refreshCustomFields' + ) + }, + [mailBlusterConf, setMailBlusterConf, loading, setLoading] + ) + + const handleConnectionSelected = useCallback( + async connectionId => { + await loadCustomFields(connectionId) + }, + [loadCustomFields] + ) - const handleInput = e => { - const newConf = { ...mailBlusterConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailBlusterConf(newConf) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !mailBlusterConf?.customFields?.length) { + loadCustomFields() + } + + setStep(value) + }, + [mailBlusterConf, loadCustomFields, setStep] + ) - const note = ` -

${__('Step of generate API token:', 'bit-integrations')}

+ const note = `

${__('Step of generate API token:', 'bit-integrations')}

  • ${__( 'Goto', @@ -51,88 +66,26 @@ const nextPage = () => { 'bit-integrations' )}
  • ${__('Finally, click Authorize button.', 'bit-integrations')}
  • -
- ` + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Token:', 'bit-integrations')} -
- -
{error.auth_token}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('MailBluster API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterCommonFunc.js b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterCommonFunc.js index 944486969..3c9d02315 100644 --- a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterCommonFunc.js @@ -37,56 +37,53 @@ export const checkMappedFields = mailBlusterConf => { return true } -export const mailBlusterAuthentication = ( +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { auth_token: confTmp.auth_token } + +export const fetchCustomFields = ( confTmp, setConf, setError, setIsAuthorized, loading, setLoading, - type + type = 'authentication' ) => { - if (!confTmp.auth_token) { - setError({ + if (!confTmp.connection_id && !confTmp.auth_token) { + setError?.({ auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' }) return } - setError({}) + setError?.({}) if (type === 'authentication') { setLoading({ ...loading, auth: true }) } + if (type === 'refreshCustomFields') { setLoading({ ...loading, customFields: true }) } - const requestParams = { auth_token: confTmp.auth_token } - bitsFetch(requestParams, 'mailBluster_authentication').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'mailBluster_fetch_custom_fields').then(result => { if (result && result.success) { const newConf = { ...confTmp } - if (result.data) { - newConf.campaigns = result.data - } + newConf.customFields = result.data || [] setConf(newConf) - setIsAuthorized(true) + setIsAuthorized?.(true) + if (type === 'authentication') { - if (result.data) { - newConf.customFields = result.data - } setLoading({ ...loading, auth: false }) toast.success(__('Authorized Successfully', 'bit-integrations')) } else if (type === 'refreshCustomFields') { - if (result.data) { - newConf.customFields = result.data - } setLoading({ ...loading, customFields: false }) - toast.success(__('Custom fields fectched successfully', 'bit-integrations')) + toast.success(__('Custom fields fetched successfully', 'bit-integrations')) } return } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed', 'bit-integrations')) + + setLoading({ ...loading, auth: false, customFields: false }) + toast.error(__('Authorization failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterIntegLayout.jsx b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterIntegLayout.jsx index a486881d8..d2573a539 100644 --- a/frontend/src/components/AllIntegrations/MailBluster/MailBlusterIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/MailBluster/MailBlusterIntegLayout.jsx @@ -1,8 +1,7 @@ -import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import MailBlusterActions from './MailBlusterActions' -import { mailBlusterAuthentication } from './MailBlusterCommonFunc' +import { fetchCustomFields } from './MailBlusterCommonFunc' import MailBlusterFieldMap from './MailBlusterFieldMap' import { addFieldMap } from './IntegrationHelpers' @@ -15,9 +14,6 @@ export default function MailBlusterIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setIsAuthorized] = useState(false) - return ( <>
@@ -51,11 +47,11 @@ export default function MailBlusterIntegLayout({ {__('Field Map', 'bit-integrations')} -
- - - )} - - + ) } diff --git a/frontend/src/components/AllIntegrations/Salesmate/SalesmateCommonFunc.js b/frontend/src/components/AllIntegrations/Salesmate/SalesmateCommonFunc.js index 160ba7c5c..ba5e43268 100644 --- a/frontend/src/components/AllIntegrations/Salesmate/SalesmateCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Salesmate/SalesmateCommonFunc.js @@ -15,13 +15,14 @@ export const handleInput = (e, salesmateConf, setSalesmateConf) => { setSalesmateConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { session_token: confTmp.session_token, link_name: confTmp.link_name } + // refreshMappedFields export const refreshSalesmateFields = (salesmateConf, setSalesmateConf, setIsLoading, setSnackbar) => { - const requestParams = { - session_token: salesmateConf.session_token, - link_name: salesmateConf.link_name, - action_id: salesmateConf.actionId - } + const requestParams = { ...buildAuthRequestParams(salesmateConf), action_id: salesmateConf.actionId } bitsFetch(requestParams, 'Salesmate_fields') .then(result => { @@ -81,55 +82,10 @@ export const checkMappedFields = salesmateConf => { return true } -export const salesmateAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.session_token || !confTmp.link_name) { - setError({ - session_token: !confTmp.session_token - ? __("Session Token can't be empty", 'bit-integrations') - : '', - link_name: !confTmp.link_name ? __("Link Name can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - - bitsFetch(requestParams, 'salesmate_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - __('Authorized failed, Please enter valid Session Token or Link Name', 'bit-integrations') - ) - }) -} - export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tags: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - - bitsFetch(requestParams, 'salesmate_fetch_all_tags').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_tags').then(result => { if (result && result.success) { const newConf = { ...confTmp } if (result.data) { @@ -217,12 +173,8 @@ export const getAllCRMPriority = setConf => { export const getAllCRMCurrency = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMCurrency: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - bitsFetch(requestParams, 'salesmate_fetch_all_currencies').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_currencies').then(result => { if (result && result.success) { const newConf = { ...confTmp } if (result.data) { @@ -241,12 +193,8 @@ export const getAllCRMCurrency = (confTmp, setConf, setLoading) => { export const getAllCRMCompany = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMCompany: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - bitsFetch(requestParams, 'salesmate_fetch_all_CRMCompanies').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_CRMCompanies').then(result => { if (result && result.success) { if (!result.data) { setLoading({ ...setLoading, CRMCompany: false }) @@ -273,12 +221,8 @@ export const getAllCRMCompany = (confTmp, setConf, setLoading) => { export const getAllCRMPipelines = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, CRMPipelines: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - bitsFetch(requestParams, 'salesmate_fetch_all_CRMPipelines').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_CRMPipelines').then(result => { setLoading({ ...loading, CRMPipelines: false }) if (result && result.success) { setConf(prevConf => { @@ -297,12 +241,8 @@ export const getAllCRMPipelines = (confTmp, setConf, loading, setLoading) => { } export const getAllCRMPrimaryContact = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, CRMContacts: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - bitsFetch(requestParams, 'salesmate_fetch_all_CRMContacts').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_CRMContacts').then(result => { setLoading({ ...loading, CRMContacts: false }) if (result && result.success) { if (!result.data) { @@ -327,12 +267,8 @@ export const getAllCRMPrimaryContact = (confTmp, setConf, loading, setLoading) = export const getAllCRMOwner = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, CRMOwners: true }) - const requestParams = { - session_token: confTmp.session_token, - link_name: confTmp.link_name - } - bitsFetch(requestParams, 'salesmate_fetch_all_CRMOwners').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'salesmate_fetch_all_CRMOwners').then(result => { setLoading({ ...loading, CRMOwners: false }) if (result && result.success) { setConf(prevConf => { diff --git a/frontend/src/components/AllIntegrations/Slack/Slack.jsx b/frontend/src/components/AllIntegrations/Slack/Slack.jsx index 1fc7389a7..7eaeff586 100644 --- a/frontend/src/components/AllIntegrations/Slack/Slack.jsx +++ b/frontend/src/components/AllIntegrations/Slack/Slack.jsx @@ -1,20 +1,18 @@ /* eslint-disable no-unused-expressions */ import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { useNavigate, useParams } from 'react-router' +import { useNavigate } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import SlackAuthorization from './SlackAuthorization' -import { handleInput } from './SlackCommonFunc' import SlackIntegLayout from './SlackIntegLayout' import BackIcn from '../../../Icons/BackIcn' function Slack({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() - const { formID } = useParams() const [isLoading, setIsLoading] = useState(false) const [step, setstep] = useState(1) const [snack, setSnackbar] = useState({ show: false }) @@ -25,6 +23,7 @@ function Slack({ formFields, setFlow, flow, allIntegURL }) { parse_mode: 'HTML', field_map: [{ formField: '', slackFormField: '' }], channel_id: '', + channels: [], body: '', actions: {} }) @@ -49,14 +48,11 @@ function Slack({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} {/* STEP 2 */} @@ -72,7 +68,6 @@ function Slack({ formFields, setFlow, flow, allIntegURL }) { }}> handleInput(e, slackConf, setSlackConf, setIsLoading, setSnackbar)} slackConf={slackConf} setSlackConf={setSlackConf} isLoading={isLoading} diff --git a/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx b/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx index 9ee657ac5..6668fb244 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx @@ -1,43 +1,49 @@ -import { useState } from 'react' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize } from './SlackCommonFunc' -import Note from '../../Utilities/Note' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchChannels } from './SlackCommonFunc' export default function SlackAuthorization({ - formID, slackConf, setSlackConf, step, setstep, - isLoading, setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ accessToken: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadChannels = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...slackConf, connection_id: connectionId } + : slackConf - setstep(2) - } - const handleInput = e => { - const newConf = { ...slackConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSlackConf(newConf) - } + await fetchChannels(nextConf, setSlackConf, setIsLoading) + }, + [slackConf, setSlackConf, setIsLoading] + ) + + const handleConnectionSelected = useCallback( + async connectionId => { + await loadChannels(connectionId) + }, + [loadChannels] + ) + + const handleSetStep = useCallback( + value => { + if (value === 2 && !slackConf?.default) { + loadChannels() + } + + setstep(value) + }, + [slackConf, loadChannels, setstep] + ) - const slackInstructions = ` -

${__('Get Access Token few step', 'bit-integrations')}

+ const slackInstructions = `

${__('Get Access Token few step', 'bit-integrations')}

  • ${__('First create app.', 'bit-integrations')}
  • ${__( @@ -51,84 +57,22 @@ const nextPage = () => {
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - - - {__('To get access Token , Please Visit', 'bit-integrations')}{' '} - - {__('Slack Console', 'bit-integrations')} - - - -
- {__('Access Token:', 'bit-integrations')} -
- -
{error.accessToken}
- - {!isInfo && ( - <> - -
- - - )} - - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js b/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js index 9ab8ab241..8df5d0ed4 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js @@ -1,6 +1,6 @@ -/* eslint-disable no-else-return */ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +import toast from 'react-hot-toast' export const handleInput = (e, slackConf, setSlackConf) => { const newConf = { ...slackConf } @@ -13,51 +13,38 @@ export const handleInput = (e, slackConf, setSlackConf) => { setSlackConf({ ...newConf }) } -export const handleAuthorize = ( +export const fetchChannels = async ( confTmp, setConf, - setError, - setisAuthorized, setIsLoading, - setSnackbar + type = 'fetch' ) => { - if (!confTmp.accessToken) { - setError({ - accessToken: !confTmp.accessToken ? __("Access Token can't be empty", 'bit-integrations') : '' - }) + if (!confTmp.connection_id && !confTmp.accessToken) { + toast.error(__("Access Token can't be empty", 'bit-integrations')) return } - setError({}) setIsLoading(true) - const tokenRequestParams = { accessToken: confTmp.accessToken } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { accessToken: confTmp.accessToken } - bitsFetch(tokenRequestParams, 'slack_authorization_and_fetch_channels') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) + const result = await bitsFetch(requestParams, 'slack_fetch_channels') + + if (result && result.success) { + const newConf = { ...confTmp } + newConf.channels = result.data?.channels || [] + setConf(newConf) + setIsLoading(false) + toast.success( + type === 'refresh' + ? __('Channels fetched successfully', 'bit-integrations') + : __('Channels loaded successfully', 'bit-integrations') + ) + return + } + + setIsLoading(false) + toast.error(__('Channels fetching failed', 'bit-integrations')) } diff --git a/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx b/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx index 0da29c2bd..30e482e40 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx @@ -1,12 +1,18 @@ import { useRef } from 'react' import MultiSelect from 'react-multiple-select-dropdown-lite' -import { useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import { setFieldInputOnMsgBody } from '../IntegrationHelpers/IntegrationHelpers' import SlackActions from './SlackActions' +import { fetchChannels } from './SlackCommonFunc' -export default function SlackIntegLayout({ formFields, slackConf, setSlackConf, isLoading }) { +export default function SlackIntegLayout({ + formFields, + slackConf, + setSlackConf, + isLoading, + setIsLoading +}) { const textAreaRef = useRef(null) const handleInput = e => { @@ -15,20 +21,6 @@ export default function SlackIntegLayout({ formFields, slackConf, setSlackConf, setSlackConf(newConf) } - const setMessageBody = val => { - const newConf = { ...slackConf } - newConf.body = val - setSlackConf(newConf) - } - const changeActionRun = e => { - const newConf = { ...slackConf } - if (newConf?.body) { - newConf.body = '' - } - newConf.parse_mode = e.target.value - setSlackConf(newConf) - } - return ( <>
@@ -40,13 +32,20 @@ export default function SlackIntegLayout({ formFields, slackConf, setSlackConf, value={slackConf.channel_id} className="btcd-paper-inp w-5"> - {slackConf?.tokenDetails?.channels && - slackConf?.tokenDetails?.channels.map(({ id, name }) => ( + {(slackConf?.channels || slackConf?.tokenDetails?.channels || []).map(({ id, name }) => ( ))} + {isLoading && ( {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteAuthorization.jsx b/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteAuthorization.jsx index 19b49633c..f149198fb 100644 --- a/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteAuthorization.jsx @@ -1,151 +1,91 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { smartSuiteAuthentication, getAllSolutions } from './SmartSuiteCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import { create } from 'mutative' +import Authorization from '../../Connections/Authorization' +import { getAllSolutions } from './SmartSuiteCommonFunc' export default function SmartSuiteAuthorization({ smartSuiteConf, setSmartSuiteConf, step, setStep, - loading, setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) -const [error, setError] = useState({ workspaceId: '', apiToken: '' }) + const loadSolutions = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...smartSuiteConf, connection_id: connectionId } + : smartSuiteConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + await getAllSolutions(nextConf, setSmartSuiteConf, setLoading) + }, + [smartSuiteConf, setSmartSuiteConf, setLoading] + ) - !smartSuiteConf?.default - setStep(2) - } + const handleConnectionSelected = useCallback( + async connectionId => { + await loadSolutions(connectionId) + }, + [loadSolutions] + ) - const handleInput = e => { - const { name, value } = e.target - setError(error => - create(error, draftError => { - draftError[name] = '' - }) - ) - setSmartSuiteConf(smartSuiteConf => - create(smartSuiteConf, draftConf => { - draftConf[name] = value - }) - ) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !smartSuiteConf?.solutions?.length) { + loadSolutions() + } - const ActiveInstructions = ` -

${__('To Get Workspace ID & API Token', 'bit-integrations')}

+ setStep(value) + }, + [smartSuiteConf, loadSolutions, setStep] + ) + + const ActiveInstructions = `

${__('To Get Workspace ID & API Token', 'bit-integrations')}

  • ${__('First go to your SmartSuite dashboard.', 'bit-integrations')}
  • ${__('Click go to Profile Icon from Right Top corner.', 'bit-integrations')}
  • ${__('Then Click "API Key" from the "My Profile Menu".', 'bit-integrations')}
  • ${__('Then Click and Copy the "Hidden Api Token".', 'bit-integrations')}
  • ${__( - 'Your Workspace Id is the 8 characters that follow https://app.smartsuite.com/ in the SmartSuite URL when you’re logged in.', + 'Your Workspace Id is the 8 characters that follow https://app.smartsuite.com/ in the SmartSuite URL when you are logged in.', 'bit-integrations' )}
  • -
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Workspace ID:', 'bit-integrations')} -
- -
{error.workspaceId}
- -
- {__('API Token:', 'bit-integrations')} -
- -
{error.apiToken}
- - - {__('To Get API Token & Workspace ID, Please Visit', 'bit-integrations')} -   - - {__('SmartSuite API Token & Workspace ID', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteCommonFunc.js b/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteCommonFunc.js index 6ce7bea89..52cee63b6 100644 --- a/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SmartSuite/SmartSuiteCommonFunc.js @@ -45,53 +45,17 @@ export const checkMappedFields = smartSuiteConf => { return true } -export const smartSuiteAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.workspaceId || !confTmp.apiToken) { - setError({ - workspaceId: !confTmp.workspaceId ? __("Workspace ID can't be empty", 'bit-integrations') : '', - apiToken: !confTmp.apiToken ? __("API Token can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - workspaceId: confTmp.workspaceId, - apiToken: confTmp.apiToken - } - - bitsFetch(requestParams, 'smartSuite_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, ' + result.data, 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { workspaceId: confTmp.workspaceId, apiToken: confTmp.apiToken } export const getAllSolutions = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, solution: true }) if (confTmp?.selectedSolution) delete confTmp?.selectedSolution - const requestParams = { - workspaceId: confTmp.workspaceId, - apiToken: confTmp.apiToken - } - - bitsFetch(requestParams, 'smartSuite_fetch_all_solutions').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'smartSuite_fetch_all_solutions').then(result => { if (result && result.success) { if (result.data) { setConf(prevConf => { @@ -116,13 +80,10 @@ export const getAllTables = (confTmp, setConf, solution_id, setLoading) => { if (confTmp?.selectedTable) delete confTmp?.selectedTable setLoading({ ...setLoading, table: true }) - const requestParams = { - workspaceId: confTmp.workspaceId, - apiToken: confTmp.apiToken, - solution_id: solution_id - } - - bitsFetch(requestParams, 'smartSuite_fetch_all_tables').then(result => { + bitsFetch( + { ...buildAuthRequestParams(confTmp), solution_id: solution_id }, + 'smartSuite_fetch_all_tables' + ).then(result => { if (result && result.success) { if (result.data) { setConf(prevConf => { @@ -146,12 +107,7 @@ export const getAllTables = (confTmp, setConf, solution_id, setLoading) => { export const getAllUser = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, assignedUser: true }) - const requestParams = { - workspaceId: confTmp.workspaceId, - apiToken: confTmp.apiToken - } - - bitsFetch(requestParams, 'smartSuite_fetch_all_user').then(result => { + bitsFetch(buildAuthRequestParams(confTmp), 'smartSuite_fetch_all_user').then(result => { if (result && result.success) { if (result.data) { setConf(prevConf => { diff --git a/frontend/src/components/AllIntegrations/Vbout/Vbout.jsx b/frontend/src/components/AllIntegrations/Vbout/Vbout.jsx index e5ae345ee..d1f359c50 100644 --- a/frontend/src/components/AllIntegrations/Vbout/Vbout.jsx +++ b/frontend/src/components/AllIntegrations/Vbout/Vbout.jsx @@ -73,7 +73,6 @@ function Vbout({ formFields, setFlow, flow, allIntegURL }) { setstep={setstep} loading={loading} setLoading={setLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/Vbout/VboutAuthorization.jsx b/frontend/src/components/AllIntegrations/Vbout/VboutAuthorization.jsx index 6c0e8e16b..da09cc2cf 100644 --- a/frontend/src/components/AllIntegrations/Vbout/VboutAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Vbout/VboutAuthorization.jsx @@ -1,12 +1,10 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { handleAuthorize } from './VboutCommonFunc' -import { getAllLists } from './VboutCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { getAllLists } from './VboutCommonFunc' export default function VboutAuthorization({ vboutConf, @@ -15,118 +13,66 @@ export default function VboutAuthorization({ setstep, loading, setLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', auth_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !vboutConf?.default - setstep(2) - if (!vboutConf.list_id) { - getAllLists(vboutConf, setVboutConf, loading, setLoading) - } - } - const handleInput = e => { - const newConf = { ...vboutConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setVboutConf(newConf) - } - const note = ` -

${__('Step of get API Key:', 'bit-integrations')}

-
    -
  • ${__( - 'Goto Settings and click on', - 'bit-integrations' - )} ${__('API Integrations', 'bit-integrations')}
  • -
  • ${__( - 'Copy the Key and paste into API Key field of your authorization form.', - 'bit-integrations' - )}
  • -
  • ${__('Finally, click Authorize button.', 'bit-integrations')}
  • -
- ` - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId ? { ...vboutConf, connection_id: connectionId } : vboutConf + getAllLists(nextConf, setVboutConf, loading, setLoading) + }, + [vboutConf, setVboutConf, loading, setLoading] + ) - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Vbout API Key', 'bit-integrations')} - - + const handleConnectionSelected = useCallback( + async connectionId => { + loadLists(connectionId) + }, + [loadLists] + ) -
- {__('API Key:', 'bit-integrations')} -
- -
{error.auth_token}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && !vboutConf?.default && !vboutConf?.list_id) { + loadLists() + } + setstep(value) + }, + [vboutConf, loadLists, setstep] + ) - {!isInfo && ( -
- -
- -
- )} - -
+ return ( + ) } + +const note = ` +

${__('Step of get API Key:', 'bit-integrations')}

+
    +
  • ${__( + 'Goto Settings and click on', + 'bit-integrations' + )} ${__('API Integrations', 'bit-integrations')}
  • +
  • ${__( + 'Copy the Key and paste into API Key field of your authorization form.', + 'bit-integrations' + )}
  • +
  • ${__('Finally, click Authorize button.', 'bit-integrations')}
  • +
+` diff --git a/frontend/src/components/AllIntegrations/Vbout/VboutCommonFunc.js b/frontend/src/components/AllIntegrations/Vbout/VboutCommonFunc.js index 58fb19faa..5ded395b8 100644 --- a/frontend/src/components/AllIntegrations/Vbout/VboutCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Vbout/VboutCommonFunc.js @@ -38,35 +38,13 @@ export const checkMappedFields = vboutConf => { } return true } -export const handleAuthorize = (confTmp, setConf, setError, setisAuthorized, loading, setLoading) => { - if (!confTmp.auth_token) { - setError({ - auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { auth_token: confTmp.auth_token } - bitsFetch(requestParams, 'vbout_handle_authorize').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} export const VboutRefreshFields = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, field: true }) - const requestParams = { auth_token: confTmp.auth_token, list_id: confTmp.list_id } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id, list_id: confTmp.list_id } + : { auth_token: confTmp.auth_token, list_id: confTmp.list_id } bitsFetch(requestParams, 'vbout_refresh_fields').then(result => { if (result && result.success) { @@ -88,7 +66,9 @@ export const VboutRefreshFields = (confTmp, setConf, loading, setLoading) => { export const getAllLists = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, list: true }) - const requestParams = { auth_token: confTmp.auth_token } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { auth_token: confTmp.auth_token } bitsFetch(requestParams, 'vbout_fetch_all_lists').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/WhatsApp/WhatsApp.jsx b/frontend/src/components/AllIntegrations/WhatsApp/WhatsApp.jsx index d3b27baf7..2d23a3e84 100644 --- a/frontend/src/components/AllIntegrations/WhatsApp/WhatsApp.jsx +++ b/frontend/src/components/AllIntegrations/WhatsApp/WhatsApp.jsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { useNavigate, useParams } from 'react-router' +import { useNavigate } from 'react-router' import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' @@ -10,14 +10,13 @@ import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' // import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' // import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import WhatsAppAuthorization from './WhatsAppAuthorization' -import { handleInput, generateMappedField, checkDisabledButton } from './WhatsAppCommonFunc' +import { generateMappedField, checkDisabledButton } from './WhatsAppCommonFunc' import WhatsAppIntegLayout from './WhatsAppIntegLayout' import { useRecoilValue } from 'recoil' import { $appConfigState } from '../../../GlobalStates' function WhatsApp({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() - const { formID } = useParams() const [isLoading, setIsLoading] = useState(false) const [step, setstep] = useState(1) const [snack, setSnackbar] = useState({ show: false }) @@ -90,14 +89,10 @@ function WhatsApp({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 2 */} @@ -106,7 +101,6 @@ function WhatsApp({ formFields, setFlow, flow, allIntegURL }) { style={{ ...(step === 2 && { width: 900, height: 'auto', overflow: 'visible' }) }}> handleInput(e, whatsAppConf, setWhatsAppConf, setIsLoading, setSnackbar)} whatsAppConf={whatsAppConf} setWhatsAppConf={setWhatsAppConf} isLoading={isLoading} diff --git a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppAuthorization.jsx b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppAuthorization.jsx index 3e3146d27..6d77d39de 100644 --- a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppAuthorization.jsx @@ -1,132 +1,46 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize } from './WhatsAppCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' - -export default function WhatsAppAuthorization({ - formID, - whatsAppConf, - setWhatsAppConf, - step, - setstep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setstep(2) - } - - const handleInput = e => { - const newConf = { ...whatsAppConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setWhatsAppConf(newConf) - } +import Authorization from '../../Connections/Authorization' +export default function WhatsAppAuthorization({ whatsAppConf, setWhatsAppConf, step, setstep, isInfo }) { return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Phone number ID:', 'bit-integrations')} -
- - -
- {__('WhatsApp Business Account ID:', 'bit-integrations')} -
- - -
- {__('Access Token:', 'bit-integrations')} -
- -
{error.clientId}
- -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ ) } + +const note = `

${__('WhatsApp Cloud API setup', 'bit-integrations')}

+
    +
  • ${__('Provide your WhatsApp Business Account ID.', 'bit-integrations')}
  • +
  • ${__('Provide your Phone Number ID.', 'bit-integrations')}
  • +
  • ${__('Paste a valid long-lived access token.', 'bit-integrations')}
  • +
` diff --git a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppCommonFunc.js b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppCommonFunc.js index 94d4dbe91..319da770e 100644 --- a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppCommonFunc.js +++ b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppCommonFunc.js @@ -13,64 +13,22 @@ export const handleInput = (e, whatsAppConf, setWhatsAppConf) => { setWhatsAppConf({ ...newConf }) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setIsAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.numberID || !confTmp.businessAccountID || !confTmp.token) { - setError({ - numberID: !confTmp.numberID - ? __("Phone number ID can't be empty or invalid", 'bit-integrations') - : '', - businessAccountID: !confTmp.businessAccountID - ? __("WhatsApp Business Account ID can't be empty or invalid", 'bit-integrations') - : '', - token: !confTmp.token ? __("Access Token can't be empty or invalid", 'bit-integrations') : '' - }) - return - } - - const requestParams = { - numberID: confTmp.numberID, - businessAccountID: confTmp.businessAccountID, - token: confTmp.token - } - - setIsLoading(true) - bitsFetch(requestParams, 'whats_app_authorization').then(result => { - setIsLoading(false) - if (result && result.success) { - setIsAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - return - } - setSnackbar({ show: true, msg: result?.data || __('Authorized failed', 'bit-integrations') }) - }) -} - export const getallTemplates = (confTmp, setConf, setIsLoading, setSnackbar) => { - if (!confTmp.numberID || !confTmp.businessAccountID || !confTmp.token) { - setError({ - numberID: !confTmp.numberID - ? __("Phone number ID can't be empty or invalid", 'bit-integrations') - : '', - businessAccountID: !confTmp.businessAccountID - ? __("WhatsApp Business Account ID can't be empty or invalid", 'bit-integrations') - : '', - token: !confTmp.token ? __("Access Token can't be empty or invalid", 'bit-integrations') : '' + if (!confTmp.connection_id && (!confTmp.numberID || !confTmp.businessAccountID || !confTmp.token)) { + setSnackbar({ + show: true, + msg: __("Phone number ID, Business Account ID and Access Token are required.", 'bit-integrations') }) return } - const requestParams = { - numberID: confTmp.numberID, - businessAccountID: confTmp.businessAccountID, - token: confTmp.token - } + const requestParams = confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + numberID: confTmp.numberID, + businessAccountID: confTmp.businessAccountID, + token: confTmp.token + } setIsLoading(true) bitsFetch(requestParams, 'whats_app_all_template').then(result => { diff --git a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppIntegLayout.jsx b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppIntegLayout.jsx index bfd9941be..3469f1a22 100644 --- a/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/WhatsApp/WhatsAppIntegLayout.jsx @@ -13,7 +13,6 @@ import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers' export default function WhatsAppIntegLayout({ formFields, - handleInput, whatsAppConf, setWhatsAppConf, isLoading, diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 046a03d69..6b92117e7 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -37,8 +37,22 @@ const resolveTemplate = (template, data) => { }) } +const resolveHeaderTemplates = (headers, data) => { + if (!headers || typeof headers !== 'object') { + return {} + } + + return Object.entries(headers).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value + return acc + }, {}) +} + const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { - const additionalHeaders = normalizeAdditionalHeaders(authDetails?.headers) + const additionalHeaders = resolveHeaderTemplates( + normalizeAdditionalHeaders(authDetails?.headers), + authData + ) const sslVerify = authDetails?.ssl_verify !== false // Extra fields captured first; standard auth keys below always win on collision. @@ -61,7 +75,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } ...extraAuthDetails, key: authDetails?.key || 'X-API-Key', value: authData.api_key, - addTo: authData.addTo || 'header', + addTo: authData.addTo || authDetails?.addTo || 'header', ssl_verify: sslVerify } return basePayload From ffb45d32a2f3df64388d0c8c1932964c0c3e22f4 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 16:43:39 +0600 Subject: [PATCH 25/58] refactor: enhance API connection handling, and streamline authentication process --- .../Connections/AddNewConnection.jsx | 344 +----------------- .../components/Connections/ApiConnection.jsx | 339 +++++++++++++++++ .../Connections/ConnectionAccountSelect.jsx | 11 +- .../Connections/Oauth2Connection.jsx | 40 +- 4 files changed, 372 insertions(+), 362 deletions(-) create mode 100644 frontend/src/components/Connections/ApiConnection.jsx diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 6b92117e7..69b4bad77 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -1,348 +1,12 @@ -import { useCallback, useState } from 'react' -import toast from 'react-hot-toast' -import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' -import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' -import { __ } from '../../Utils/i18nwrap' -import LoaderSm from '../Loaders/LoaderSm' -import Oauth2Connection from './Oauth2Connection' - -const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } - -const normalizeAdditionalHeaders = headers => { - if (!headers || typeof headers !== 'object') { - return {} - } - - return Object.entries(headers).reduce((acc, [key, value]) => { - const normalizedKey = String(key || '').trim() - const normalizedValue = value == null ? '' : String(value).trim() - - if (normalizedKey && normalizedValue) { - acc[normalizedKey] = normalizedValue - } - - return acc - }, {}) -} - -// Resolves {fieldName} placeholders in URL templates using authData values. -// Strips trailing slashes from substituted values to avoid double-slash in paths. -// Unknown tokens are replaced with '' (empty required fields caught by validation). -const resolveTemplate = (template, data) => { - if (!template) return '' - return template.replace(/\{(\w+)\}/g, (_, key) => { - const val = data[key] - if (val == null) return '' - return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) - }) -} - -const resolveHeaderTemplates = (headers, data) => { - if (!headers || typeof headers !== 'object') { - return {} - } - - return Object.entries(headers).reduce((acc, [key, value]) => { - acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value - return acc - }, {}) -} - -const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { - const additionalHeaders = resolveHeaderTemplates( - normalizeAdditionalHeaders(authDetails?.headers), - authData - ) - const sslVerify = authDetails?.ssl_verify !== false - - // Extra fields captured first; standard auth keys below always win on collision. - // Reserved auth_details keys: value, token, key, addTo, username, password, ssl_verify - const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { - if (authData[name] != null) acc[name] = authData[name] - return acc - }, {}) - - const basePayload = { - auth_type: authType, - api_endpoint: resolveTemplate(apiEndpoint, authData), - method: method || 'GET', - auth_details: {}, - headers: additionalHeaders - } - - if (authType === AUTH_TYPES.API_KEY) { - basePayload.auth_details = { - ...extraAuthDetails, - key: authDetails?.key || 'X-API-Key', - value: authData.api_key, - addTo: authData.addTo || authDetails?.addTo || 'header', - ssl_verify: sslVerify - } - return basePayload - } - - if (authType === AUTH_TYPES.BASIC_AUTH) { - basePayload.auth_details = { - ...extraAuthDetails, - username: authData.username, - password: authData.password, - ssl_verify: sslVerify - } - return basePayload - } - - if (authType === AUTH_TYPES.BEARER_TOKEN) { - basePayload.auth_details = { - ...extraAuthDetails, - token: authData.token, - ssl_verify: sslVerify - } - } - - return basePayload -} - -const getValidationErrors = (authType, authData, extraFields = []) => { - const nextErrors = {} - - if (!authData.connectionName?.trim()) { - nextErrors.connectionName = __('Connection name is required', 'bit-integrations') - } - - if (authType === AUTH_TYPES.API_KEY && !authData.api_key?.trim()) { - nextErrors.api_key = __('API key is required', 'bit-integrations') - } - - if (authType === AUTH_TYPES.BASIC_AUTH) { - if (!authData.username?.trim()) { - nextErrors.username = __('Username is required', 'bit-integrations') - } - - if (!authData.password?.trim()) { - nextErrors.password = __('Password is required', 'bit-integrations') - } - } - - if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token?.trim()) { - nextErrors.token = __('Bearer token is required', 'bit-integrations') - } - - extraFields.forEach(field => { - if (field.required && !authData[field.name]?.trim()) { - nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` - } - }) - - return nextErrors -} +import { AUTH_TYPES } from "../../Utils/connectionAuth" +import ApiConnection from "./ApiConnection" +import Oauth2Connection from "./Oauth2Connection" export default function AddNewConnection(props) { if (props?.authDetails?.authType === AUTH_TYPES.OAUTH2) { return } - return + return } -function CredentialAuthorizeForm({ - authDetails, - config, - setConfig, - isInfo = false, - customAuthFields, - onConnectionSaved -}) { - const [authData, setAuthData] = useState({}) - const [isAuthorized, setIsAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [errors, setErrors] = useState({}) - const { authType, apiEndpoint, method } = authDetails || {} - - const handleChange = useCallback(event => { - const { name, value } = event.target - - setAuthData(prev => ({ ...prev, [name]: value })) - setErrors(prev => ({ ...prev, [name]: '' })) - }, []) - - const handleAuthorize = useCallback(async () => { - const validationErrors = getValidationErrors(authType, authData, authDetails?.extraFields) - setErrors(validationErrors) - - if (Object.keys(validationErrors).length > 0) { - return - } - - const payload = getAuthPayload({ - authType, - apiEndpoint, - method, - authData, - authDetails - }) - - setIsLoading(true) - - try { - const authorizeRes = await authorizeConnection(payload) - - if (!authorizeRes?.success) { - setIsAuthorized(false) - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' - }. ${__('please try again', 'bit-integrations')}` - ) - return - } - - const saveRes = await saveConnection({ - app_slug: config?.app_slug || config?.type, - auth_type: authType, - connection_name: authData.connectionName, - account_name: authData.connectionName, - auth_details: payload.auth_details, - encrypt_keys: defaultEncryptKeys[authType] || [] - }) - - if (!saveRes?.success) { - toast.error( - `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' - }. ${__('please try again', 'bit-integrations')}` - ) - return - } - - const connection = saveRes?.data?.data || null - setConfig(prev => ({ ...prev, connection_id: connection?.id })) - - if (onConnectionSaved) { - await onConnectionSaved(connection) - } - - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } catch (error) { - setIsAuthorized(false) - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error' - }. ${__('please try again', 'bit-integrations')}` - ) - } finally { - setIsLoading(false) - } - }, [apiEndpoint, authData, authDetails, authType, config?.app_slug, config?.type, method, onConnectionSaved, setConfig]) - - return ( - <> -
- {__('Connection Name:', 'bit-integrations')} -
- -
{errors.connectionName || ''}
- - {authType === AUTH_TYPES.API_KEY && ( - <> -
- {__('API Key:', 'bit-integrations')} -
- -
{errors.api_key || ''}
- - )} - - {authType === AUTH_TYPES.BASIC_AUTH && ( - <> -
- {__('Username:', 'bit-integrations')} -
- -
{errors.username || ''}
- -
- {__('Password:', 'bit-integrations')} -
- -
{errors.password || ''}
- - )} - - {authType === AUTH_TYPES.BEARER_TOKEN && ( - <> -
- {__('Bearer Token:', 'bit-integrations')} -
- -
{errors.token || ''}
- - )} - - {authDetails?.extraFields?.map(field => ( -
-
- {field.label}: -
- -
{errors[field.name] || ''}
-
- ))} - - {customAuthFields} - - - - ) -} diff --git a/frontend/src/components/Connections/ApiConnection.jsx b/frontend/src/components/Connections/ApiConnection.jsx new file mode 100644 index 000000000..ea64e1481 --- /dev/null +++ b/frontend/src/components/Connections/ApiConnection.jsx @@ -0,0 +1,339 @@ +import { useCallback, useState } from 'react' +import toast from 'react-hot-toast' +import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' +import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' +import { __ } from '../../Utils/i18nwrap' +import LoaderSm from '../Loaders/LoaderSm' + +const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } + +const normalizeAdditionalHeaders = headers => { + if (!headers || typeof headers !== 'object') { + return {} + } + + return Object.entries(headers).reduce((acc, [key, value]) => { + const normalizedKey = String(key || '').trim() + const normalizedValue = value == null ? '' : String(value).trim() + + if (normalizedKey && normalizedValue) { + acc[normalizedKey] = normalizedValue + } + + return acc + }, {}) +} + +// Resolves {fieldName} placeholders in URL templates using authData values. +// Strips trailing slashes from substituted values to avoid double-slash in paths. +// Unknown tokens are replaced with '' (empty required fields caught by validation). +const resolveTemplate = (template, data) => { + if (!template) return '' + return template.replace(/\{(\w+)\}/g, (_, key) => { + const val = data[key] + if (val == null) return '' + return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) + }) +} + +const resolveHeaderTemplates = (headers, data) => { + if (!headers || typeof headers !== 'object') { + return {} + } + + return Object.entries(headers).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value + return acc + }, {}) +} + +const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { + const additionalHeaders = resolveHeaderTemplates( + normalizeAdditionalHeaders(authDetails?.headers), + authData + ) + const sslVerify = authDetails?.ssl_verify !== false + + // Extra fields captured first; standard auth keys below always win on collision. + // Reserved auth_details keys: value, token, key, addTo, username, password, ssl_verify + const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (authData[name] != null) acc[name] = authData[name] + return acc + }, {}) + + const basePayload = { + auth_type: authType, + api_endpoint: resolveTemplate(apiEndpoint, authData), + method: method || 'GET', + auth_details: {}, + headers: additionalHeaders + } + + if (authType === AUTH_TYPES.API_KEY) { + basePayload.auth_details = { + ...extraAuthDetails, + key: authDetails?.key || 'X-API-Key', + value: authData.api_key, + addTo: authData.addTo || authDetails?.addTo || 'header', + ssl_verify: sslVerify + } + return basePayload + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + basePayload.auth_details = { + ...extraAuthDetails, + username: authData.username, + password: authData.password, + ssl_verify: sslVerify + } + return basePayload + } + + if (authType === AUTH_TYPES.BEARER_TOKEN) { + basePayload.auth_details = { + ...extraAuthDetails, + token: authData.token, + ssl_verify: sslVerify + } + } + + return basePayload +} + +const getValidationErrors = (authType, authData, extraFields = []) => { + const nextErrors = {} + + if (!authData.connectionName?.trim()) { + nextErrors.connectionName = __('Connection name is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.API_KEY && !authData.api_key?.trim()) { + nextErrors.api_key = __('API key is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + if (!authData.username?.trim()) { + nextErrors.username = __('Username is required', 'bit-integrations') + } + + if (!authData.password?.trim()) { + nextErrors.password = __('Password is required', 'bit-integrations') + } + } + + if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token?.trim()) { + nextErrors.token = __('Bearer token is required', 'bit-integrations') + } + + extraFields.forEach(field => { + if (field.required && !authData[field.name]?.trim()) { + nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` + } + }) + + return nextErrors +} + +export default function ApiConnection({ + authDetails, + config, + setConfig, + isInfo = false, + customAuthFields, + onConnectionSaved +}) { + const [authData, setAuthData] = useState({}) + const [isAuthorized, setIsAuthorized] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [errors, setErrors] = useState({}) + const { authType, apiEndpoint, method } = authDetails || {} + + const handleChange = useCallback(event => { + const { name, value } = event.target + + setAuthData(prev => ({ ...prev, [name]: value })) + setErrors(prev => ({ ...prev, [name]: '' })) + }, []) + + const handleAuthorize = useCallback(async () => { + const validationErrors = getValidationErrors(authType, authData, authDetails?.extraFields) + setErrors(validationErrors) + + if (Object.keys(validationErrors).length > 0) { + return + } + + const payload = getAuthPayload({ + authType, + apiEndpoint, + method, + authData, + authDetails + }) + + setIsLoading(true) + + try { + const authorizeRes = await authorizeConnection(payload) + + if (!authorizeRes?.success) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')}${authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` + ) + return + } + + const saveRes = await saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: authType, + connection_name: authData.connectionName, + account_name: authData.connectionName, + auth_details: payload.auth_details, + encrypt_keys: defaultEncryptKeys[authType] || [] + }) + + if (!saveRes?.success) { + toast.error( + `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' + }. ${__('please try again', 'bit-integrations')}` + ) + return + } + + const connection = saveRes?.data?.data || null + setConfig(prev => ({ ...prev, connection_id: connection?.id })) + + if (onConnectionSaved) { + await onConnectionSaved(connection) + } + + setIsAuthorized(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + } catch (error) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` + ) + } finally { + setIsLoading(false) + } + }, [apiEndpoint, authData, authDetails, authType, config?.app_slug, config?.type, method, onConnectionSaved, setConfig]) + + return ( + <> +
+ {__('Connection Name:', 'bit-integrations')} +
+ +
{errors.connectionName || ''}
+ + {customAuthFields} + + {authDetails?.extraFields?.map(field => ( +
+
+ {field.label}: +
+ +
{errors[field.name] || ''}
+
+ ))} + + {authType === AUTH_TYPES.API_KEY && ( + <> +
+ {__('API Key:', 'bit-integrations')} +
+ +
{errors.api_key || ''}
+ + )} + + {authType === AUTH_TYPES.BASIC_AUTH && ( + <> +
+ {__('Username:', 'bit-integrations')} +
+ +
{errors.username || ''}
+ +
+ {__('Password:', 'bit-integrations')} +
+ +
{errors.password || ''}
+ + )} + + {authType === AUTH_TYPES.BEARER_TOKEN && ( + <> +
+ {__('Bearer Token:', 'bit-integrations')} +
+ +
{errors.token || ''}
+ + )} + + + + ) +} diff --git a/frontend/src/components/Connections/ConnectionAccountSelect.jsx b/frontend/src/components/Connections/ConnectionAccountSelect.jsx index c8c66ca21..d1e83c1c6 100644 --- a/frontend/src/components/Connections/ConnectionAccountSelect.jsx +++ b/frontend/src/components/Connections/ConnectionAccountSelect.jsx @@ -19,6 +19,14 @@ const buildConnectionOption = conn => { } } +const getConnectionOptionById = (connections, connection_id) => { + if (connection_id) return String(connection_id) + if (connection_id === '') return NEW_VALUE + if (Array.isArray(connections) && connections.length > 0) return '' + + return NEW_VALUE +} + export default function ConnectionAccountSelect({ config, setConfig, @@ -30,8 +38,7 @@ export default function ConnectionAccountSelect({ onConnectionSelected }) { const { integUrlName } = useParams() - - const dropdownValue = config?.connection_id ? String(config.connection_id) : NEW_VALUE + const dropdownValue = getConnectionOptionById(connections, config?.connection_id) const options = useMemo( () => [ diff --git a/frontend/src/components/Connections/Oauth2Connection.jsx b/frontend/src/components/Connections/Oauth2Connection.jsx index d662efd38..9fd055753 100644 --- a/frontend/src/components/Connections/Oauth2Connection.jsx +++ b/frontend/src/components/Connections/Oauth2Connection.jsx @@ -317,6 +317,26 @@ export default function Oauth2Connection({ />
{errors.connectionName || ''}
+ {extraFields.map(field => ( +
+
+ {field.label}: +
+ +
{errors[field.name] || ''}
+
+ ))} + + {customAuthFields} + {isAuthCodeFlow && ( <>
@@ -366,26 +386,6 @@ export default function Oauth2Connection({ />
{errors.clientSecret || ''}
- {extraFields.map(field => ( -
-
- {field.label}: -
- -
{errors[field.name] || ''}
-
- ))} - - {customAuthFields} - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/DirectIq/DirectIqCommonFunc.js b/frontend/src/components/AllIntegrations/DirectIq/DirectIqCommonFunc.js index a9fbf4f34..2b43bc62d 100644 --- a/frontend/src/components/AllIntegrations/DirectIq/DirectIqCommonFunc.js +++ b/frontend/src/components/AllIntegrations/DirectIq/DirectIqCommonFunc.js @@ -8,12 +8,17 @@ export const handleInput = (e, directIqConf, setDirectIqConf) => { setDirectIqConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + client_id: confTmp.client_id, + client_secret: confTmp.client_secret + } + // refreshMappedLists export const refreshDirectIqList = (directIqConf, setDirectIqConf, setIsLoading, setSnackbar) => { - const refreshListsRequestParams = { - client_id: directIqConf.client_id, - client_secret: directIqConf.client_secret - } + const refreshListsRequestParams = buildAuthRequestParams(directIqConf) bitsFetch(refreshListsRequestParams, 'directIq_lists') .then(result => { if (result && result.success) { @@ -52,8 +57,7 @@ export const refreshDirectIqList = (directIqConf, setDirectIqConf, setIsLoading, // refreshMappedFields export const refreshDirectIqHeader = (directIqConf, setDirectIqConf, setIsLoading, setSnackbar) => { const refreshListsRequestParams = { - client_id: directIqConf.client_id, - client_secret: directIqConf.client_secret, + ...buildAuthRequestParams(directIqConf), list_id: directIqConf.listId } diff --git a/frontend/src/components/AllIntegrations/Drip/DripAuthorization.jsx b/frontend/src/components/AllIntegrations/Drip/DripAuthorization.jsx index df6fe73e0..8e607cebe 100644 --- a/frontend/src/components/AllIntegrations/Drip/DripAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Drip/DripAuthorization.jsx @@ -1,135 +1,64 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { dripAuthentication } from './DripCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import toast from 'react-hot-toast' +import Authorization from '../../Connections/Authorization' +import { fetchDripAccounts } from './DripCommonFunc' -export default function DripAuthorization({ - formID, - dripConf, - setDripConf, - step, - setstep, - isInfo, - loading, - setLoading -}) { -const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_token: '' }) - - const handleInput = e => { - const newConf = { ...dripConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setDripConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) +export default function DripAuthorization({ dripConf, setDripConf, step, setstep, isInfo, setLoading }) { + const loadAccounts = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...dripConf, connection_id: connectionId } : dripConf + await fetchDripAccounts(nextConf, setDripConf, setLoading, 'fetch') + }, + [dripConf, setDripConf, setLoading] + ) - setstep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !(dripConf?.accounts || []).length) { + loadAccounts() + } + setstep(value) + }, + [dripConf, loadAccounts, setstep] + ) - const ActiveInstructions = ` -

${__('Get Drip Api Token', 'bit-integrations')}

- ` + const note = ` +

${__('Get Drip Api Token', 'bit-integrations')}

+
    +
  • ${__( + 'First go to your', + 'bit-integrations' + )} ${__( + 'Drip user settings', + 'bit-integrations' + )}.
  • +
  • ${__('Copy the API Token from "User Info".', 'bit-integrations')}
  • +
  • ${__( + 'Use that token as Username in the authorization form and keep Password empty.', + 'bit-integrations' + )}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Drip Api Token:', 'bit-integrations')} -
- -
{error.api_token}
- - - {__('To Get Drip Api Token, Please Visit', 'bit-integrations')} -   - - {__('Drip User Settings', 'bit-integrations')} - - -
-
- {loading?.auth && ( -
- - Checking Api Token Key!!! -
- )} - - {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Drip/DripCommonFunc.js b/frontend/src/components/AllIntegrations/Drip/DripCommonFunc.js index 1e67babf4..f7db81166 100644 --- a/frontend/src/components/AllIntegrations/Drip/DripCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Drip/DripCommonFunc.js @@ -9,55 +9,33 @@ export const handleInput = (e, dripConf, setDripConf) => { setDripConf({ ...newConf }) } -export const dripAuthentication = ( - dripConf, - setDripConf, - setError, - setisAuthorized, - loading, - setLoading, - type = 'authentication' -) => { - const newConf = { ...dripConf } +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { api_token: confTmp.api_token } - if (!newConf.name || !newConf.api_token) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_token: !newConf.api_token ? __("Access Api Token Key can't be empty", 'bit-integrations') : '' - }) +export const fetchDripAccounts = async (confTmp, setConf, setLoading, type = 'fetch') => { + if (!confTmp.connection_id && !confTmp.api_token) { + toast.error(__("Access Api Token can't be empty", 'bit-integrations')) return } - let responseErrorMsg + setLoading(prev => ({ ...prev, accounts: true })) - if (type === 'authentication') { - setLoading({ ...loading, auth: true }) - responseErrorMsg = 'Authorization Failed' - } else if (type === 'accounts') { - setLoading({ ...loading, accounts: true }) - responseErrorMsg = 'Accounts fetching failed' - } + const result = await bitsFetch(buildAuthRequestParams(confTmp), 'drip_fetch_all_accounts') - const data = { - api_token: newConf.api_token + if (result?.success) { + const newConf = { ...confTmp, accounts: result.data || [] } + setConf(newConf) + setLoading(prev => ({ ...prev, accounts: false })) + toast.success( + type === 'refresh' + ? __('Accounts fetched Successfully', 'bit-integrations') + : __('Authorized Successfully', 'bit-integrations') + ) + return } - bitsFetch(data, 'drip_authorize').then(result => { - if (result?.success) { - newConf.accounts = result.data - if (type === 'authentication') { - setisAuthorized(true) - toast.success('Authorized Successfully') - } else if (type === 'accounts') { - toast.success('Accounts fetched Successfully') - } - } else { - toast.error(responseErrorMsg) - } - - setDripConf({ ...newConf }) - setLoading({ ...loading, auth: false, accounts: false }) - }) + setLoading(prev => ({ ...prev, accounts: false })) + toast.error(__('Accounts fetching failed', 'bit-integrations')) } export const checkMappedFields = dripConf => { @@ -83,10 +61,10 @@ export const generateMappedField = dripConf => { } export const getCustomFields = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, customFields: true }) + setLoading(prev => ({ ...prev, customFields: true })) const requestParams = { - apiToken: confTmp.api_token, + ...buildAuthRequestParams(confTmp), selectedAccountId: confTmp.selectedAccountId } @@ -97,19 +75,20 @@ export const getCustomFields = (confTmp, setConf, setLoading) => { newConf.dripFormFields = [...staticFields, ...result.data] } setConf(newConf) - setLoading({ ...setLoading, customFields: false }) + setLoading(prev => ({ ...prev, customFields: false })) toast.success(__('Custom fields fetch successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, customFields: false }) + setLoading(prev => ({ ...prev, customFields: false })) toast.error(__('Custom fields fetch failed', 'bit-integrations')) }) } + export const getAllTags = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, tags: true }) + setLoading(prev => ({ ...prev, tags: true })) const requestParams = { - apiToken: confTmp.api_token, + ...buildAuthRequestParams(confTmp), selectedAccountId: confTmp.selectedAccountId } @@ -120,11 +99,11 @@ export const getAllTags = (confTmp, setConf, setLoading) => { newConf.tags = result.data } setConf(newConf) - setLoading({ ...setLoading, tags: false }) + setLoading(prev => ({ ...prev, tags: false })) toast.success(__('Tags fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, customFields: false }) + setLoading(prev => ({ ...prev, tags: false })) toast.error(__('Tags fetching failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/Drip/DripIntegLayout.jsx b/frontend/src/components/AllIntegrations/Drip/DripIntegLayout.jsx index b83aa5a09..9217f1d2e 100644 --- a/frontend/src/components/AllIntegrations/Drip/DripIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Drip/DripIntegLayout.jsx @@ -1,15 +1,14 @@ // eslint-disable-next-line import/no-extraneous-dependencies +import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers' -import { dripAuthentication, getCustomFields, staticFields } from './DripCommonFunc' -import DripFieldMap from './DripFieldMap' -import { useState } from 'react' import DripActions from './DripActions' +import { fetchDripAccounts, getCustomFields, staticFields } from './DripCommonFunc' +import DripFieldMap from './DripFieldMap' export default function DripIntegLayout({ formFields, dripConf, setDripConf, loading, setLoading }) { const [error, setError] = useState({ name: '', api_token: '' }) - const [isAuthorized, setisAuthorized] = useState(false) const handleInput = e => { const accountId = e.target.value @@ -45,14 +44,11 @@ export default function DripIntegLayout({ formFields, dripConf, setDripConf, loa -
- - - )} - - + + data?.version === 'v2' + ? 'https://connect.mailerlite.com/api/subscribers' + : 'https://api.mailerlite.com/api/v2/me', + method: 'GET', + key: 'X-Mailerlite-Apikey', + addTo: 'header', + headers: data => + data?.version === 'v2' + ? { + Authorization: 'Bearer {api_key}', + Accept: 'application/json' + } + : {}, + extraFields: [ + { + name: 'version', + label: __('MailerLite Version', 'bit-integrations'), + required: true, + type: 'select', + placeholder: __('Select version', 'bit-integrations'), + options: [ + { label: __('MailerLite Classic (v1)', 'bit-integrations'), value: 'v1' }, + { label: __('MailerLite New (v2)', 'bit-integrations'), value: 'v2' } + ] + } + ] + }} + noteDetails={{ note }} + onConnectionSelected={syncVersionFromConnection} + /> ) } diff --git a/frontend/src/components/AllIntegrations/MailerLite/MailerLiteCommonFunc.js b/frontend/src/components/AllIntegrations/MailerLite/MailerLiteCommonFunc.js index bd1a7d848..cc96fac4e 100644 --- a/frontend/src/components/AllIntegrations/MailerLite/MailerLiteCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailerLite/MailerLiteCommonFunc.js @@ -43,37 +43,16 @@ export const checkMappedFields = mailerLiteConf => { return true } -export const authorization = (confTmp, setIsAuthorized, loading, setLoading) => { - if (!confTmp.auth_token) { - toast.error(__("API Key can't be empty", 'bit-integrations')) - - return - } - - setLoading({ ...loading, auth: true }) - - const requestParams = { - auth_token: confTmp.auth_token, - version: confTmp.version - } - - bitsFetch(requestParams, 'mailerlite_authorization').then(result => { - setLoading({ ...loading, auth: false }) - - if (result && result.success) { - setIsAuthorized(true) - - toast.success(__('Authorized Successfully', 'bit-integrations')) - - return - } - - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + auth_token: confTmp.auth_token, + version: confTmp.version + } export const mailerliteRefreshFields = (confTmp, setConf, loading, setLoading) => { - if (!confTmp.auth_token) { + if (!confTmp.connection_id && !confTmp.auth_token) { toast.error(__("API Key can't be empty", 'bit-integrations')) return @@ -103,10 +82,7 @@ export const mailerliteRefreshFields = (confTmp, setConf, loading, setLoading) = return } - const requestParams = { - auth_token: confTmp.auth_token, - version: confTmp.version - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'mailerlite_refresh_fields').then(result => { setLoading({ ...loading, field: false }) @@ -129,12 +105,9 @@ export const mailerliteRefreshFields = (confTmp, setConf, loading, setLoading) = } export const getAllGroups = (confTmp, setConf, loding, setLoading) => { - setLoading({ ...setLoading, group: true }) + setLoading({ ...loding, group: true }) - const requestParams = { - auth_token: confTmp.auth_token, - version: confTmp.version - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'mailerlite_fetch_all_groups').then(result => { if (result && result.success) { @@ -143,12 +116,12 @@ export const getAllGroups = (confTmp, setConf, loding, setLoading) => { newConf.groups = result.data } setConf(newConf) - setLoading({ ...setLoading, group: false }) + setLoading({ ...loding, group: false }) toast.success(__('Group fetch successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, group: false }) + setLoading({ ...loding, group: false }) toast.error(__('Group fetch failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/Mailjet/MailjetAuthorization.jsx b/frontend/src/components/AllIntegrations/Mailjet/MailjetAuthorization.jsx index f2bf9cb19..eda35c886 100644 --- a/frontend/src/components/AllIntegrations/Mailjet/MailjetAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mailjet/MailjetAuthorization.jsx @@ -1,11 +1,10 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { mailjetAuthentication } from './MailjetCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { getAllLists } from './MailjetCommonFunc' export default function MailjetAuthorization({ mailjetConf, @@ -16,116 +15,47 @@ export default function MailjetAuthorization({ setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '', secretKey: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...mailjetConf, connection_id: connectionId } : mailjetConf + await getAllLists(nextConf, setMailjetConf, loading, setLoading, 'fetch') + }, + [mailjetConf, setMailjetConf, loading, setLoading] + ) - !mailjetConf?.default - setStep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !(mailjetConf?.lists || []).length) { + loadLists() + } + setStep(value) + }, + [mailjetConf, loadLists, setStep] + ) - const handleInput = e => { - const newConf = { ...mailjetConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailjetConf(newConf) - } + const note = ` +

${__('To Get API key & Secret Key', 'bit-integrations')}

+
    +
  • ${__('Open your Mailjet account API keys page.', 'bit-integrations')}
  • +
  • ${__('Use API Key as Username and Secret Key as Password in this form.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.apiKey}
-
- {__('Secret Key:', 'bit-integrations')} -
- -
{error.secretKey}
- - {__('To Get API key & Secret Key, Please Visit', 'bit-integrations')} -   - - {__('Mailjet API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js index d5d5422eb..71dc56b01 100644 --- a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js @@ -40,6 +40,11 @@ export const checkMappedFields = mailjetConf => { return true } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { apiKey: confTmp.apiKey, secretKey: confTmp.secretKey } + export const mailjetAuthentication = ( confTmp, setConf, @@ -49,11 +54,11 @@ export const mailjetAuthentication = ( setLoading, type ) => { - if (!confTmp.apiKey) { + if (!confTmp.connection_id && !confTmp.apiKey) { setError({ apiKey: !confTmp.apiKey ? __("API key can't be empty", 'bit-integrations') : '' }) return } - if (!confTmp.secretKey) { + if (!confTmp.connection_id && !confTmp.secretKey) { setError({ secretKey: !confTmp.secretKey ? __("Secret key can't be empty", 'bit-integrations') : '' }) @@ -68,9 +73,9 @@ export const mailjetAuthentication = ( if (type === 'refreshLists') { setLoading({ ...loading, lists: true }) } - const requestParams = { apiKey: confTmp.apiKey, secretKey: confTmp.secretKey } + const requestParams = buildAuthRequestParams(confTmp) - bitsFetch(requestParams, 'mailjet_authentication').then(result => { + bitsFetch(requestParams, 'mailjet_fetch_all_lists').then(result => { if (result && result.success) { const newConf = { ...confTmp } setIsAuthorized(true) @@ -96,10 +101,13 @@ export const mailjetAuthentication = ( }) } +export const getAllLists = (confTmp, setConf, loading, setLoading, type = 'refresh') => + mailjetAuthentication(confTmp, setConf, () => {}, () => {}, loading, setLoading, type === 'fetch' ? 'authentication' : 'refreshLists') + export const getCustomFields = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, customFields: true }) + setLoading(prev => ({ ...prev, customFields: true })) - const requestParams = { apiKey: confTmp.apiKey, secretKey: confTmp.secretKey } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'mailjet_fetch_all_custom_fields').then(result => { if (result && result.success) { @@ -108,12 +116,12 @@ export const getCustomFields = (confTmp, setConf, setLoading) => { newConf.customFields = result.data } setConf(newConf) - setLoading({ ...setLoading, customFields: false }) + setLoading(prev => ({ ...prev, customFields: false })) toast.success(__('Custom fields fetch successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, customFields: false }) + setLoading(prev => ({ ...prev, customFields: false })) toast.error(__('Custom fields fetch failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMAuthorization.jsx index 1f1b1fafb..c828220a4 100644 --- a/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMAuthorization.jsx @@ -1,141 +1,44 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { toast } from 'react-hot-toast' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { nutshellCRMAuthentication } from './NutshellCRMCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function NutshellCRMAuthorization({ nutshellCRMConf, setNutshellCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !nutshellCRMConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...nutshellCRMConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setNutshellCRMConf(newConf) - } - - const handleApiTokenLink = 'https://app.nutshell.com/setup/api-key' - - const ActiveInstructions = ` -

${__('Get API Token', 'bit-integrations')}

-
    -
  • ${__("Go to your Nutshell CRM's user dashboard", 'bit-integrations')}
  • -
  • ${__('Then select "Settings"', 'bit-integrations')}
  • -
  • ${__('Then go to "API Keys → Add API Key"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get API Token', 'bit-integrations')}

+
    +
  • ${__("Go to your Nutshell CRM's user dashboard", 'bit-integrations')}
  • +
  • ${__('Then select "Settings"', 'bit-integrations')}
  • +
  • ${__('Then go to "API Keys → Add API Key"', 'bit-integrations')}
  • +
  • ${__('Use User Name as Username and API Token as Password in this form.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('User Name:', 'bit-integrations')} -
- -
{error.user_name}
- -
- {__('API Token:', 'bit-integrations')} -
- -
{error.api_token}
- - - {__('To Get User Name & API Token, Please Visit', 'bit-integrations')} -   - - {__('NutshellCRM User Name & API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMCommonFunc.js b/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMCommonFunc.js index d35598942..57c349833 100644 --- a/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/NutshellCRM/NutshellCRMCommonFunc.js @@ -45,49 +45,18 @@ export const checkMappedFields = nutshellCRMConf => { return true } -export const nutshellCRMAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.user_name || !confTmp.api_token) { - setError({ - user_name: !confTmp.user_name ? __("User Name can't be empty", 'bit-integrations') : '', - api_token: !confTmp.api_token ? __("API Token can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } - - bitsFetch(requestParams, 'nutshellcrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid User Name & Api Token', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + user_name: confTmp.user_name, + api_token: confTmp.api_token + } export const getAllCompanies = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, companies: true }) + setLoading(prev => ({ ...prev, companies: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_companies').then(result => { if (result && result.success) { @@ -96,23 +65,20 @@ export const getAllCompanies = (confTmp, setConf, setLoading) => { newConf.companies = result.data } setConf(newConf) - setLoading({ ...setLoading, companies: false }) + setLoading(prev => ({ ...prev, companies: false })) toast.success(__('Companies fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, companies: false }) + setLoading(prev => ({ ...prev, companies: false })) toast.error(__('Companies fetching failed', 'bit-integrations')) }) } export const getAllContacts = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, contacts: true }) + setLoading(prev => ({ ...prev, contacts: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_contacts').then(result => { if (result && result.success) { @@ -121,23 +87,20 @@ export const getAllContacts = (confTmp, setConf, setLoading) => { newConf.contacts = result.data } setConf(newConf) - setLoading({ ...setLoading, contacts: false }) + setLoading(prev => ({ ...prev, contacts: false })) toast.success(__('Contacts fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, contacts: false }) + setLoading(prev => ({ ...prev, contacts: false })) toast.error(__('Contacts fetching failed', 'bit-integrations')) }) } export const getAllSources = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, sources: true }) + setLoading(prev => ({ ...prev, sources: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_sources').then(result => { if (result && result.success) { @@ -146,22 +109,19 @@ export const getAllSources = (confTmp, setConf, setLoading) => { newConf.sources = result.data } setConf(newConf) - setLoading({ ...setLoading, sources: false }) + setLoading(prev => ({ ...prev, sources: false })) toast.success(__('Sources fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, sources: false }) + setLoading(prev => ({ ...prev, sources: false })) toast.error(__('Sources fetching failed', 'bit-integrations')) }) } export const getAllTags = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, tags: true }) + setLoading(prev => ({ ...prev, tags: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_tags').then(result => { if (result && result.success) { @@ -170,23 +130,20 @@ export const getAllTags = (confTmp, setConf, setLoading) => { newConf.tags = result.data } setConf(newConf) - setLoading({ ...setLoading, tags: false }) + setLoading(prev => ({ ...prev, tags: false })) toast.success(__('Tags fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, tags: false }) + setLoading(prev => ({ ...prev, tags: false })) toast.error(__('Tags fetching failed', 'bit-integrations')) }) } export const getAllProducts = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, products: true }) + setLoading(prev => ({ ...prev, products: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_products').then(result => { if (result && result.success) { @@ -195,23 +152,20 @@ export const getAllProducts = (confTmp, setConf, setLoading) => { newConf.products = result.data } setConf(newConf) - setLoading({ ...setLoading, products: false }) + setLoading(prev => ({ ...prev, products: false })) toast.success(__('Products fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, products: false }) + setLoading(prev => ({ ...prev, products: false })) toast.error(__('Products fetching failed', 'bit-integrations')) }) } export const getAllCompanyTypes = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, companyTypes: true }) + setLoading(prev => ({ ...prev, companyTypes: true })) - const requestParams = { - user_name: confTmp.user_name, - api_token: confTmp.api_token - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nutshellcrm_fetch_all_companytypes').then(result => { if (result && result.success) { @@ -220,12 +174,12 @@ export const getAllCompanyTypes = (confTmp, setConf, setLoading) => { newConf.companyTypes = result.data } setConf(newConf) - setLoading({ ...setLoading, companyTypes: false }) + setLoading(prev => ({ ...prev, companyTypes: false })) toast.success(__('CompanyTypes fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, companyTypes: false }) + setLoading(prev => ({ ...prev, companyTypes: false })) toast.error(__('CompanyTypes fetching failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMAuthorization.jsx index e5966be57..855f3b829 100644 --- a/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMAuthorization.jsx @@ -1,158 +1,62 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { toast } from 'react-hot-toast' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { perfexCRMAuthentication } from './PerfexCRMCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' export default function PerfexCRMAuthorization({ perfexCRMConf, setPerfexCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !perfexCRMConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...perfexCRMConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setPerfexCRMConf(newConf) - } - - const handleApiTokenLink = () => { - perfexCRMConf.domain - ? window.open(`${perfexCRMConf.domain}/admin/api/api_management`, '_blank', 'noreferrer') - : toast.error(__('Access API URL is required!', 'bit-integrations')) - } - - const ActiveInstructions = ` -

${__('Get API Token', 'bit-integrations')}

-
    -
  • ${__( - "Go to your Perfex CRM's Admin area and select the following menu item: SETUP → MODULES.", - 'bit-integrations' - )}
  • -
  • ${__( - 'Select the extracted upload.zip at Module installation selection prompt and press INSTALL.', - 'bit-integrations' - )}
  • -
  • ${__( - 'Find the newly installed module in the list, press ACTIVATE and enter your license key.', - 'bit-integrations' - )}
  • -
  • ${__( - "Go to your Perfex's CRM backend as an admin, go to API → API Management, and create a new token.", - 'bit-integrations' - )}
  • -
` + const note = ` +

${__('Get API Token', 'bit-integrations')}

+
    +
  • ${__( + "Go to your Perfex CRM's Admin area and select the following menu item: SETUP → MODULES.", + 'bit-integrations' + )}
  • +
  • ${__( + 'Select the extracted upload.zip at Module installation selection prompt and press INSTALL.', + 'bit-integrations' + )}
  • +
  • ${__( + 'Find the newly installed module in the list, press ACTIVATE and enter your license key.', + 'bit-integrations' + )}
  • +
  • ${__( + "Go to your Perfex's CRM backend as an admin, go to API → API Management, and create a new token.", + 'bit-integrations' + )}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Access API URL:', 'bit-integrations')} -
- -
{error.domain}
- -
- {__('API Token:', 'bit-integrations')} -
- -
{error.api_token}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('PerfexCRM API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMCommonFunc.js b/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMCommonFunc.js index c509ed6a3..53bfc261d 100644 --- a/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/PerfexCRM/PerfexCRMCommonFunc.js @@ -16,12 +16,19 @@ export const handleInput = (e, perfexCRMConf, setPerfexCRMConf) => { setPerfexCRMConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_token: confTmp.api_token, + domain: confTmp.domain + } + // refreshMappedFields export const refreshCustomFields = (perfexCRMConf, setPerfexCRMConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - api_token: perfexCRMConf.api_token, - domain: perfexCRMConf.domain, + ...buildAuthRequestParams(perfexCRMConf), action_name: perfexCRMConf.actionName } @@ -97,53 +104,10 @@ export const checkMappedFields = perfexCRMConf => { return true } -export const perfexCRMAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_token || !confTmp.domain) { - setError({ - api_token: !confTmp.api_token ? __("API Token can't be empty", 'bit-integrations') : '', - domain: !confTmp.domain ? __("API URL can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_token: confTmp.api_token, - domain: confTmp.domain - } - - bitsFetch(requestParams, 'perfexcrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - String(result?.data) - ? result?.data - : __('Authorized failed, Please enter valid API Token or Access API URL', 'bit-integrations') - ) - }) -} - export const getAllCustomer = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, customers: true }) - const requestParams = { - api_token: confTmp.api_token, - domain: confTmp.domain - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'perfexcrm_fetch_all_customers').then(result => { if (result && result.success) { @@ -166,10 +130,7 @@ export const getAllCustomer = (confTmp, setConf, loading, setLoading) => { export const getAllLeads = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, leads: true }) - const requestParams = { - api_token: confTmp.api_token, - domain: confTmp.domain - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'perfexcrm_fetch_all_leads').then(result => { if (result && result.success) { @@ -192,10 +153,7 @@ export const getAllLeads = (confTmp, setConf, loading, setLoading) => { export const getAllStaffs = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, staffs: true }) - const requestParams = { - api_token: confTmp.api_token, - domain: confTmp.domain - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'perfexcrm_fetch_all_staffs').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx b/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx index 41a6d7c34..95bdd09aa 100644 --- a/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx @@ -1,191 +1,69 @@ -import { useState } from 'react' -import { toast } from 'react-hot-toast' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { refreshSendPulseList } from './SendPulseCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshSendPulseList } from './SendPulseCommonFunc' export default function SendPulseAuthorization({ - formID, sendPulseConf, setSendPulseConf, step, setstep, setSnackbar, isInfo, - isLoading, setIsLoading }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', client_secret: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const handleAuthorize = () => { - const newConf = { ...sendPulseConf } - if (!newConf.name || !newConf.client_secret) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - client_id: !newConf.client_id ? __("Access Client Id can't be empty", 'bit-integrations') : '', - client_secret: !newConf.client_secret - ? __("Access Client Secret Key can't be empty", 'bit-integrations') - : '' - }) - return - } - setIsLoading('auth') - const data = { - client_id: newConf.client_id, - client_secret: newConf.client_secret - } - bitsFetch(data, 'sendPulse_authorize').then(result => { - if (result && result.success) { - const newConf = { ...sendPulseConf } - newConf.tokenDetails = result.data - setSendPulseConf(newConf) - setisAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...sendPulseConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSendPulseConf(newConf) - } + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...sendPulseConf, connection_id: connectionId } + : sendPulseConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + refreshSendPulseList(nextConf, setSendPulseConf, setIsLoading, setSnackbar) + }, + [sendPulseConf, setSendPulseConf, setIsLoading, setSnackbar] + ) - refreshSendPulseList(sendPulseConf, setSendPulseConf, setIsLoading, setSnackbar) - setstep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !sendPulseConf?.default?.sendPulseLists) { + loadLists() + } + setstep(value) + }, + [sendPulseConf, loadLists, setstep] + ) - const ActiveInstructions = ` -

${__('Get client id and client secret key', 'bit-integrations')}

-
    -
  • ${__('First go to your SendPulse dashboard.', 'bit-integrations')}
  • -
  • ${__('Click "Integrations", Then click "API Keys"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get client id and client secret key', 'bit-integrations')}

+
    +
  • ${__('First go to your SendPulse dashboard.', 'bit-integrations')}
  • +
  • ${__('Click "Integrations", Then click "API Keys"', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access Client id:', 'bit-integrations')} -
- -
{error.client_id}
- -
- {__('Access Client Secret Key:', 'bit-integrations')} -
- -
{error.client_secret}
- - - {__('To Get Client Id and Client Secret Key, Please Visit', 'bit-integrations')} -   - - {__('Send Pulse API Token', 'bit-integrations')} - - -
-
- {isLoading === 'auth' && ( -
- - Checking Client Secret Key!!! -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Client Secret key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js b/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js index 8cccabb71..ec8b67783 100644 --- a/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js @@ -8,13 +8,18 @@ export const handleInput = (e, sendPulseConf, setSendPulseConf) => { setSendPulseConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + client_id: confTmp.client_id, + client_secret: confTmp.client_secret, + tokenDetails: confTmp.tokenDetails + } + // refreshMappedLists export const refreshSendPulseList = (sendPulseConf, setSendPulseConf, setIsLoading, setSnackbar) => { - const refreshListsRequestParams = { - client_id: sendPulseConf.client_id, - client_secret: sendPulseConf.client_secret, - tokenDetails: sendPulseConf.tokenDetails - } + const refreshListsRequestParams = buildAuthRequestParams(sendPulseConf) bitsFetch(refreshListsRequestParams, 'sendPulse_lists') .then(result => { if (result && result.success) { @@ -53,10 +58,8 @@ export const refreshSendPulseList = (sendPulseConf, setSendPulseConf, setIsLoadi // refreshMappedFields export const refreshSendPulseHeader = (sendPulseConf, setSendPulseConf, setIsLoading, setSnackbar) => { const refreshListsRequestParams = { - client_id: sendPulseConf.client_id, - client_secret: sendPulseConf.client_secret, + ...buildAuthRequestParams(sendPulseConf), list_id: sendPulseConf.listId, - tokenDetails: sendPulseConf.tokenDetails } bitsFetch(refreshListsRequestParams, 'sendPulse_headers') diff --git a/frontend/src/components/Connections/ApiConnection.jsx b/frontend/src/components/Connections/ApiConnection.jsx index ea64e1481..59d6953cf 100644 --- a/frontend/src/components/Connections/ApiConnection.jsx +++ b/frontend/src/components/Connections/ApiConnection.jsx @@ -47,9 +47,39 @@ const resolveHeaderTemplates = (headers, data) => { }, {}) } +const resolvePayloadTemplates = (payload, data) => { + if (Array.isArray(payload)) { + return payload.map(item => resolvePayloadTemplates(item, data)) + } + + if (payload && typeof payload === 'object') { + return Object.entries(payload).reduce((acc, [key, value]) => { + acc[key] = resolvePayloadTemplates(value, data) + return acc + }, {}) + } + + if (typeof payload === 'string') { + return resolveTemplate(payload, data) + } + + return payload +} + +const resolveConfigValue = (value, data) => { + if (typeof value === 'function') { + return value(data) + } + + return value +} + const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { + const resolvedApiEndpoint = resolveConfigValue(apiEndpoint, authData) + const resolvedHeaders = resolveConfigValue(authDetails?.headers, authData) + const resolvedPayload = resolveConfigValue(authDetails?.payload, authData) const additionalHeaders = resolveHeaderTemplates( - normalizeAdditionalHeaders(authDetails?.headers), + normalizeAdditionalHeaders(resolvedHeaders), authData ) const sslVerify = authDetails?.ssl_verify !== false @@ -63,12 +93,16 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } const basePayload = { auth_type: authType, - api_endpoint: resolveTemplate(apiEndpoint, authData), + api_endpoint: resolveTemplate(resolvedApiEndpoint, authData), method: method || 'GET', auth_details: {}, headers: additionalHeaders } + if (resolvedPayload !== undefined) { + basePayload.payload = resolvePayloadTemplates(resolvedPayload, authData) + } + if (authType === AUTH_TYPES.API_KEY) { basePayload.auth_details = { ...extraAuthDetails, @@ -101,7 +135,7 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } return basePayload } -const getValidationErrors = (authType, authData, extraFields = []) => { +const getValidationErrors = (authType, authData, extraFields = [], authDetails = {}) => { const nextErrors = {} if (!authData.connectionName?.trim()) { @@ -117,7 +151,7 @@ const getValidationErrors = (authType, authData, extraFields = []) => { nextErrors.username = __('Username is required', 'bit-integrations') } - if (!authData.password?.trim()) { + if (!authDetails?.allowEmptyPassword && !authData.password?.trim()) { nextErrors.password = __('Password is required', 'bit-integrations') } } @@ -127,7 +161,9 @@ const getValidationErrors = (authType, authData, extraFields = []) => { } extraFields.forEach(field => { - if (field.required && !authData[field.name]?.trim()) { + const value = authData[field.name] + const normalized = typeof value === 'string' ? value.trim() : value + if (field.required && (normalized === '' || normalized == null)) { nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` } }) @@ -157,7 +193,12 @@ export default function ApiConnection({ }, []) const handleAuthorize = useCallback(async () => { - const validationErrors = getValidationErrors(authType, authData, authDetails?.extraFields) + const validationErrors = getValidationErrors( + authType, + authData, + authDetails?.extraFields, + authDetails + ) setErrors(validationErrors) if (Object.keys(validationErrors).length > 0) { @@ -204,7 +245,14 @@ export default function ApiConnection({ } const connection = saveRes?.data?.data || null - setConfig(prev => ({ ...prev, connection_id: connection?.id })) + const persistedExtraFields = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (authData[name] != null) { + acc[name] = authData[name] + } + return acc + }, {}) + + setConfig(prev => ({ ...prev, connection_id: connection?.id, ...persistedExtraFields })) if (onConnectionSaved) { await onConnectionSaved(connection) @@ -245,15 +293,31 @@ export default function ApiConnection({
{field.label}:
- + {field.type === 'select' ? ( + + ) : ( + + )}
{errors[field.name] || ''}
))} @@ -292,19 +356,23 @@ export default function ApiConnection({ />
{errors.username || ''}
-
- {__('Password:', 'bit-integrations')} -
- -
{errors.password || ''}
+ {!authDetails?.allowEmptyPassword && ( + <> +
+ {__('Password:', 'bit-integrations')} +
+ +
{errors.password || ''}
+ + )} )} From 24fa0110cd68d5199b2d65eff8c4a6708152390e Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 17:58:45 +0600 Subject: [PATCH 27/58] refactor: integration authorization components to use a unified Authorization component - Updated ActiveCampaign, Asana, Bento, CapsuleCRM, and MoxieCRM authorization components to utilize a new Authorization component for handling authentication. - Simplified state management and input handling by removing redundant code. - Introduced a consistent structure for authorization details, including API endpoints and authentication methods. - Improved user instructions for obtaining API keys and tokens across various integrations. --- backend/Actions/ACPT/ACPTController.php | 47 +--- backend/Actions/ACPT/Routes.php | 2 - .../ActiveCampaignController.php | 46 +--- backend/Actions/ActiveCampaign/Routes.php | 1 - backend/Actions/Asana/AsanaController.php | 30 +-- backend/Actions/Asana/Routes.php | 1 - backend/Actions/Bento/BentoController.php | 29 +-- backend/Actions/Bento/Routes.php | 1 - .../CapsuleCRM/CapsuleCRMController.php | 31 +-- backend/Actions/CapsuleCRM/Routes.php | 1 - .../Actions/MoxieCRM/MoxieCRMController.php | 32 +-- backend/Actions/MoxieCRM/Routes.php | 1 - .../ACPT/ACPTAuthorization.jsx | 183 ++++----------- .../AllIntegrations/ACPT/ACPTCommonFunc.js | 40 ---- .../ActiveCampaignAuthorization.jsx | 208 ++++++------------ .../ActiveCampaignCommonFunc.js | 28 +-- .../Asana/AsanaAuthorization.jsx | 136 +++--------- .../AllIntegrations/Asana/AsanaCommonFunc.js | 39 +--- .../Bento/BentoAuthorization.jsx | 177 +++------------ .../AllIntegrations/Bento/BentoCommonFunc.js | 43 +--- .../CapsuleCRM/CapsuleCRMAuthorization.jsx | 152 +++---------- .../CapsuleCRM/CapsuleCRMCommonFunc.js | 59 ++--- .../MoxieCRM/MoxieCRMAuthorization.jsx | 153 +++---------- .../MoxieCRM/MoxieCRMCommonFunc.js | 50 +---- 24 files changed, 340 insertions(+), 1150 deletions(-) diff --git a/backend/Actions/ACPT/ACPTController.php b/backend/Actions/ACPT/ACPTController.php index af0fc8825..8ef63013a 100644 --- a/backend/Actions/ACPT/ACPTController.php +++ b/backend/Actions/ACPT/ACPTController.php @@ -6,7 +6,7 @@ namespace BitApps\Integrations\Actions\ACPT; -use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use WP_Error; /** @@ -14,27 +14,14 @@ */ class ACPTController { - protected $_defaultHeader; - - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key); - - $apiEndpoint = $fieldsRequestParams->base_url . '/wp-json/acpt/v1/taxonomy'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (is_wp_error($response) || HttpHelper::$responseCode != 200) { - wp_send_json_error( - !empty($response->message) - ? $response->message - : __('Please enter valid Api key-secret', 'bit-integrations'), - HttpHelper::$responseCode - ); - } - - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'acpt', + 'fields' => [ + 'api_key' => 'value', + 'base_url' => 'base_url', + ], + ]; public function execute($integrationData, $fieldValues) { @@ -59,20 +46,4 @@ public function execute($integrationData, $fieldValues) return $acptApiResponse; } - - private function checkValidation($fieldsRequestParams, $customParam = '**') - { - if (empty($fieldsRequestParams->base_url) || empty($fieldsRequestParams->api_key) || empty($customParam)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - } - - private function setHeaders($apiKey) - { - $this->_defaultHeader = [ - 'acpt-api-key' => $apiKey, - 'Content-Type' => 'application/json', - 'accept' => 'application/json', - ]; - } } diff --git a/backend/Actions/ACPT/Routes.php b/backend/Actions/ACPT/Routes.php index 981f2b9fb..51dc11fcf 100644 --- a/backend/Actions/ACPT/Routes.php +++ b/backend/Actions/ACPT/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\ACPT\ACPTController; use BitApps\Integrations\Core\Util\Route; - -Route::post('acpt_authentication', [ACPTController::class, 'authentication']); diff --git a/backend/Actions/ActiveCampaign/ActiveCampaignController.php b/backend/Actions/ActiveCampaign/ActiveCampaignController.php index d3be0cbd6..ceb0fa833 100644 --- a/backend/Actions/ActiveCampaign/ActiveCampaignController.php +++ b/backend/Actions/ActiveCampaign/ActiveCampaignController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ActiveCampaign; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class ActiveCampaignController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'activecampaign', + 'fields' => [ + 'api_key' => 'value', + 'api_url' => 'api_url', + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -26,42 +36,6 @@ public static function _apiEndpoint($api_url, $method) return "{$api_url}/api/3/{$method}/"; } - /** - * Process ajax request - * - * @param $requestsParams Params to authorize - * - * @return JSON Active Campaign api response and status - */ - public static function activeCampaignAuthorize($requestsParams) - { - if ( - empty($requestsParams->api_key) - || empty($requestsParams->api_url) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::_apiEndpoint($requestsParams->api_url, 'accounts'); - $authorizationHeader['Api-Token'] = $requestsParams->api_key; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || empty($apiResponse)) { - wp_send_json_error( - empty($apiResponse) ? 'Unknown' : $apiResponse, - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh lists * diff --git a/backend/Actions/ActiveCampaign/Routes.php b/backend/Actions/ActiveCampaign/Routes.php index b5e496266..ae12ead36 100644 --- a/backend/Actions/ActiveCampaign/Routes.php +++ b/backend/Actions/ActiveCampaign/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ActiveCampaign\ActiveCampaignController; use BitApps\Integrations\Core\Util\Route; -Route::post('aCampaign_authorize', [ActiveCampaignController::class, 'activeCampaignAuthorize']); Route::post('aCampaign_headers', [ActiveCampaignController::class, 'activeCampaignHeaders']); Route::post('aCampaign_lists', [ActiveCampaignController::class, 'activeCampaignLists']); Route::post('aCampaign_accounts', [ActiveCampaignController::class, 'activeCampaignAccounts']); diff --git a/backend/Actions/Asana/AsanaController.php b/backend/Actions/Asana/AsanaController.php index 3c268fbda..1ac477b70 100644 --- a/backend/Actions/Asana/AsanaController.php +++ b/backend/Actions/Asana/AsanaController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Asana; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,7 +15,13 @@ */ class AsanaController { - protected $_defaultHeader; + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'asana', + 'fields' => [ + 'api_key' => 'token', + ], + ]; protected $apiEndpoint; @@ -23,27 +30,6 @@ public function __construct() $this->apiEndpoint = 'https://app.asana.com/api/1.0/'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->apiEndpoint . 'users/me'; - $headers = [ - 'Authorization' => 'Bearer ' . $apiKey, - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->data)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/Asana/Routes.php b/backend/Actions/Asana/Routes.php index 34ac3470b..d780696b5 100644 --- a/backend/Actions/Asana/Routes.php +++ b/backend/Actions/Asana/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Asana\AsanaController; use BitApps\Integrations\Core\Util\Route; -Route::post('asana_authentication', [AsanaController::class, 'authentication']); Route::post('asana_fetch_custom_fields', [AsanaController::class, 'getCustomFields']); Route::post('asana_fetch_all_tasks', [AsanaController::class, 'getAllTasks']); Route::post('asana_fetch_all_Projects', [AsanaController::class, 'getAllProjects']); diff --git a/backend/Actions/Bento/BentoController.php b/backend/Actions/Bento/BentoController.php index 5fd99b32a..6a72923b4 100644 --- a/backend/Actions/Bento/BentoController.php +++ b/backend/Actions/Bento/BentoController.php @@ -6,9 +6,9 @@ namespace BitApps\Integrations\Actions\Bento; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\Hooks; -use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; /** @@ -16,24 +16,15 @@ */ class BentoController { - protected $_defaultHeader; - - public function authentication($fieldsRequestParams) - { - BentoHelper::checkValidation($fieldsRequestParams); - - $headers = BentoHelper::setHeaders($fieldsRequestParams->publishable_key, $fieldsRequestParams->secret_key); - $apiEndpoint = BentoHelper::setEndpoint('tags', $fieldsRequestParams->site_uuid); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (BentoHelper::checkResponseCode()) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - - return; - } - - wp_send_json_error(!empty($response) ? $response : __('Please enter valid Publishable Key, Secret Key & Site UUID', 'bit-integrations'), 400); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::BASIC_AUTH, + 'slug' => 'bento', + 'fields' => [ + 'publishable_key' => 'username', + 'secret_key' => 'password', + 'site_uuid' => 'site_uuid', + ], + ]; public function getAllFields($fieldsRequestParams) { diff --git a/backend/Actions/Bento/Routes.php b/backend/Actions/Bento/Routes.php index 764aa9011..1dd87a4f4 100644 --- a/backend/Actions/Bento/Routes.php +++ b/backend/Actions/Bento/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Bento\BentoController; use BitApps\Integrations\Core\Util\Route; -Route::post('bento_authentication', [BentoController::class, 'authentication']); Route::post('bento_get_fields', [BentoController::class, 'getAllFields']); Route::post('bento_get_all_tags', [BentoController::class, 'getAlTags']); diff --git a/backend/Actions/CapsuleCRM/CapsuleCRMController.php b/backend/Actions/CapsuleCRM/CapsuleCRMController.php index ef644eccb..c6e1b2f7d 100644 --- a/backend/Actions/CapsuleCRM/CapsuleCRMController.php +++ b/backend/Actions/CapsuleCRM/CapsuleCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\CapsuleCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class CapsuleCRMController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'capsulecrm', + 'fields' => [ + 'api_key' => 'token', + 'api_url' => 'api_url', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,27 +33,6 @@ public function __construct() $this->apiEndpoint = 'https://api.capsulecrm.com/api/v2'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->apiEndpoint . '/users'; - $headers = [ - 'Authorization' => 'Bearer ' . $apiKey, - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->users)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/CapsuleCRM/Routes.php b/backend/Actions/CapsuleCRM/Routes.php index e6ebd62d1..21103a3fb 100644 --- a/backend/Actions/CapsuleCRM/Routes.php +++ b/backend/Actions/CapsuleCRM/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\CapsuleCRM\CapsuleCRMController; use BitApps\Integrations\Core\Util\Route; -Route::post('capsulecrm_authentication', [CapsuleCRMController::class, 'authentication']); Route::post('capsulecrm_fetch_custom_fields', [CapsuleCRMController::class, 'getCustomFields']); Route::post('capsulecrm_fetch_all_opportunities', [CapsuleCRMController::class, 'getAllOpportunities']); Route::post('capsulecrm_fetch_all_owners', [CapsuleCRMController::class, 'getAllOwners']); diff --git a/backend/Actions/MoxieCRM/MoxieCRMController.php b/backend/Actions/MoxieCRM/MoxieCRMController.php index 555e728a6..773244575 100644 --- a/backend/Actions/MoxieCRM/MoxieCRMController.php +++ b/backend/Actions/MoxieCRM/MoxieCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\MoxieCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class MoxieCRMController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'moxiecrm', + 'fields' => [ + 'api_key' => 'value', + 'api_url' => 'api_url', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,28 +33,6 @@ class MoxieCRMController // $this->apiEndpoint = "https://api.moxie.com/developer_api/v1"; // } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = 'https://' . $fieldsRequestParams->api_url . '/api/public/action/users/list'; - $headers = [ - 'X-API-KEY' => $apiKey, - 'Content-Type' => 'application/json' - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (!isset($response->error)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - // public function getCustomFields($fieldsRequestParams) // { // if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/MoxieCRM/Routes.php b/backend/Actions/MoxieCRM/Routes.php index 70192e20b..68c77b676 100644 --- a/backend/Actions/MoxieCRM/Routes.php +++ b/backend/Actions/MoxieCRM/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\MoxieCRM\MoxieCRMController; use BitApps\Integrations\Core\Util\Route; -Route::post('moxiecrm_authentication', [MoxieCRMController::class, 'authentication']); // Route::post('moxiecrm_fetch_custom_fields', [MoxieCRMController::class, 'getCustomFields']); Route::post('moxiecrm_fetch_all_opportunities', [MoxieCRMController::class, 'getAllOpportunities']); Route::post('moxiecrm_fetch_all_clients', [MoxieCRMController::class, 'getAllClients']); diff --git a/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx b/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx index 0a3833bd1..ae85836f4 100644 --- a/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx @@ -1,147 +1,48 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useCallback, useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { acptAuthentication } from './ACPTCommonFunc' - -export default function ACPTAuthorization({ - acptConf, - setAcptConf, - step, - setStep, - loading, - setLoading, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', api_secret: '' }) -const handleInput = useCallback(e => { - const { name, value } = e.target - - setAcptConf(prev => ({ - ...prev, - [name]: value - })) - - setError(prev => ({ - ...prev, - [name]: '' - })) - }, []) - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - if (isAuthorized) { - setStep(2) - } - } +import Authorization from '../../Connections/Authorization' + +export default function ACPTAuthorization({ acptConf, setAcptConf, step, setStep, isInfo }) { + const note = ` + ${__('Please note', 'bit-integrations')} +

${__( + 'The secret key will no longer be displayed, so please take note of it. Eventually, you can regenerate your API keys.', + 'bit-integrations' + )}

+

${__('To get API key-secret', 'bit-integrations')}

+
    +
  • ${__('Go to the ACPT dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Tools, then go to API dashboard.', 'bit-integrations')}
  • +
  • ${__('Open REST API and generate an API key if needed.', 'bit-integrations')}
  • +
  • ${__('Copy the generated key-secret pair.', 'bit-integrations')}
  • +
` return ( -
- - -
-
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
-
-
- {__('Homepage URL:', 'bit-integrations')} -
- -
{error.base_url}
-
-
-
- {__('Api Key-Secret:', 'bit-integrations')} -
- -
{error.api_key}
-
- - {!isInfo && ( -
- - -
- - -
- )} - - -
+ ) } - -const note = ` - ${__('Please note', 'bit-integrations')} -

${__( - 'The secret key will no longer be displayed, so please take note of it. Eventually, you can regenerate your API keys.', - 'bit-integrations' - )}

-

${__('To Get Api Key-secret', 'bit-integrations')}

-
    -
  • ${__('First go to "ACPT" dashboard', 'bit-integrations')}
  • -
  • ${__('Then go to "Tools" from menu', 'bit-integrations')}
  • -
  • ${__('Click on "Go to API dashboard" from tools', 'bit-integrations')}
  • -
  • ${__('Then click "REST API" from the top sub menu', 'bit-integrations')}
  • -
  • ${__( - 'Then If you don’t have one API key click on the "Generate API key" button.', - 'bit-integrations' - )}
  • -
  • ${__( - 'The API "key-secret" pair will be displayed in a popup.', - 'bit-integrations' - )}
  • -
` diff --git a/frontend/src/components/AllIntegrations/ACPT/ACPTCommonFunc.js b/frontend/src/components/AllIntegrations/ACPT/ACPTCommonFunc.js index 9a9af4865..3a7161102 100644 --- a/frontend/src/components/AllIntegrations/ACPT/ACPTCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ACPT/ACPTCommonFunc.js @@ -1,8 +1,5 @@ /* 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, salesmateConf, setSalesmateConf) => { const newConf = { ...salesmateConf } @@ -40,40 +37,3 @@ export const checkMappedFields = acptConf => { } return true } - -export const acptAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.api_key || !confTmp.base_url) { - setError({ - base_url: !confTmp.base_url ? __("Homepage URL can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("Api Key-Secret can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key - } - - bitsFetch(requestParams, 'acpt_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - - toast.success(__('Authorized Successfully', 'bit-integrations')) - - return - } - - setLoading({ ...loading, auth: false }) - - toast.error( - result?.data && typeof result.data === 'string' - ? result.data - : __('Authorized failed, Please enter valid Api Key-Secret', 'bit-integrations') - ) - }) -} diff --git a/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignAuthorization.jsx b/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignAuthorization.jsx index 098263665..fc55a5a5d 100644 --- a/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignAuthorization.jsx @@ -1,162 +1,86 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { refreshActiveCampaingHeader, refreshActiveCampaingList } from './ActiveCampaignCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' +import { + refreshActiveCampaingAccounts, + refreshActiveCampaingHeader, + refreshActiveCampaingList, + refreshActiveCampaingTags +} from './ActiveCampaignCommonFunc' export default function ActiveCampaignAuthorization({ - formID, activeCampaingConf, setActiveCampaingConf, step, setstep, setSnackbar, isInfo, - isLoading, setIsLoading }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - // const [isLoading, setIsLoading] = useState(false) + const loadMetadata = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...activeCampaingConf, connection_id: connectionId } + : activeCampaingConf - const handleAuthorize = () => { - const newConf = { ...activeCampaingConf } - if (!newConf.name || !newConf.api_key || !newConf.api_url) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("Access Api Key can't be empty", 'bit-integrations') : '', - api_url: !newConf.api_url ? __("Access API URL can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const data = { - api_key: newConf.api_key, - api_url: newConf.api_url - } - bitsFetch(data, 'aCampaign_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) + refreshActiveCampaingList(nextConf, setActiveCampaingConf, setIsLoading, setSnackbar) + refreshActiveCampaingHeader(nextConf, setActiveCampaingConf, setIsLoading, setSnackbar) + refreshActiveCampaingAccounts(nextConf, setActiveCampaingConf, setIsLoading, setSnackbar) + refreshActiveCampaingTags(nextConf, setActiveCampaingConf, setIsLoading, setSnackbar) + }, + [activeCampaingConf, setActiveCampaingConf, setIsLoading, setSnackbar] + ) + + const handleSetStep = useCallback( + value => { + if ( + value === 2 && + (!activeCampaingConf?.default?.activeCampaignLists || !activeCampaingConf?.default?.fields) + ) { + loadMetadata() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...activeCampaingConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setActiveCampaingConf(newConf) - } - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - refreshActiveCampaingHeader(activeCampaingConf, setActiveCampaingConf, setIsLoading, setSnackbar) - refreshActiveCampaingList(activeCampaingConf, setActiveCampaingConf, setIsLoading, setSnackbar) - setstep(2) - } + setstep(value) + }, + [activeCampaingConf, loadMetadata, setstep] + ) - const ActiveInstructions = ` -

${__('Get api url and api key', 'bit-integrations')}

-
    -
  • ${__('First go to activeCampaign your dashboard.', 'bit-integrations')}
  • -
  • ${__('Click Settings, Then click Developer', 'bit-integrations')}"
  • -
` + const note = ` +

${__('Get API URL and API key', 'bit-integrations')}

+
    +
  • ${__('Go to your ActiveCampaign dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Settings, then Developer.', 'bit-integrations')}
  • +
  • ${__('Copy API URL and API Key.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access API URL:', 'bit-integrations')} -
- -
{error.api_url}
- -
- {__('Access API Key:', 'bit-integrations')} -
- -
{error.api_key}
- {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignCommonFunc.js b/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignCommonFunc.js index dc71f1319..4c8c991df 100644 --- a/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ActiveCampaign/ActiveCampaignCommonFunc.js @@ -91,16 +91,21 @@ export const handleInput = (e, activeCampaingConf, setActiveCampaingConf) => { setActiveCampaingConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + api_url: confTmp.api_url + } + export const refreshActiveCampaingList = ( activeCampaingConf, setActiveCampaingConf, setIsLoading, setSnackbar ) => { - const refreshListsRequestParams = { - api_key: activeCampaingConf.api_key, - api_url: activeCampaingConf.api_url - } + const refreshListsRequestParams = buildAuthRequestParams(activeCampaingConf) setIsLoading(true) bitsFetch(refreshListsRequestParams, 'aCampaign_lists') .then(result => { @@ -144,10 +149,7 @@ export const refreshActiveCampaingAccounts = ( setIsLoading, setSnackbar ) => { - const refreshListsRequestParams = { - api_key: activeCampaingConf.api_key, - api_url: activeCampaingConf.api_url - } + const refreshListsRequestParams = buildAuthRequestParams(activeCampaingConf) setIsLoading(true) bitsFetch(refreshListsRequestParams, 'aCampaign_accounts') .then(result => { @@ -188,10 +190,7 @@ export const refreshActiveCampaingTags = ( setIsLoading, setSnackbar ) => { - const refreshListsRequestParams = { - api_key: activeCampaingConf.api_key, - api_url: activeCampaingConf.api_url - } + const refreshListsRequestParams = buildAuthRequestParams(activeCampaingConf) bitsFetch(refreshListsRequestParams, 'aCampaign_tags') .then(result => { if (result && result.success) { @@ -233,10 +232,7 @@ export const refreshActiveCampaingHeader = ( setIsLoading, setSnackbar ) => { - const refreshListsRequestParams = { - api_key: activeCampaingConf.api_key, - api_url: activeCampaingConf.api_url - } + const refreshListsRequestParams = buildAuthRequestParams(activeCampaingConf) setIsLoading(true) bitsFetch(refreshListsRequestParams, 'aCampaign_headers') .then(result => { diff --git a/frontend/src/components/AllIntegrations/Asana/AsanaAuthorization.jsx b/frontend/src/components/AllIntegrations/Asana/AsanaAuthorization.jsx index 8ff08352e..64f383fc1 100644 --- a/frontend/src/components/AllIntegrations/Asana/AsanaAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Asana/AsanaAuthorization.jsx @@ -1,118 +1,32 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { asanaAuthentication } from './AsanaCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' -export default function AsanaAuthorization({ - asanaConf, - setAsanaConf, - step, - setStep, - loading, - setLoading, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !asanaConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...asanaConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setAsanaConf(newConf) - } +export default function AsanaAuthorization({ asanaConf, setAsanaConf, step, setStep, isInfo }) { + const note = ` +

${__('Get API token', 'bit-integrations')}

+
    +
  • ${__('Open your Asana account settings.', 'bit-integrations')}
  • +
  • ${__('Create a personal access token.', 'bit-integrations')}
  • +
  • ${__('Use that token for this connection.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('Asana API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Asana/AsanaCommonFunc.js b/frontend/src/components/AllIntegrations/Asana/AsanaCommonFunc.js index 908b25447..b1ad6b10d 100644 --- a/frontend/src/components/AllIntegrations/Asana/AsanaCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Asana/AsanaCommonFunc.js @@ -45,43 +45,14 @@ export const checkMappedFields = asanaConf => { return true } -export const asanaAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'asana_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { api_key: confTmp.api_key } export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customFields: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action: confTmp.actionName, project_id: confTmp.selectedProject } @@ -120,7 +91,7 @@ export const getAllProjects = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Projects: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -149,7 +120,7 @@ export const getAllSections = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Sections: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), selected_project: confTmp.selectedProject } diff --git a/frontend/src/components/AllIntegrations/Bento/BentoAuthorization.jsx b/frontend/src/components/AllIntegrations/Bento/BentoAuthorization.jsx index bf85717e8..57c24db18 100644 --- a/frontend/src/components/AllIntegrations/Bento/BentoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Bento/BentoAuthorization.jsx @@ -1,148 +1,41 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { bentoAuthentication } from './BentoCommonFunc' - -export default function BentoAuthorization({ - bentoConf, - setBentoConf, - step, - setStep, - loading, - setLoading, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) -const [error, setError] = useState({ publishable_key: '', secret_key: '' }) - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !bentoConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...bentoConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setBentoConf(newConf) - } +import Authorization from '../../Connections/Authorization' + +export default function BentoAuthorization({ bentoConf, setBentoConf, step, setStep, isInfo }) { + const note = ` +

${__('To get Publishable Key, Secret Key and Site UUID', 'bit-integrations')}

+
    +
  • ${__('Open the Bento team dashboard.', 'bit-integrations')}
  • +
  • ${__('Go to Settings, then API Keys.', 'bit-integrations')}
  • +
  • ${__('Copy Publishable Key, Secret Key and Site UUID.', 'bit-integrations')}
  • +
  • ${__('Use Publishable Key as Username and Secret Key as Password.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Publishable Key:', 'bit-integrations')} -
- -
{error.publishable_key}
- -
- {__('Secret Key:', 'bit-integrations')} -
- -
{error.secret_key}
- -
- {__('Site UUID:', 'bit-integrations')} -
- -
{error.site_uuid}
- - - {__('To Get Publishable Key, Secret Key & Site UUID, Please Visit', 'bit-integrations')} -   - - {__('Bento team dashboard', 'bit-integrations')} - - -
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } - -const note = ` -

${__('To Get Publishable Key, Secret Key & Site UUID', 'bit-integrations')}

-
    -
  • ${__('Navigate to the Bento team dashboard,.', 'bit-integrations')}
  • -
  • ${__('go to "Settings" and then "API Keys"', 'bit-integrations')}
  • -
  • ${__( - "where you'll find your Publishable Key, Secret Key & Site UUID", - 'bit-integrations' - )}
  • -
` diff --git a/frontend/src/components/AllIntegrations/Bento/BentoCommonFunc.js b/frontend/src/components/AllIntegrations/Bento/BentoCommonFunc.js index fa3d1a7bd..c611de1b2 100644 --- a/frontend/src/components/AllIntegrations/Bento/BentoCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Bento/BentoCommonFunc.js @@ -44,6 +44,13 @@ export const checkMappedFields = bentoConf => { } const setRequestParams = (config, customs = {}) => { + if (config.connection_id) { + return { + ...customs, + connection_id: config.connection_id + } + } + return { ...customs, publishable_key: config.publishable_key, @@ -52,42 +59,6 @@ const setRequestParams = (config, customs = {}) => { } } -export const bentoAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.publishable_key || !confTmp.secret_key || !confTmp.site_uuid) { - setError({ - publishable_key: !confTmp.publishable_key - ? __("Publishable Key can't be empty", 'bit-integrations') - : '', - secret_key: !confTmp.secret_key ? __("Secret Key can't be empty", 'bit-integrations') : '', - site_uuid: !confTmp.site_uuid ? __("Site UUID can't be empty", 'bit-integrations') : '' - }) - - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - bitsFetch(setRequestParams(confTmp), 'bento_authentication').then(result => { - setLoading({ ...loading, auth: false }) - - if (result && result.success) { - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - - toast.error( - result?.data - ? result?.data - : __( - 'Authorized failed, Please enter valid Publishable Key, Secret Key & Site UUID', - 'bit-integrations' - ) - ) - }) -} - export const getFields = (confTmp, setConf, action, setIsLoading) => { setIsLoading(true) diff --git a/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMAuthorization.jsx index 29e7127e3..6173d334f 100644 --- a/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMAuthorization.jsx @@ -1,133 +1,47 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { capsulecrmAuthentication } from './CapsuleCRMCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' export default function CapsuleCRMAuthorization({ capsulecrmConf, setCapsuleCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', api_url: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !capsulecrmConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...capsulecrmConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCapsuleCRMConf(newConf) - } + const note = ` +

${__('Get API Token', 'bit-integrations')}

+
    +
  • ${__('Sign in to your CapsuleCRM account.', 'bit-integrations')}
  • +
  • ${__('Open My Preferences, then API Authentication Tokens.', 'bit-integrations')}
  • +
  • ${__('Create and copy your API token.', 'bit-integrations')}
  • +
  • ${__('For reference, your account domain looks like {name}.capsulecrm.com.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Your API URL:', 'bit-integrations')} -
- -
{error.api_url}
- {__('Example: {name}.capsulecrm.com', 'bit-integrations')} -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- {capsulecrmConf.api_url && ( - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('CapsuleCRM API Token', 'bit-integrations')} - - - )} -
-
- - {!isInfo && ( -
- -
- -
- )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMCommonFunc.js b/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMCommonFunc.js index fb6bdb7a5..7ddc2408f 100644 --- a/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/CapsuleCRM/CapsuleCRMCommonFunc.js @@ -51,45 +51,19 @@ export const checkMappedFields = capsulecrmConf => { return true } -export const capsulecrmAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_url || !confTmp.api_key) { - setError({ - api_url: !confTmp.api_url ? __("API URL can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } - - bitsFetch(requestParams, 'capsulecrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid api_url name & API key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + api_url: confTmp.api_url + } export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customFields: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action: confTmp.actionName } @@ -116,7 +90,7 @@ export const getCustomFields = (confTmp, setConf, setLoading) => { export const getAllOpportunities = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, opportunities: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'capsulecrm_fetch_all_opportunities').then(result => { if (result && result.success) { @@ -138,7 +112,7 @@ export const getAllOpportunities = (confTmp, setConf, setLoading) => { export const getAllOwners = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, owners: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'capsulecrm_fetch_all_owners').then(result => { if (result && result.success) { @@ -160,7 +134,7 @@ export const getAllOwners = (confTmp, setConf, setLoading) => { export const getAllTeams = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, teams: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'capsulecrm_fetch_all_teams').then(result => { if (result && result.success) { @@ -183,8 +157,7 @@ export const getAllCurrencies = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, currencies: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -209,8 +182,7 @@ export const getAllCRMParties = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMParties: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -234,10 +206,7 @@ export const getAllCRMParties = (confTmp, setConf, setLoading) => { export const getAllCRMMilestones = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMMilestones: true }) - const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'capsulecrm_fetch_all_CRMMilestones').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx index 439b1da09..aeca24194 100644 --- a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx @@ -1,132 +1,49 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { moxiecrmAuthentication } from './MoxieCRMCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function MoxieCRMAuthorization({ moxiecrmConf, setMoxieCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', api_url: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !moxiecrmConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...moxiecrmConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMoxieCRMConf(newConf) - } - - const ActiveInstructions = ` -

${__('Get api secret key', 'bit-integrations')}

-
    -
  • ${__('First go to your Moxie dashboard.', 'bit-integrations')}
  • -
  • ${__('Then click Workspace Settings from bottom left corner.', 'bit-integrations')}
  • -
  • ${__('Click "Connneted Apps", Then click "Integrations"', 'bit-integrations')}
  • -
  • ${__('Select "Custom Integrations"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get API Key', 'bit-integrations')}

+
    +
  • ${__('Go to your Moxie dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Workspace Settings from the bottom-left corner.', 'bit-integrations')}
  • +
  • ${__('Go to Connected Apps, then Integrations.', 'bit-integrations')}
  • +
  • ${__('Open Custom Integrations and copy your API key.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Your API URL:', 'bit-integrations')} -
- -
{error.api_url}
- {__('Example: {name}.withmoxie.com', 'bit-integrations')} -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMCommonFunc.js b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMCommonFunc.js index 1af1ea7a2..a2428e220 100644 --- a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMCommonFunc.js @@ -49,41 +49,13 @@ export const checkMappedFields = moxiecrmConf => { return true } -export const moxiecrmAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_url || !confTmp.api_key) { - setError({ - api_url: !confTmp.api_url ? __("API URL can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url - } - - bitsFetch(requestParams, 'moxiecrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid api_url name & API key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + api_url: confTmp.api_url + } // export const getCustomFields = (confTmp, setConf, setLoading) => { // setLoading({ ...setLoading, customFields: true }); @@ -119,10 +91,7 @@ export const moxiecrmAuthentication = ( export const getAllClients = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, clients: true }) - const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'moxiecrm_fetch_all_clients').then(result => { if (result && result.success) { @@ -145,8 +114,7 @@ export const getAllPipelineStages = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, pipelineStages: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } From 8709e3d96078ad96d7a6043e9c7747fab40e5d13 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 18:05:08 +0600 Subject: [PATCH 28/58] refactor: remove deprecated authentication methods and implement unified authorization handling for Clickup, Demio, and SuiteDash integrations --- backend/Actions/Clickup/ClickupController.php | 30 +-- backend/Actions/Clickup/Routes.php | 1 - backend/Actions/Demio/DemioController.php | 24 +-- backend/Actions/Demio/Routes.php | 1 - backend/Actions/SuiteDash/Routes.php | 1 - .../Actions/SuiteDash/SuiteDashController.php | 24 +-- .../Clickup/ClickupAuthorization.jsx | 150 +++------------ .../Clickup/ClickupCommonFunc.js | 43 +---- .../Demio/DemioAuthorization.jsx | 181 ++++++------------ .../AllIntegrations/Demio/DemioCommonFunc.js | 47 +---- .../SuiteDash/SuiteDashAuthorization.jsx | 167 ++++------------ .../SuiteDash/SuiteDashCommonFunc.js | 47 +---- 12 files changed, 168 insertions(+), 548 deletions(-) diff --git a/backend/Actions/Clickup/ClickupController.php b/backend/Actions/Clickup/ClickupController.php index 1878bf39b..3b77644fa 100644 --- a/backend/Actions/Clickup/ClickupController.php +++ b/backend/Actions/Clickup/ClickupController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Clickup; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class ClickupController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'clickup', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,27 +32,6 @@ public function __construct() $this->apiEndpoint = 'https://api.clickup.com/api/v2/'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->apiEndpoint . 'user'; - $headers = [ - 'Authorization' => $apiKey, - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->user)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/Clickup/Routes.php b/backend/Actions/Clickup/Routes.php index 3f486e49b..361318e93 100644 --- a/backend/Actions/Clickup/Routes.php +++ b/backend/Actions/Clickup/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Clickup\ClickupController; use BitApps\Integrations\Core\Util\Route; -Route::post('clickup_authentication', [ClickupController::class, 'authentication']); Route::post('clickup_fetch_custom_fields', [ClickupController::class, 'getCustomFields']); Route::post('clickup_fetch_all_tasks', [ClickupController::class, 'getAllTasks']); Route::post('clickup_fetch_all_Teams', [ClickupController::class, 'getAllTeams']); diff --git a/backend/Actions/Demio/DemioController.php b/backend/Actions/Demio/DemioController.php index c07932d91..45fcf7443 100644 --- a/backend/Actions/Demio/DemioController.php +++ b/backend/Actions/Demio/DemioController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Demio; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class DemioController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'demio', + 'fields' => [ + 'api_key' => 'value', + 'api_secret' => 'api_secret', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,20 +33,6 @@ public function __construct() $this->_apiEndpoint = 'https://my.demio.com/api/v1'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key, $fieldsRequestParams->api_secret); - $apiEndpoint = $this->_apiEndpoint . '/ping'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if ($response->pong) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API Key & API Secret', 'bit-integrations'), 400); - } - } - public function getAllEvents($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/backend/Actions/Demio/Routes.php b/backend/Actions/Demio/Routes.php index 8717e7db9..e6741f3b8 100644 --- a/backend/Actions/Demio/Routes.php +++ b/backend/Actions/Demio/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Demio\DemioController; use BitApps\Integrations\Core\Util\Route; -Route::post('demio_authentication', [DemioController::class, 'authentication']); Route::post('demio_fetch_all_events', [DemioController::class, 'getAllEvents']); Route::post('demio_fetch_all_sessions', [DemioController::class, 'getAllSessions']); diff --git a/backend/Actions/SuiteDash/Routes.php b/backend/Actions/SuiteDash/Routes.php index a0350c9a5..e114d1f5d 100644 --- a/backend/Actions/SuiteDash/Routes.php +++ b/backend/Actions/SuiteDash/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\SuiteDash\SuiteDashController; use BitApps\Integrations\Core\Util\Route; -Route::post('suite_dash_authentication', [SuiteDashController::class, 'authentication']); Route::post('suite_dash_fetch_all_fields', [SuiteDashController::class, 'getAllFields']); Route::post('suite_dash_fetch_all_companies', [SuiteDashController::class, 'getAllCompanies']); diff --git a/backend/Actions/SuiteDash/SuiteDashController.php b/backend/Actions/SuiteDash/SuiteDashController.php index fb68dd68e..0cce43f4f 100644 --- a/backend/Actions/SuiteDash/SuiteDashController.php +++ b/backend/Actions/SuiteDash/SuiteDashController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\SuiteDash; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class SuiteDashController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'suitedash', + 'fields' => [ + 'public_id' => 'value', + 'secret_key' => 'secret_key', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,20 +33,6 @@ public function __construct() $this->_apiEndpoint = 'https://app.suitedash.com/secure-api'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->public_id, $fieldsRequestParams->secret_key); - $apiEndpoint = $this->_apiEndpoint . '/contacts'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (isset($response->success) && $response->success) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Session Token or Link Name', 'bit-integrations'), 400); - } - } - public function getAllFields($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/frontend/src/components/AllIntegrations/Clickup/ClickupAuthorization.jsx b/frontend/src/components/AllIntegrations/Clickup/ClickupAuthorization.jsx index e94ab3bc8..a50ab7aa6 100644 --- a/frontend/src/components/AllIntegrations/Clickup/ClickupAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Clickup/ClickupAuthorization.jsx @@ -1,130 +1,34 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { clickupAuthentication } from './ClickupCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import Note from '../../Utilities/Note' +import Authorization from '../../Connections/Authorization' -export default function ClickupAuthorization({ - clickupConf, - setClickupConf, - step, - setStep, - loading, - setLoading, - isInfo -}) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !clickupConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...clickupConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setClickupConf(newConf) - } - - const ActiveInstructions = ` -

${__('To get the ClickUp API Key', 'bit-integrations')}

-
    -
  • ${__('Navigate to your personal Settings.', 'bit-integrations')}
  • -
  • ${__('Click Apps in the left sidebar.', 'bit-integrations')}
  • -
  • ${__('Click Generate to create your API token.', 'bit-integrations')}
  • -
  • ${__('Click Copy to copy the key to your clipboard.', 'bit-integrations')}
  • -
  • ${__('Paste your API Key into the “API Key” field.', 'bit-integrations')}
  • -
` +export default function ClickupAuthorization({ clickupConf, setClickupConf, step, setStep, isInfo }) { + const note = ` +

${__('To get the ClickUp API key', 'bit-integrations')}

+
    +
  • ${__('Open your personal Settings in ClickUp.', 'bit-integrations')}
  • +
  • ${__('Go to Apps in the left sidebar.', 'bit-integrations')}
  • +
  • ${__('Generate your API token and copy it.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('Clickup API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Clickup/ClickupCommonFunc.js b/frontend/src/components/AllIntegrations/Clickup/ClickupCommonFunc.js index a5f60cbb5..ca35171f7 100644 --- a/frontend/src/components/AllIntegrations/Clickup/ClickupCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Clickup/ClickupCommonFunc.js @@ -45,43 +45,14 @@ export const checkMappedFields = clickupConf => { return true } -export const clickupAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'clickup_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id ? { connection_id: confTmp.connection_id } : { api_key: confTmp.api_key } export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customFields: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action: confTmp.actionName, list_id: confTmp.selectedList } @@ -115,7 +86,7 @@ export const getAllTeams = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Teams: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -144,7 +115,7 @@ export const getAllSpaces = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Spaces: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName, team_id: confTmp.selectedTeam } @@ -174,7 +145,7 @@ export const getAllFolders = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Folders: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName, space_id: confTmp.selectedSpace } @@ -204,7 +175,7 @@ export const getAllLists = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, Lists: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName, folder_id: confTmp.selectedFolder } diff --git a/frontend/src/components/AllIntegrations/Demio/DemioAuthorization.jsx b/frontend/src/components/AllIntegrations/Demio/DemioAuthorization.jsx index 943536ad4..462fd865d 100644 --- a/frontend/src/components/AllIntegrations/Demio/DemioAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Demio/DemioAuthorization.jsx @@ -1,142 +1,73 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { demioAuthentication, getAllEvents } from './DemioCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { getAllEvents } from './DemioCommonFunc' export default function DemioAuthorization({ demioConf, setDemioConf, step, setStep, - loading, setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) -const [error, setError] = useState({ api_key: '', api_secret: '' }) - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !demioConf?.default - setStep(2) - getAllEvents(demioConf, setDemioConf, setLoading) - } + const loadEvents = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...demioConf, connection_id: connectionId } : demioConf + getAllEvents(nextConf, setDemioConf, setLoading) + }, + [demioConf, setDemioConf, setLoading] + ) - const handleInput = e => { - const newConf = { ...demioConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setDemioConf(newConf) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !(demioConf?.events || []).length) { + loadEvents() + } + setStep(value) + }, + [demioConf, loadEvents, setStep] + ) - const ActiveInstructions = ` -

${__('To Get API Key & API Secret', 'bit-integrations')}

-
    -
  • ${__('First go to your Demio dashboard.', 'bit-integrations')}
  • -
  • ${__('Click go to "Settings" from Right Top corner', 'bit-integrations')}
  • -
  • ${__('Then Click "API" from the "Settings Menu"', 'bit-integrations')}
  • -
  • ${__('Then Click "Generate Api Secret"', 'bit-integrations')}
  • -
  • ${__('Then copy "API Authorization Credentials"', 'bit-integrations')}
  • -
` + const note = ` +

${__('To get API Key and API Secret', 'bit-integrations')}

+
    +
  • ${__('Open your Demio dashboard.', 'bit-integrations')}
  • +
  • ${__('Go to Settings, then API.', 'bit-integrations')}
  • +
  • ${__('Generate API secret and copy credentials.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- -
- {__('API Secret:', 'bit-integrations')} -
- -
{error.api_secret}
- - - {__('To Get API Key & API Secret, Please Visit', 'bit-integrations')} -   - - {__('Demio API Key & Secret', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Demio/DemioCommonFunc.js b/frontend/src/components/AllIntegrations/Demio/DemioCommonFunc.js index caeb92369..2f7c551dc 100644 --- a/frontend/src/components/AllIntegrations/Demio/DemioCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Demio/DemioCommonFunc.js @@ -41,49 +41,15 @@ export const checkMappedFields = demioConf => { return true } -export const demioAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key || !confTmp.api_secret) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '', - api_secret: !confTmp.api_secret ? __("API Secret can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } - - bitsFetch(requestParams, 'demio_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid Sub Domain & API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { api_key: confTmp.api_key, api_secret: confTmp.api_secret } export const getAllEvents = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, event: true }) - const requestParams = { - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'demio_fetch_all_events').then(result => { if (result && result.success) { @@ -110,8 +76,7 @@ export const getAllSessions = (confTmp, setConf, event_id, setLoading) => { setLoading({ ...setLoading, session: true }) const requestParams = { - api_key: confTmp.api_key, - api_secret: confTmp.api_secret, + ...buildAuthRequestParams(confTmp), event_id: event_id } diff --git a/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashAuthorization.jsx b/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashAuthorization.jsx index 286b822e7..250a860bd 100644 --- a/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashAuthorization.jsx @@ -1,144 +1,51 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { suiteDashAuthentication } from './SuiteDashCommonFunc' -import Note from '../../Utilities/Note' -import { toast } from 'react-hot-toast' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function SuiteDashAuthorization({ suiteDashConf, setSuiteDashConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ session_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !suiteDashConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...suiteDashConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSuiteDashConf(newConf) - } - - const ActiveInstructions = ` -

${__('To Get Public Id & Secret Key', 'bit-integrations')}

-
    -
  • ${__('First go to your SuiteDash dashboard.', 'bit-integrations')}
  • -
  • ${__('Click go to your "Profile" from Right top corner', 'bit-integrations')}
  • -
  • ${__('Then Click "Integrations"', 'bit-integrations')}
  • -
  • ${__('Then Click "Secure Api"', 'bit-integrations')}
  • -
  • ${__('Then copy "API Authorization Credentials"', 'bit-integrations')}
  • -
` + const note = ` +

${__('To get Public ID and Secret Key', 'bit-integrations')}

+
    +
  • ${__('Open your SuiteDash dashboard.', 'bit-integrations')}
  • +
  • ${__('Go to Profile, then Integrations.', 'bit-integrations')}
  • +
  • ${__('Open Secure API and copy credentials.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Public Id:', 'bit-integrations')} -
- -
{error.public_id}
- -
- {__('Secret Key:', 'bit-integrations')} -
- -
{error.secret_key}
- - - {__('To Get Public Id & Secret Key, Please Visit', 'bit-integrations')} -   - - {__('SuiteDash Public Id & Secret Key', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashCommonFunc.js b/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashCommonFunc.js index 898b3b868..1c2f11c0c 100644 --- a/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SuiteDash/SuiteDashCommonFunc.js @@ -18,8 +18,7 @@ export const handleInput = (e, salesmateConf, setSalesmateConf) => { export const refreshSuiteDashFields = (suiteDashConf, setSuiteDashConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - public_id: suiteDashConf.public_id, - secret_key: suiteDashConf.secret_key, + ...buildAuthRequestParams(suiteDashConf), action_name: suiteDashConf.actionName } @@ -81,49 +80,15 @@ export const checkMappedFields = suiteDashConf => { return true } -export const suiteDashAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.public_id || !confTmp.secret_key) { - setError({ - public_id: !confTmp.public_id ? __("Public Id can't be empty", 'bit-integrations') : '', - secret_key: !confTmp.secret_key ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - public_id: confTmp.public_id, - secret_key: confTmp.secret_key - } - - bitsFetch(requestParams, 'suite_dash_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid Public Id & Secret Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { public_id: confTmp.public_id, secret_key: confTmp.secret_key } export const getAllCompanies = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, companies: true }) - const requestParams = { - public_id: confTmp.public_id, - secret_key: confTmp.secret_key - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'suite_dash_fetch_all_companies').then(result => { if (result && result.success) { From 118a7b3cfbff71def9d30466b5fa5e3b8ef26f3e Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 18:12:45 +0600 Subject: [PATCH 29/58] refactor: implement unified authorization handling for BenchMark and ConvertKit integrations; streamline API request logic and enhance code maintainability --- .../Actions/BenchMark/BenchMarkController.php | 44 +---- backend/Actions/BenchMark/Routes.php | 1 - backend/Actions/BitForm/BitFormController.php | 39 +--- backend/Actions/BitForm/Routes.php | 1 - .../ConvertKit/ConvertKitController.php | 41 +--- backend/Actions/ConvertKit/Routes.php | 1 - .../BenchMark/BenchMarkAuthorization.jsx | 177 ++++------------- .../BenchMark/BenchMarkCommonFunc.js | 8 +- .../BitForm/BitFormAuthorization.jsx | 157 ++++++--------- .../BitForm/BitFormCommonFunc.js | 39 +--- .../ConvertKit/ConvertKitAuthorization.jsx | 179 ++++-------------- .../ConvertKit/ConvertKitCommonFunc.js | 9 +- 12 files changed, 179 insertions(+), 517 deletions(-) diff --git a/backend/Actions/BenchMark/BenchMarkController.php b/backend/Actions/BenchMark/BenchMarkController.php index 2baec9847..c74f7c498 100644 --- a/backend/Actions/BenchMark/BenchMarkController.php +++ b/backend/Actions/BenchMark/BenchMarkController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\BenchMark; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class BenchMarkController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'benchmark', + 'fields' => [ + 'api_secret' => 'value', + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -26,41 +35,6 @@ public static function _apiEndpoint($method) return "https://clientapi.benchmarkemail.com/{$method}"; } - /** - * Process ajax request - * - * @param $requestsParams Params to authorize - * - * @return JSON Benchmark api response and status - */ - public static function benchMarkAuthorize($requestsParams) - { - if (empty($requestsParams->api_secret) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::_apiEndpoint('Client/'); - - $authorizationHeader['AuthToken'] = $requestsParams->api_secret; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || empty($apiResponse)) { - wp_send_json_error( - empty($apiResponse) ? 'Unknown' : $apiResponse, - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh Lists * diff --git a/backend/Actions/BenchMark/Routes.php b/backend/Actions/BenchMark/Routes.php index aa1b75edf..996408d8f 100644 --- a/backend/Actions/BenchMark/Routes.php +++ b/backend/Actions/BenchMark/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\BenchMark\BenchMarkController; use BitApps\Integrations\Core\Util\Route; -Route::post('benchMark_authorize', [BenchMarkController::class, 'benchMarkAuthorize']); Route::post('benchMark_headers', [BenchMarkController::class, 'benchMarkHeaders']); Route::post('benchMark_lists', [BenchMarkController::class, 'benchMarkLists']); diff --git a/backend/Actions/BitForm/BitFormController.php b/backend/Actions/BitForm/BitFormController.php index 3a2034e0a..331b551a8 100644 --- a/backend/Actions/BitForm/BitFormController.php +++ b/backend/Actions/BitForm/BitFormController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\BitForm; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,36 +15,14 @@ */ class BitFormController { - public function bitFormAuthorization($requestParams) - { - if ( - empty($requestParams->app_domain) - || empty($requestParams->api_key) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $authorizationHeader = [ - 'Bitform-Api-Key' => $requestParams->api_key - ]; - - $apiEndpoint = $requestParams->app_domain . '/wp-json/bitform/v1/forms'; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader, ['sslverify' => false]); - - if ($apiResponse->success) { - wp_send_json_success($apiResponse, 200); - } else { - wp_send_json_error( - 'There is an error .', - 400 - ); - } - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'bitform', + 'fields' => [ + 'api_key' => 'value', + 'domainName' => 'domainName', + ], + ]; public function bitFormAllFormList($requestParams) { diff --git a/backend/Actions/BitForm/Routes.php b/backend/Actions/BitForm/Routes.php index 027e8ac7d..50d68f110 100644 --- a/backend/Actions/BitForm/Routes.php +++ b/backend/Actions/BitForm/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\BitForm\BitFormController; use BitApps\Integrations\Core\Util\Route; -Route::post('bitForm_authorization_and_fetch_form_list', [BitFormController::class, 'bitFormAuthorization']); Route::post('bitForm_all_form_list', [BitFormController::class, 'bitFormAllFormList']); Route::post('bitForm_fetch_single_form_fields', [BitFormController::class, 'bitFormFetchSingleFormFields']); diff --git a/backend/Actions/ConvertKit/ConvertKitController.php b/backend/Actions/ConvertKit/ConvertKitController.php index 545a6319c..78b8e93c1 100644 --- a/backend/Actions/ConvertKit/ConvertKitController.php +++ b/backend/Actions/ConvertKit/ConvertKitController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ConvertKit; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class ConvertKitController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'convertkit', + 'fields' => [ + 'api_secret' => 'value', + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -26,38 +35,6 @@ public static function _apiEndpoint($method, $apiSecret) return "https://api.convertkit.com/v3/{$method}?api_secret={$apiSecret}"; } - /** - * Process ajax request - * - * @param $requestsParams Params to authorize - * - * @return JSON Convert Kit api response and status - */ - public static function convertKitAuthorize($requestsParams) - { - if (empty($requestsParams->api_secret)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::_apiEndpoint('account', $requestsParams->api_secret); - $apiResponse = HttpHelper::get($apiEndpoint, null); - - if (is_wp_error($apiResponse) || empty($apiResponse) || !empty($apiResponse->error) || empty($apiResponse->primary_email_address)) { - wp_send_json_error( - !empty($apiResponse->error) ? $apiResponse->message : 'Unknown', - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh Forms * diff --git a/backend/Actions/ConvertKit/Routes.php b/backend/Actions/ConvertKit/Routes.php index b49f71dbf..fdaf8cd39 100644 --- a/backend/Actions/ConvertKit/Routes.php +++ b/backend/Actions/ConvertKit/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ConvertKit\ConvertKitController; use BitApps\Integrations\Core\Util\Route; -Route::post('convertKit_authorize', [ConvertKitController::class, 'convertKitAuthorize']); Route::post('convertKit_headers', [ConvertKitController::class, 'convertKitHeaders']); Route::post('convertKit_forms', [ConvertKitController::class, 'convertKitForms']); Route::post('convertKit_tags', [ConvertKitController::class, 'convertKitTags']); diff --git a/frontend/src/components/AllIntegrations/BenchMark/BenchMarkAuthorization.jsx b/frontend/src/components/AllIntegrations/BenchMark/BenchMarkAuthorization.jsx index 2be98e62d..1aea2814e 100644 --- a/frontend/src/components/AllIntegrations/BenchMark/BenchMarkAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/BenchMark/BenchMarkAuthorization.jsx @@ -1,71 +1,39 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { refreshBenchMarkList, refreshBenchMarkHeader } from './BenchMarkCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshBenchMarkList, refreshBenchMarkHeader } from './BenchMarkCommonFunc' export default function BenchMarkAuthorization({ - formID, benchMarkConf, setBenchMarkConf, step, setstep, setSnackbar, isInfo, - isLoading, setIsLoading }) { -const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_secret: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const handleAuthorize = () => { - const newConf = { ...benchMarkConf } - if (!newConf.name || !newConf.api_secret) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_secret: !newConf.api_secret - ? __("Access API Secret Key can't be empty", 'bit-integrations') - : '' - }) - return - } - setIsLoading('auth') - const data = { - api_secret: newConf.api_secret - } - bitsFetch(data, 'benchMark_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...benchMarkConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setBenchMarkConf(newConf) - } + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId ? { ...benchMarkConf, connection_id: connectionId } : benchMarkConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + refreshBenchMarkList(nextConf, setBenchMarkConf, setIsLoading, setSnackbar) + }, + [benchMarkConf, setBenchMarkConf, setIsLoading, setSnackbar] + ) - refreshBenchMarkList(benchMarkConf, setBenchMarkConf, setIsLoading, setSnackbar) - setstep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !benchMarkConf?.default?.benchMarkLists) { + loadLists() + } + setstep(value) + }, + [benchMarkConf, loadLists, setstep] + ) - const ActiveInstructions = ` + const note = `

${__('Get api secret key', 'bit-integrations')}

  • ${__('First go to your BenchMark dashboard.', 'bit-integrations')}
  • @@ -73,90 +41,23 @@ const [isAuthorized, setisAuthorized] = useState(false)
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access API Secret Key:', 'bit-integrations')} -
- -
{error.api_secret}
- - - {__('To Get API Secret Key, Please Visit', 'bit-integrations')} -   - - {__('BenchMark API Token', 'bit-integrations')} - - -
-
- - {isLoading === 'auth' && ( -
- - Checking API Secret Key!!! -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, API Secret key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/BenchMark/BenchMarkCommonFunc.js b/frontend/src/components/AllIntegrations/BenchMark/BenchMarkCommonFunc.js index 4419445ec..2065ee065 100644 --- a/frontend/src/components/AllIntegrations/BenchMark/BenchMarkCommonFunc.js +++ b/frontend/src/components/AllIntegrations/BenchMark/BenchMarkCommonFunc.js @@ -7,10 +7,14 @@ export const handleInput = (e, benchMarkConf, setBenchMarkConf) => { newConf.name = e.target.value setBenchMarkConf({ ...newConf }) } + +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_secret: conf.api_secret } + // refreshMappedLists export const refreshBenchMarkList = (benchMarkConf, setBenchMarkConf, setIsLoading, setSnackbar) => { const refreshListsRequestParams = { - api_secret: benchMarkConf.api_secret + ...buildAuthRequestParams(benchMarkConf) } bitsFetch(refreshListsRequestParams, 'benchMark_lists') .then(result => { @@ -49,7 +53,7 @@ export const refreshBenchMarkList = (benchMarkConf, setBenchMarkConf, setIsLoadi // refreshMappedFields export const refreshBenchMarkHeader = (benchMarkConf, setBenchMarkConf, setIsLoading, setSnackbar) => { const refreshListsRequestParams = { - api_secret: benchMarkConf.api_secret, + ...buildAuthRequestParams(benchMarkConf), list_id: benchMarkConf.listId } bitsFetch(refreshListsRequestParams, 'benchMark_headers') diff --git a/frontend/src/components/AllIntegrations/BitForm/BitFormAuthorization.jsx b/frontend/src/components/AllIntegrations/BitForm/BitFormAuthorization.jsx index e722eecae..99791c749 100644 --- a/frontend/src/components/AllIntegrations/BitForm/BitFormAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/BitForm/BitFormAuthorization.jsx @@ -1,121 +1,72 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { fetchAllForm, handleAuthorize } from './BitFormCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchAllForm } from './BitFormCommonFunc' export default function BitFormAuthorization({ - formID, bitFormConf, setBitFormConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) -const [error, setError] = useState({ dataCenter: '', api_key: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadForms = useCallback( + connectionId => { + const nextConf = connectionId ? { ...bitFormConf, connection_id: connectionId } : bitFormConf - setstep(2) - fetchAllForm(bitFormConf, setBitFormConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...bitFormConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setBitFormConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + fetchAllForm(nextConf, setBitFormConf, setIsLoading, setSnackbar) + }, + [bitFormConf, setBitFormConf, setIsLoading, setSnackbar] + ) -
- {__('Your Domain Name:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !bitFormConf?.default?.forms) { + loadForms() + } + setstep(value) + }, + [bitFormConf, loadForms, setstep] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.api_key}
+ const note = ` +

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

+
    +
  • ${__('Open your Bit Form WordPress dashboard.', 'bit-integrations')}
  • +
  • ${__('Go to Integrations and copy your Client ID (API key).', 'bit-integrations')}
  • +
` -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/BitForm/BitFormCommonFunc.js b/frontend/src/components/AllIntegrations/BitForm/BitFormCommonFunc.js index 0019b7bf3..49655b6b4 100644 --- a/frontend/src/components/AllIntegrations/BitForm/BitFormCommonFunc.js +++ b/frontend/src/components/AllIntegrations/BitForm/BitFormCommonFunc.js @@ -44,40 +44,14 @@ export const checkAddressFieldMapRequired = sheetConf => { return true } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.api_key) { - setError({ api_key: !confTmp.api_key ? __("Api Key can't be empty", 'bit-integrations') : '' }) - return - } - setError({}) - setIsLoading(true) - - const requestParams = { app_domain: confTmp.domainName, api_key: confTmp.api_key } - - bitsFetch(requestParams, 'bitForm_authorization_and_fetch_form_list').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setIsLoading(false) - toast.success(__('Authorization Successful', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__('Authorization Failed', 'bit-integrations')) - }) -} +const buildAuthRequestParams = conf => + conf.connection_id + ? { connection_id: conf.connection_id } + : { app_domain: conf.domainName, api_key: conf.api_key } export const fetchAllForm = (bitFormConf, setBitFormConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { app_domain: bitFormConf.domainName, api_key: bitFormConf.api_key } + const requestParams = buildAuthRequestParams(bitFormConf) bitsFetch(requestParams, 'bitForm_all_form_list') .then(result => { @@ -103,8 +77,7 @@ export const fetchAllForm = (bitFormConf, setBitFormConf, setIsLoading, setSnack const fetchSingleFormFeilds = (formID, bitFormConf, setBitFormConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - app_domain: bitFormConf.domainName, - api_key: bitFormConf.api_key, + ...buildAuthRequestParams(bitFormConf), id: bitFormConf.id } diff --git a/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitAuthorization.jsx b/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitAuthorization.jsx index 02967242e..493f9b5ee 100644 --- a/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitAuthorization.jsx @@ -1,71 +1,41 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __, sprintf } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { refreshConvertKitForm } from './ConvertKitCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshConvertKitForm } from './ConvertKitCommonFunc' export default function ConvertKitAuthorization({ - formID, convertKitConf, setConvertKitConf, step, setstep, setSnackbar, isInfo, - isLoading, setIsLoading }) { -const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_secret: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const handleAuthorize = () => { - const newConf = { ...convertKitConf } - if (!newConf.name || !newConf.api_secret) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_secret: !newConf.api_secret - ? __("Access API Secret Key can't be empty", 'bit-integrations') - : '' - }) - return - } - setIsLoading('auth') - const data = { - api_secret: newConf.api_secret - } - bitsFetch(data, 'convertKit_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...convertKitConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setConvertKitConf(newConf) - } + const loadForms = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...convertKitConf, connection_id: connectionId } + : convertKitConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + refreshConvertKitForm(nextConf, setConvertKitConf, setIsLoading, setSnackbar) + }, + [convertKitConf, setConvertKitConf, setIsLoading, setSnackbar] + ) - refreshConvertKitForm(convertKitConf, setConvertKitConf, setIsLoading, setSnackbar) - setstep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !convertKitConf?.default?.convertKitForms) { + loadForms() + } + setstep(value) + }, + [convertKitConf, loadForms, setstep] + ) - const ActiveInstructions = ` + const note = `

${__('Get api secret key', 'bit-integrations')}

  • ${sprintf( @@ -76,90 +46,23 @@ const [isAuthorized, setisAuthorized] = useState(false)
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access API Secret Key:', 'bit-integrations')} -
- -
{error.api_secret}
- - - {__('To Get API Secret Key, Please Visit', 'bit-integrations')} -   - - {sprintf(__('%s API Token', 'bit-integrations'), 'Kit(ConvertKit)')} - - -
-
- - {isLoading === 'auth' && ( -
- - {__('Checking API Secret Key', 'bit-integrations')}!!! -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, API Secret key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitCommonFunc.js b/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitCommonFunc.js index 75a2af68a..c2fa94eba 100644 --- a/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ConvertKit/ConvertKitCommonFunc.js @@ -9,9 +9,12 @@ export const handleInput = (e, convertKitConf, setConvertKitConf) => { setConvertKitConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_secret: conf.api_secret } + export const refreshConvertKitForm = (convertKitConf, setConvertKitConf, setIsLoading, setSnackbar) => { const refreshFormsRequestParams = { - api_secret: convertKitConf.api_secret + ...buildAuthRequestParams(convertKitConf) } bitsFetch(refreshFormsRequestParams, 'convertKit_forms') .then(result => { @@ -57,7 +60,7 @@ export const refreshConvertKitForm = (convertKitConf, setConvertKitConf, setIsLo // refreshConvertKitTags export const refreshConvertKitTags = (convertKitConf, setConvertKitConf, setIsLoading, setSnackbar) => { const refreshFormsRequestParams = { - api_secret: convertKitConf.api_secret + ...buildAuthRequestParams(convertKitConf) } bitsFetch(refreshFormsRequestParams, 'convertKit_tags') .then(result => { @@ -136,7 +139,7 @@ export const refreshConvertKitHeader = ( }) } else { const refreshFormsRequestParams = { - api_secret: convertKitConf.api_secret + ...buildAuthRequestParams(convertKitConf) } bitsFetch(refreshFormsRequestParams, 'convertKit_headers') From 3988c29d7f7ba478aa0c4c24cf081bf8c81e007c Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Mon, 11 May 2026 18:20:25 +0600 Subject: [PATCH 30/58] refactor: unify authorization handling across ElasticEmail, Encharge, and Moosend integrations; streamline API request logic and enhance code maintainability --- .../ElasticEmail/ElasticEmailController.php | 39 ++--- backend/Actions/ElasticEmail/Routes.php | 1 - .../Actions/Encharge/EnchargeController.php | 43 +---- backend/Actions/Encharge/Routes.php | 1 - backend/Actions/Moosend/MoosendController.php | 11 +- backend/Actions/Moosend/Routes.php | 2 +- .../ElasticEmailAuthorization.jsx | 163 +++++------------ .../ElasticEmail/ElasticEmailCommonFunc.jsx | 12 +- .../Encharge/EnchargeAuthorization.jsx | 165 +++++------------- .../Encharge/EnchargeCommonFunc.js | 5 +- .../Moosend/MoosendAuthorization.jsx | 103 +++++------ .../Moosend/MoosendCommonFunc.js | 29 +-- 12 files changed, 186 insertions(+), 388 deletions(-) diff --git a/backend/Actions/ElasticEmail/ElasticEmailController.php b/backend/Actions/ElasticEmail/ElasticEmailController.php index 3c4f04400..b0298bdf5 100644 --- a/backend/Actions/ElasticEmail/ElasticEmailController.php +++ b/backend/Actions/ElasticEmail/ElasticEmailController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ElasticEmail; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,37 +15,18 @@ */ class ElasticEmailController { - public static function elasticEmailAuthorize($requestsParams) - { - if (empty($requestsParams->api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = 'https://api.elasticemail.com/v4/lists'; - $apiKey = $requestsParams->api_key; - $header = [ - 'X-ElasticEmail-ApiKey' => $apiKey, - 'Accept' => '*/*', - ]; - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - if (is_wp_error($apiResponse) || !\is_null($apiResponse->Error)) { - wp_send_json_error( - empty($apiResponse->code) ? 'Unknown' : $apiResponse->Error, - 400 - ); - } - wp_send_json_success(true); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'elasticemail', + 'fields' => [ + 'api_key' => 'value', + ], + ]; public static function getAllLists($requestsParams) { - if (empty($requestsParams->apiKey)) { + $apiKey = $requestsParams->apiKey ?? $requestsParams->api_key ?? null; + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -55,7 +37,6 @@ public static function getAllLists($requestsParams) } $apiEndpoint = 'https://api.elasticemail.com/v4/lists'; - $apiKey = $requestsParams->apiKey; $header = [ 'X-ElasticEmail-ApiKey' => $apiKey, 'Accept' => '*/*', diff --git a/backend/Actions/ElasticEmail/Routes.php b/backend/Actions/ElasticEmail/Routes.php index bda0b5ffa..95cd71ec1 100644 --- a/backend/Actions/ElasticEmail/Routes.php +++ b/backend/Actions/ElasticEmail/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\ElasticEmail\ElasticEmailController; use BitApps\Integrations\Core\Util\Route; -Route::post('elasticemail_authorize', [ElasticEmailController::class, 'elasticEmailAuthorize']); Route::get('get_all_lists', [ElasticEmailController::class, 'getAllLists']); diff --git a/backend/Actions/Encharge/EnchargeController.php b/backend/Actions/Encharge/EnchargeController.php index eab9191e8..266626eaf 100644 --- a/backend/Actions/Encharge/EnchargeController.php +++ b/backend/Actions/Encharge/EnchargeController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Encharge; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class EnchargeController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'encharge', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + public const APIENDPOINT = 'https://api.encharge.io/v1/'; private $_integrationID; @@ -23,40 +32,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param $requestsParams Params for Auth - * - * @return JSON enchagre user Authorization - */ - public static function enChargeAuthorize($requestsParams) - { - if (empty($requestsParams->api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::APIENDPOINT . 'accounts/info'; - $authorizationHeader['Accept'] = 'application/json'; - $authorizationHeader['X-Encharge-Token'] = $requestsParams->api_key; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || isset($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->code) ? 'Unknown' : $apiResponse->error->message, - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh crm modules * diff --git a/backend/Actions/Encharge/Routes.php b/backend/Actions/Encharge/Routes.php index de991c80c..71de8a901 100644 --- a/backend/Actions/Encharge/Routes.php +++ b/backend/Actions/Encharge/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Encharge\EnchargeController; use BitApps\Integrations\Core\Util\Route; -Route::post('encharge_authorize', [EnchargeController::class, 'enChargeAuthorize']); Route::post('encharge_headers', [EnchargeController::class, 'enchargeHeaders']); diff --git a/backend/Actions/Moosend/MoosendController.php b/backend/Actions/Moosend/MoosendController.php index 304df1b33..1c11a7038 100644 --- a/backend/Actions/Moosend/MoosendController.php +++ b/backend/Actions/Moosend/MoosendController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Moosend; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,9 +15,17 @@ */ class MoosendController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'moosend', + 'fields' => [ + 'authKey' => 'value', + ], + ]; + private $baseUrl = 'https://api.moosend.com/v3/'; - public function handleAuthorize($requestParams) + public function getAllLists($requestParams) { if (empty($requestParams->authKey)) { wp_send_json_error( diff --git a/backend/Actions/Moosend/Routes.php b/backend/Actions/Moosend/Routes.php index 10bfc550d..3e0ac5708 100644 --- a/backend/Actions/Moosend/Routes.php +++ b/backend/Actions/Moosend/Routes.php @@ -7,4 +7,4 @@ use BitApps\Integrations\Actions\Moosend\MoosendController; use BitApps\Integrations\Core\Util\Route; -Route::post('moosend_handle_authorize', [MoosendController::class, 'handleAuthorize']); +Route::post('moosend_lists', [MoosendController::class, 'getAllLists']); diff --git a/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailAuthorization.jsx b/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailAuthorization.jsx index d26e69ded..13e58f875 100644 --- a/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailAuthorization.jsx @@ -1,12 +1,8 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import Authorization from '../../Connections/Authorization' import { getAllList } from './ElasticEmailCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' export default function ElasticEmailAuthorization({ @@ -16,124 +12,57 @@ export default function ElasticEmailAuthorization({ setstep, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...elasticEmailConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const data = { api_key: newConf.api_key } - bitsFetch(data, 'elasticemail_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...elasticEmailConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setElasticEmailConf(newConf) - } + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...elasticEmailConf, connection_id: connectionId } + : elasticEmailConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - !elasticEmailConf?.default && getAllList(elasticEmailConf, setElasticEmailConf, setIsLoading) - setstep(2) - } + getAllList(nextConf, setElasticEmailConf, () => {}) + }, + [elasticEmailConf, setElasticEmailConf] + ) - return ( -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !elasticEmailConf?.default?.lists) { + loadLists() + } + setstep(value) + }, + [elasticEmailConf, loadLists, setstep] + ) -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get API , Please Visit', 'bit-integrations')}{' '} + const note = ` + + ${__('To get API, please visit', 'bit-integrations')} - {__('Elastic Email API Console', 'bit-integrations')} + ${__(' Elastic Email API Console', 'bit-integrations')} - - {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} +
` - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailCommonFunc.jsx b/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailCommonFunc.jsx index 55cdd95b6..c3273e5fa 100644 --- a/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailCommonFunc.jsx +++ b/frontend/src/components/AllIntegrations/ElasticEmail/ElasticEmailCommonFunc.jsx @@ -34,9 +34,13 @@ export const generateMappedField = elasticEmailConf => { ? requiredFlds.map(field => ({ formField: '', elasticEmailField: field.key })) : [{ formField: '', elasticEmailField: '' }] } + +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { apiKey: conf.api_key } + export const getAllList = (elasticEmailConf, setElasticEmailConf, setIsLoading) => { - setIsLoading(true) - const queryParams = { apiKey: elasticEmailConf.api_key } + if (setIsLoading) setIsLoading(true) + const queryParams = buildAuthRequestParams(elasticEmailConf) const loadPostTypes = bitsFetch(null, 'get_all_lists', queryParams, 'GET').then(result => { if (result && result.success) { const newConf = { ...elasticEmailConf } @@ -45,10 +49,10 @@ export const getAllList = (elasticEmailConf, setElasticEmailConf, setIsLoading) newConf.default.lists = result.data.lists } setElasticEmailConf({ ...newConf }) - setIsLoading(false) + if (setIsLoading) setIsLoading(false) return __('List refreshed successfully', 'bit-integrations') } - setIsLoading(false) + if (setIsLoading) setIsLoading(false) return __('List refresh failed. please try again', 'bit-integrations') }) toast.promise(loadPostTypes, { diff --git a/frontend/src/components/AllIntegrations/Encharge/EnchargeAuthorization.jsx b/frontend/src/components/AllIntegrations/Encharge/EnchargeAuthorization.jsx index e9bb3fc8d..0cb903cfa 100644 --- a/frontend/src/components/AllIntegrations/Encharge/EnchargeAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Encharge/EnchargeAuthorization.jsx @@ -1,141 +1,68 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import Authorization from '../../Connections/Authorization' import { refreshEnchargeHeader } from './EnchargeCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function EnchargeAuthorization({ - formID, enchargeConf, setEnchargeConf, step, setstep, setSnackbar, - isInfo + isInfo, + setIsLoading }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...enchargeConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const data = { api_key: newConf.api_key } - bitsFetch(data, 'encharge_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...enchargeConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setEnchargeConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshEnchargeHeader(enchargeConf, setEnchargeConf, setIsLoading, setSnackbar) - } + const loadFields = useCallback( + connectionId => { + const nextConf = connectionId ? { ...enchargeConf, connection_id: connectionId } : enchargeConf - return ( -
- + refreshEnchargeHeader(nextConf, setEnchargeConf, setIsLoading, setSnackbar) + }, + [enchargeConf, setEnchargeConf, setIsLoading, setSnackbar] + ) -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && !enchargeConf?.default?.fields) { + loadFields() + } + setstep(value) + }, + [enchargeConf, loadFields, setstep] + ) -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get API , Please Visit', 'bit-integrations')}{' '} + const note = ` + + ${__('To get API, please visit', 'bit-integrations')} - {__('Encharge API Console', 'bit-integrations')} + ${__(' Encharge API Console', 'bit-integrations')} - - {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} +
` - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/Encharge/EnchargeCommonFunc.js b/frontend/src/components/AllIntegrations/Encharge/EnchargeCommonFunc.js index 303e39fa7..d8d135105 100644 --- a/frontend/src/components/AllIntegrations/Encharge/EnchargeCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Encharge/EnchargeCommonFunc.js @@ -8,8 +8,11 @@ export const handleInput = (e, enchargeConf, setEnchargeConf) => { setEnchargeConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } + export const refreshEnchargeHeader = (enchargeConf, setEnchargeConf, setIsLoading, setSnackbar) => { - const refreshEnchargeHeaderData = { api_key: enchargeConf.api_key } + const refreshEnchargeHeaderData = buildAuthRequestParams(enchargeConf) const newConf = { ...enchargeConf } bitsFetch(refreshEnchargeHeaderData, 'encharge_headers') .then(result => { diff --git a/frontend/src/components/AllIntegrations/Moosend/MoosendAuthorization.jsx b/frontend/src/components/AllIntegrations/Moosend/MoosendAuthorization.jsx index cbfc5436a..3e16a0eeb 100644 --- a/frontend/src/components/AllIntegrations/Moosend/MoosendAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Moosend/MoosendAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import AuthorizeButton from '../../Utilities/AuthorizeButton' -import ErrorField from '../../Utilities/ErrorField' -import GetInfo from '../../Utilities/GetInfo' -import Input from '../../Utilities/Input' -import Note from '../../Utilities/Note' -import StepPage from '../../Utilities/StepPage' -import { getAllLists, handleAuthorize, handleInput } from './MoosendCommonFunc' -import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' +import { getAllLists } from './MoosendCommonFunc' function MoosendAuthorization({ moosendConf, @@ -19,21 +14,28 @@ function MoosendAuthorization({ setLoading, isInfo }) { - const [authorized, setAuthorized] = useState(false) - const [error, setError] = useState({ name: '', authKey: '' }) + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...moosendConf, connection_id: connectionId } : moosendConf - const nextPage = async () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + setLoading({ ...loading, page: true }) + const loaded = await getAllLists(nextConf, setMoosendConf, loading, setLoading) + if (loaded) { + setLoading({ ...loading, page: false }) + } + }, + [moosendConf, setMoosendConf, loading, setLoading] + ) - setStep(2) - setLoading({ ...loading, page: true }) - const data = await getAllLists(moosendConf, setMoosendConf) - if (data) { - setLoading({ ...loading, page: false }) - } - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !moosendConf?.default?.lists) { + loadLists() + } + setStep(value) + }, + [moosendConf, loadLists, setStep] + ) const note = `

${__('Step of get API Key:', 'bit-integrations')}

@@ -49,43 +51,24 @@ function MoosendAuthorization({ ` return ( - - - -
- {/* Moosend Authorization */} - - handleInput(e, moosendConf, setMoosendConf, error, setError)} - /> - handleInput(e, moosendConf, setMoosendConf, error, setError)} - /> - - - {!isInfo && ( - handleAuthorize(moosendConf, setError, setAuthorized, loading, setLoading)} - nextPage={nextPage} - auth={authorized} - loading={loading.auth} - /> - )} -
- - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js b/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js index 920e62921..860d28572 100644 --- a/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js @@ -14,31 +14,20 @@ export const handleInput = (e, conf, setConf, error, setError) => { } export const handleAuthorize = (conf, setError, setAuthorized, loading, setLoading) => { - if (!conf.authKey) { - setError({ authKey: !conf.authKey ? __("API Key can't be empty") : '' }) - return - } + // Legacy local authorization has been replaced by shared Connection Authorization. + // Kept as a no-op for backward import safety in older component trees. setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { authKey: conf.authKey } - - bitsFetch(requestParams, 'moosend_handle_authorize').then(result => { - if (result.data.Code === 0) { - setAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed')) - }) + setAuthorized(true) + setLoading({ ...loading, auth: false }) } +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { authKey: conf.authKey } + export const getAllLists = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, list: true }) - const requestParams = { authKey: conf.authKey } - const result = await bitsFetch(requestParams, 'moosend_handle_authorize') + const requestParams = buildAuthRequestParams(conf) + const result = await bitsFetch(requestParams, 'moosend_lists') if (result.success && result.data.Code === 0) { const { MailingLists } = result.data.Context const newConf = { ...conf } From d83d16dd857c05e546a3b4564df191786b39bd9e Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 11:07:39 +0600 Subject: [PATCH 31/58] Refactor integration authorization components to use a unified Authorization component - Replaced individual authorization logic in SendinBlue, Telegram, Woodpecker, and ZagoMail components with a shared Authorization component. - Simplified state management and error handling by removing redundant state variables and functions. - Updated API request handling to use connection IDs where applicable. - Improved user feedback by ensuring snackbar notifications are conditionally displayed based on the success of API calls. - Enhanced code readability and maintainability by consolidating common functionalities into utility functions. --- .../CampaignMonitorController.php | 26 +- backend/Actions/CampaignMonitor/Routes.php | 1 - .../CompanyHub/CompanyHubController.php | 24 +- backend/Actions/CompanyHub/Routes.php | 1 - .../Actions/HighLevel/HighLevelController.php | 38 +-- backend/Actions/HighLevel/Routes.php | 1 - backend/Actions/Hubspot/HubspotController.php | 29 +-- backend/Actions/Hubspot/Routes.php | 1 - backend/Actions/Klaviyo/KlaviyoController.php | 11 +- backend/Actions/Klaviyo/Routes.php | 2 +- backend/Actions/Lemlist/LemlistController.php | 28 +- backend/Actions/Lemlist/Routes.php | 1 - .../Actions/Livestorm/LivestormController.php | 25 +- backend/Actions/Livestorm/Routes.php | 1 - .../Actions/OmniSend/OmniSendController.php | 38 +-- backend/Actions/OmniSend/Routes.php | 2 - .../OneHashCRM/OneHashCRMController.php | 28 +- backend/Actions/OneHashCRM/Routes.php | 2 - backend/Actions/SendinBlue/Routes.php | 1 - .../SendinBlue/SendinBlueController.php | 41 +-- backend/Actions/Telegram/Routes.php | 1 - .../Actions/Telegram/TelegramController.php | 51 +--- backend/Actions/Woodpecker/Routes.php | 1 - .../Woodpecker/WoodpeckerController.php | 24 +- backend/Actions/ZagoMail/Routes.php | 1 - .../Actions/ZagoMail/ZagoMailController.php | 48 +--- .../CampaignMonitorAuthorization.jsx | 230 +++++------------ .../CampaignMonitorCommonFunc.js | 37 +-- .../CompanyHub/CompanyHubAuthorization.jsx | 187 +++++--------- .../CompanyHub/CompanyHubCommonFunc.js | 102 +++----- .../HighLevel/HighLevelAuthorization.jsx | 240 ++++++------------ .../HighLevel/HighLevelCommonFunc.js | 88 ++----- .../Hubspot/HubspotAuthorization.jsx | 102 ++------ .../Hubspot/HubspotCommonFunc.jsx | 40 +-- .../Klaviyo/KlaviyoAuthorization.jsx | 150 ++++------- .../Klaviyo/KlaviyoCommonFunc.js | 49 +--- .../Lemlist/LemlistAuthorization.jsx | 189 +++----------- .../Lemlist/LemlistCommonFunc.js | 17 +- .../Livestorm/LivestormAuthorization.jsx | 143 +++-------- .../Livestorm/LivestormCommonFunc.js | 81 +++--- .../Livestorm/LivestormIntegLayout.jsx | 15 +- .../OmniSend/OmniSendAuthorization.jsx | 125 ++------- .../OmniSend/OmniSendCommonFunc.js | 35 --- .../OneHashCRM/OneHashCRMAuthorization.jsx | 193 ++++---------- .../OneHashCRM/OneHashCRMCommonFunc.js | 42 --- .../SendinBlue/SendinBlueAuthorization.jsx | 163 ++++-------- .../SendinBlue/SendinBlueCommonFunc.js | 42 +-- .../Telegram/TelegramAuthorization.jsx | 157 +++--------- .../Telegram/TelegramCommonFunc.js | 19 +- .../Woodpecker/WoodpeckerAuthorization.jsx | 132 +++------- .../Woodpecker/WoodpeckerCommonFunc.js | 46 ++-- .../ZagoMail/ZagoMailAuthorization.jsx | 190 ++++---------- .../ZagoMail/ZagoMailCommonFunc.js | 55 ++-- 53 files changed, 938 insertions(+), 2358 deletions(-) diff --git a/backend/Actions/CampaignMonitor/CampaignMonitorController.php b/backend/Actions/CampaignMonitor/CampaignMonitorController.php index 0043c3af5..36c1c961f 100644 --- a/backend/Actions/CampaignMonitor/CampaignMonitorController.php +++ b/backend/Actions/CampaignMonitor/CampaignMonitorController.php @@ -2,11 +2,21 @@ namespace BitApps\Integrations\Actions\CampaignMonitor; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class CampaignMonitorController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BASIC_AUTH, + 'slug' => 'campaignmonitor', + 'fields' => [ + 'api_key' => 'username', + 'client_id' => 'client_id', + ], + ]; + private $baseUrl; public function __construct() @@ -14,22 +24,6 @@ public function __construct() $this->baseUrl = 'https://api.createsend.com/api/v3.3'; } - public function authorization($requestParams) - { - $this->checkValidation($requestParams->api_key, $requestParams->client_id); - $apiEndpoint = $this->baseUrl . "/clients/{$requestParams->client_id}.json"; - $headers = $this->setHeader($requestParams->api_key); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (!isset($response->ApiKey)) { - wp_send_json_error( - empty($response) ? 'Unknown' : $response, - 400 - ); - } - wp_send_json_success(true); - } - public function getAllLists($requestParams) { $this->checkValidation($requestParams->api_key, $requestParams->client_id); diff --git a/backend/Actions/CampaignMonitor/Routes.php b/backend/Actions/CampaignMonitor/Routes.php index 4e1d3c6ae..620b11b50 100644 --- a/backend/Actions/CampaignMonitor/Routes.php +++ b/backend/Actions/CampaignMonitor/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\CampaignMonitor\CampaignMonitorController; use BitApps\Integrations\Core\Util\Route; -Route::post('campaign_monitor_authorize', [CampaignMonitorController::class, 'authorization']); Route::post('campaign_monitor_lists', [CampaignMonitorController::class, 'getAllLists']); Route::post('campaign_monitor_custom_fields', [CampaignMonitorController::class, 'getCustomFields']); diff --git a/backend/Actions/CompanyHub/CompanyHubController.php b/backend/Actions/CompanyHub/CompanyHubController.php index 223980a4f..ce0e06c1f 100644 --- a/backend/Actions/CompanyHub/CompanyHubController.php +++ b/backend/Actions/CompanyHub/CompanyHubController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\CompanyHub; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class CompanyHubController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'companyhub', + 'fields' => [ + 'api_key' => 'value', + 'sub_domain' => 'sub_domain', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,20 +33,6 @@ public function __construct() $this->_apiEndpoint = 'https://api.companyhub.com/v1'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->sub_domain, $fieldsRequestParams->api_key); - $apiEndpoint = $this->_apiEndpoint . '/me'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (!isset($response->Success) && !$response->Success) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Sub Domain & API Key', 'bit-integrations'), 400); - } - } - public function getAllCompanies($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/backend/Actions/CompanyHub/Routes.php b/backend/Actions/CompanyHub/Routes.php index a5adcbb7e..01dc1e966 100644 --- a/backend/Actions/CompanyHub/Routes.php +++ b/backend/Actions/CompanyHub/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\CompanyHub\CompanyHubController; use BitApps\Integrations\Core\Util\Route; -Route::post('company_hub_authentication', [CompanyHubController::class, 'authentication']); Route::post('company_hub_fetch_all_contacts', [CompanyHubController::class, 'getAllContacts']); Route::post('company_hub_fetch_all_companies', [CompanyHubController::class, 'getAllCompanies']); diff --git a/backend/Actions/HighLevel/HighLevelController.php b/backend/Actions/HighLevel/HighLevelController.php index 76a6aceaf..ff978edd0 100644 --- a/backend/Actions/HighLevel/HighLevelController.php +++ b/backend/Actions/HighLevel/HighLevelController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\HighLevel; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -15,6 +16,15 @@ class HighLevelController { private const V2_HEADER_VERSION = '2021-07-28'; + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'gohighlevel', + 'fields' => [ + 'api_key' => 'token', + 'version' => 'version', + 'location_id' => 'location_id', + ], + ]; private $_integrationID; @@ -23,34 +33,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - public static function highLevelAuthorization($requestsParams) - { - $apiKey = self::getApiKey($requestsParams); - $version = self::getVersion($requestsParams); - $headers = self::buildHeaders($apiKey, $version); - - if ($version === 'v2') { - $locationId = self::getLocationIdIfV2($requestsParams, $version); - $endpoint = "https://services.leadconnectorhq.com/locations/{$locationId}"; - $response = self::getOrError($endpoint, $headers); - - if (!isset($response->location) && !isset($response->id)) { - wp_send_json_error($response, 400); - } - - wp_send_json_success($response); - } - - $endpoint = 'https://rest.gohighlevel.com/v1/contacts/?limit=1'; - $response = self::getOrError($endpoint, $headers); - - if (!isset($response->contacts) || !\is_array($response->contacts)) { - wp_send_json_error($response, 400); - } - - wp_send_json_success($response); - } - public static function getCustomFields($requestsParams) { $apiKey = self::getApiKey($requestsParams); diff --git a/backend/Actions/HighLevel/Routes.php b/backend/Actions/HighLevel/Routes.php index f0a95b402..fc68b6516 100644 --- a/backend/Actions/HighLevel/Routes.php +++ b/backend/Actions/HighLevel/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\HighLevel\HighLevelController; use BitApps\Integrations\Core\Util\Route; -Route::post('highLevel_authorization', [HighLevelController::class, 'highLevelAuthorization']); Route::post('get_highLevel_contact_custom_fields', [HighLevelController::class, 'getCustomFields']); Route::post('high_level_contact_tags', [HighLevelController::class, 'getAllTags']); Route::post('get_highLevel_contacts', [HighLevelController::class, 'getContacts']); diff --git a/backend/Actions/Hubspot/HubspotController.php b/backend/Actions/Hubspot/HubspotController.php index 322d7d810..bd361c4f5 100644 --- a/backend/Actions/Hubspot/HubspotController.php +++ b/backend/Actions/Hubspot/HubspotController.php @@ -2,12 +2,21 @@ namespace BitApps\Integrations\Actions\Hubspot; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Log\LogHandler; use WP_Error; final class HubspotController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'hubspot', + 'fields' => [ + 'api_key' => 'token', + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -15,26 +24,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - public static function authorization($requestParams) - { - if (empty($requestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiEndpoint = 'https://api.hubapi.com/crm/v3/objects/contacts'; - $header = [ - 'authorization' => 'Bearer ' . $requestParams->api_key - ]; - - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (isset($apiResponse->results)) { - wp_send_json_success(__('Authorization successfull', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); - } - } - public static function getFields($requestParams) { if (empty($requestParams->api_key) || empty($requestParams->type)) { diff --git a/backend/Actions/Hubspot/Routes.php b/backend/Actions/Hubspot/Routes.php index d3be0f01c..ae979ffd9 100644 --- a/backend/Actions/Hubspot/Routes.php +++ b/backend/Actions/Hubspot/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Hubspot\HubspotController; use BitApps\Integrations\Core\Util\Route; -Route::post('hubSpot_authorization', [HubspotController::class, 'authorization']); Route::post('getFields', [HubspotController::class, 'getFields']); Route::post('hubspot_pipeline', [HubspotController::class, 'getAllPipelines']); Route::post('hubspot_owners', [HubspotController::class, 'getAllOwners']); diff --git a/backend/Actions/Klaviyo/KlaviyoController.php b/backend/Actions/Klaviyo/KlaviyoController.php index ec459743e..8c9a242c9 100644 --- a/backend/Actions/Klaviyo/KlaviyoController.php +++ b/backend/Actions/Klaviyo/KlaviyoController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Klaviyo; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,9 +15,17 @@ */ class KlaviyoController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'klaviyo', + 'fields' => [ + 'authKey' => 'value', + ], + ]; + private $baseUrl = 'https://a.klaviyo.com/api/'; - public function handleAuthorize($requestParams) + public function getAllLists($requestParams) { if (empty($requestParams->authKey)) { wp_send_json_error( diff --git a/backend/Actions/Klaviyo/Routes.php b/backend/Actions/Klaviyo/Routes.php index 33c812ece..cfe92d108 100644 --- a/backend/Actions/Klaviyo/Routes.php +++ b/backend/Actions/Klaviyo/Routes.php @@ -7,4 +7,4 @@ use BitApps\Integrations\Actions\Klaviyo\KlaviyoController; use BitApps\Integrations\Core\Util\Route; -Route::post('klaviyo_handle_authorize', [klaviyoController::class, 'handleAuthorize']); +Route::post('klaviyo_lists', [KlaviyoController::class, 'getAllLists']); diff --git a/backend/Actions/Lemlist/LemlistController.php b/backend/Actions/Lemlist/LemlistController.php index 107c7ef59..66b0bca1e 100644 --- a/backend/Actions/Lemlist/LemlistController.php +++ b/backend/Actions/Lemlist/LemlistController.php @@ -2,11 +2,20 @@ namespace BitApps\Integrations\Actions\Lemlist; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class LemlistController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'lemlist', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -14,25 +23,6 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) - { - if (empty($requestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiEndpoint = 'https://api.lemlist.com/api/team'; - $header['Authorization'] = 'Basic ' . base64_encode(":{$requestParams->api_key}"); - $response = HttpHelper::get($apiEndpoint, null, $header); - - if (!isset($response->_id)) { - wp_send_json_error( - empty($response) ? 'Unknown' : $response, - 400 - ); - } - wp_send_json_success(true); - } - public static function getAllCampaign($requestParams) { if (empty($requestParams->api_key)) { diff --git a/backend/Actions/Lemlist/Routes.php b/backend/Actions/Lemlist/Routes.php index 2ac0857b1..410e9da75 100644 --- a/backend/Actions/Lemlist/Routes.php +++ b/backend/Actions/Lemlist/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Lemlist\LemlistController; use BitApps\Integrations\Core\Util\Route; -Route::post('lemlist_authorize', [LemlistController::class, 'authorization']); Route::post('lemlist_campaigns', [LemlistController::class, 'getAllCampaign']); diff --git a/backend/Actions/Livestorm/LivestormController.php b/backend/Actions/Livestorm/LivestormController.php index c65f4d389..61dc1b211 100644 --- a/backend/Actions/Livestorm/LivestormController.php +++ b/backend/Actions/Livestorm/LivestormController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Livestorm; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class LivestormController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'livestorm', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,22 +32,6 @@ public function __construct() $this->_apiEndpoint = 'https://api.livestorm.co/v1'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key); - $apiEndpoint = $this->_apiEndpoint . '/ping'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (!\count((array) $response)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } elseif (isset($response->errors) && $response->errors[0]->title === 'Workspace blocked') { - wp_send_json_error($response->errors[0]->detail, 400); - } else { - wp_send_json_error(__('Authorized failed, Please enter valid API Key', 'bit-integrations'), 400); - } - } - public function getAllEvents($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/backend/Actions/Livestorm/Routes.php b/backend/Actions/Livestorm/Routes.php index 6e1e90a4e..d6e06912e 100644 --- a/backend/Actions/Livestorm/Routes.php +++ b/backend/Actions/Livestorm/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Livestorm\LivestormController; use BitApps\Integrations\Core\Util\Route; -Route::post('livestorm_authentication', [LivestormController::class, 'authentication']); Route::post('livestorm_fetch_all_events', [LivestormController::class, 'getAllEvents']); Route::post('livestorm_fetch_all_sessions', [LivestormController::class, 'getAllSessions']); diff --git a/backend/Actions/OmniSend/OmniSendController.php b/backend/Actions/OmniSend/OmniSendController.php index b0b871841..2a07550db 100644 --- a/backend/Actions/OmniSend/OmniSendController.php +++ b/backend/Actions/OmniSend/OmniSendController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\OmniSend; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,39 +15,18 @@ */ class OmniSendController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'omnisend', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; private $baseUrl = 'https://api.omnisend.com/v3/'; - public function authorization($requestParams) - { - if (empty($requestParams->api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoints = $this->baseUrl . 'contacts'; - - $header = [ - 'X-API-KEY' => $requestParams->api_key, - ]; - - $response = HttpHelper::get($apiEndpoints, null, $header); - if (isset($response->contacts)) { - wp_send_json_success('', 200); - } else { - wp_send_json_error( - 'The token is invalid', - 400 - ); - } - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/OmniSend/Routes.php b/backend/Actions/OmniSend/Routes.php index 9d121d959..026fbbb69 100644 --- a/backend/Actions/OmniSend/Routes.php +++ b/backend/Actions/OmniSend/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\OmniSend\OmniSendController; use BitApps\Integrations\Core\Util\Route; - -Route::post('Omnisend_authorization', [OmniSendController::class, 'authorization']); diff --git a/backend/Actions/OneHashCRM/OneHashCRMController.php b/backend/Actions/OneHashCRM/OneHashCRMController.php index d7285d08b..1152fec3e 100644 --- a/backend/Actions/OneHashCRM/OneHashCRMController.php +++ b/backend/Actions/OneHashCRM/OneHashCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\OneHashCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,29 +15,22 @@ */ class OneHashCRMController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'onehashcrm', + 'fields' => [ + 'api_key' => 'value', + 'api_secret' => 'api_secret', + 'domain' => 'domain', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; protected $domain; - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->domain = $fieldsRequestParams->domain; - $apiKey = $fieldsRequestParams->api_key; - $apiSecret = $fieldsRequestParams->api_secret; - $apiEndpoint = $this->setApiEndpoint() . '/Lead'; - $headers = $this->setHeaders($apiKey, $apiSecret); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->data)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API Key & Secret or Access Api URL', 'bit-integrations'), 400); - } - } - public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; diff --git a/backend/Actions/OneHashCRM/Routes.php b/backend/Actions/OneHashCRM/Routes.php index 3cacbe6e3..b8636c828 100644 --- a/backend/Actions/OneHashCRM/Routes.php +++ b/backend/Actions/OneHashCRM/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\OneHashCRM\OneHashCRMController; use BitApps\Integrations\Core\Util\Route; - -Route::post('onehashcrm_authentication', [OneHashCRMController::class, 'authentication']); diff --git a/backend/Actions/SendinBlue/Routes.php b/backend/Actions/SendinBlue/Routes.php index 66a3c1534..781737742 100644 --- a/backend/Actions/SendinBlue/Routes.php +++ b/backend/Actions/SendinBlue/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\SendinBlue\SendinBlueController; use BitApps\Integrations\Core\Util\Route; -Route::post('sblue_authorize', [SendinBlueController::class, 'sendinBlueAuthorize']); Route::post('sblue_refresh_lists', [SendinBlueController::class, 'refreshlists']); Route::post('sblue_headers', [SendinBlueController::class, 'sendinblueHeaders']); Route::post('sblue_refresh_template', [SendinBlueController::class, 'refreshTemplate']); diff --git a/backend/Actions/SendinBlue/SendinBlueController.php b/backend/Actions/SendinBlue/SendinBlueController.php index 4b5476a94..95225d236 100644 --- a/backend/Actions/SendinBlue/SendinBlueController.php +++ b/backend/Actions/SendinBlue/SendinBlueController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\SendinBlue; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -16,39 +17,13 @@ class SendinBlueController { public const APIENDPOINT = 'https://api.sendinblue.com/v3'; - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to Authorize - * - * @return JSON zoho crm api response and status - */ - public static function sendinBlueAuthorize($requestsParams) - { - if (empty($requestsParams->api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::APIENDPOINT . '/account'; - $authorizationHeader['Accept'] = 'application/json'; - $authorizationHeader['api-key'] = $requestsParams->api_key; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || $apiResponse->code === 'unauthorized') { - wp_send_json_error( - empty($apiResponse->code) ? 'Unknown' : $apiResponse->message, - 400 - ); - } - - wp_send_json_success(true); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'sendinblue', + 'fields' => [ + 'api_key' => 'value', + ], + ]; /** * Process ajax request for refresh crm modules diff --git a/backend/Actions/Telegram/Routes.php b/backend/Actions/Telegram/Routes.php index cf9541976..b65b55009 100644 --- a/backend/Actions/Telegram/Routes.php +++ b/backend/Actions/Telegram/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Telegram\TelegramController; use BitApps\Integrations\Core\Util\Route; -Route::post('telegram_authorize', [TelegramController::class, 'telegramAuthorize']); Route::post('refresh_get_updates', [TelegramController::class, 'refreshGetUpdates']); diff --git a/backend/Actions/Telegram/TelegramController.php b/backend/Actions/Telegram/TelegramController.php index 91274eaba..acba059e8 100644 --- a/backend/Actions/Telegram/TelegramController.php +++ b/backend/Actions/Telegram/TelegramController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Telegram; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -15,6 +16,13 @@ class TelegramController { public const APIENDPOINT = 'https://api.telegram.org/bot'; + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'telegram', + 'fields' => [ + 'bot_api_key' => 'value', + ], + ]; private $_integrationID; @@ -23,49 +31,6 @@ class TelegramController // $this->_integrationID = $integrationID; // } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to authorize - * - * @return JSON zoho crm api response and status - */ - public static function telegramAuthorize($requestsParams) - { - if (empty($requestsParams->bot_api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::APIENDPOINT . $requestsParams->bot_api_key . '/getMe'; - $authorizationHeader['Accept'] = 'application/x-www-form-urlencoded'; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || !$apiResponse->ok) { - wp_send_json_error( - empty($apiResponse->error_code) ? 'Unknown' : $apiResponse, - 400 - ); - } - $apiEndpoint = self::APIENDPOINT . $requestsParams->bot_api_key . '/getUpdates'; - $authorizationHeader['Accept'] = 'application/x-www-form-urlencoded'; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || !$apiResponse->ok) { - wp_send_json_error( - empty($apiResponse->error_code) ? 'Unknown' : $apiResponse, - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh telegram get Updates * diff --git a/backend/Actions/Woodpecker/Routes.php b/backend/Actions/Woodpecker/Routes.php index 48374cfe2..ab898e566 100644 --- a/backend/Actions/Woodpecker/Routes.php +++ b/backend/Actions/Woodpecker/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Woodpecker\WoodpeckerController; use BitApps\Integrations\Core\Util\Route; -Route::post('woodpecker_authentication', [WoodpeckerController::class, 'authentication']); Route::post('woodpecker_fetch_all_campaigns', [WoodpeckerController::class, 'getAllCampagns']); diff --git a/backend/Actions/Woodpecker/WoodpeckerController.php b/backend/Actions/Woodpecker/WoodpeckerController.php index be70ec9ee..9327680a8 100644 --- a/backend/Actions/Woodpecker/WoodpeckerController.php +++ b/backend/Actions/Woodpecker/WoodpeckerController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Woodpecker; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,27 +15,20 @@ */ class WoodpeckerController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'woodpecker', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; protected $domain; - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->setApiEndpoint() . '/campaign_list'; - $headers = $this->setHeaders(base64_encode($apiKey)); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->status) && $response->status->status === 'ERROR') { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } else { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } - } - public function getAllCampagns($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/backend/Actions/ZagoMail/Routes.php b/backend/Actions/ZagoMail/Routes.php index 1d4fe59cb..e493fb4ea 100644 --- a/backend/Actions/ZagoMail/Routes.php +++ b/backend/Actions/ZagoMail/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ZagoMail\ZagoMailController; use BitApps\Integrations\Core\Util\Route; -Route::post('zagoMail_authorize', [ZagoMailController::class, 'zagoMailAuthorize']); Route::post('zagoMail_refresh_fields', [ZagoMailController::class, 'zagoMailRefreshFields']); Route::post('zagoMail_lists', [ZagoMailController::class, 'zagoMailLists']); Route::post('zagoMail_tags', [ZagoMailController::class, 'zagoMailTags']); diff --git a/backend/Actions/ZagoMail/ZagoMailController.php b/backend/Actions/ZagoMail/ZagoMailController.php index 4af28533a..bc906a2dd 100644 --- a/backend/Actions/ZagoMail/ZagoMailController.php +++ b/backend/Actions/ZagoMail/ZagoMailController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZagoMail; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class ZagoMailController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'zagomail', + 'fields' => [ + 'api_public_key' => 'value', + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -26,45 +35,6 @@ public static function _apiEndpoint($method) return "https://api.zagomail.com/{$method}"; } - /** - * Process ajax request - * - * @param $requestsParams Params to authorize - * - * @return JSON ZagoMail api response and status - */ - public static function zagoMailAuthorize($requestsParams) - { - if (empty($requestsParams->api_public_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $body = [ - 'publicKey' => $requestsParams->api_public_key - ]; - - $header['Content-Type'] = 'application/json'; - - $apiEndpoint = self::_apiEndpoint('lists/all-lists'); - - $apiResponse = HttpHelper::post($apiEndpoint, wp_json_encode($body), $header); - - if ($apiResponse->status == 'error' || $apiResponse->status !== 'success') { - wp_send_json_error( - empty($apiResponse) ? 'Unknown' : $apiResponse, - 400 - ); - } - - wp_send_json_success(true); - } - /** * Process ajax request for refresh Lists * diff --git a/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorAuthorization.jsx b/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorAuthorization.jsx index 605ae69f9..2ab941122 100644 --- a/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorAuthorization.jsx @@ -1,13 +1,9 @@ -import { useState } from 'react' -import { toast } from 'react-hot-toast' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' +import Authorization from '../../Connections/Authorization' import { refreshCampaignMonitorLists } from './CampaignMonitorCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function CampaignMonitorAuthorization({ campaignMonitorConf, @@ -15,179 +11,73 @@ export default function CampaignMonitorAuthorization({ step, setstep, setSnackbar, - isInfo, - isLoading, - setIsLoading + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const handleAuthorize = () => { - const newConf = { ...campaignMonitorConf } - if (!newConf.name || !newConf.client_id || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - client_id: !newConf.client_id ? __("Client Id can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("Access Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...campaignMonitorConf, connection_id: connectionId } + : campaignMonitorConf - const data = { - api_key: newConf.api_key, - client_id: newConf.client_id - } + refreshCampaignMonitorLists(nextConf, setCampaignMonitorConf, () => {}, setSnackbar) + }, + [campaignMonitorConf, setCampaignMonitorConf, setSnackbar] + ) - bitsFetch(data, 'campaign_monitor_authorize').then(result => { - if (result && result.success) { - const newConf = { ...campaignMonitorConf } - newConf.tokenDetails = result.data - setCampaignMonitorConf(newConf) - setisAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data) || - (!result.success && typeof result.data.Message === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${result.data.Message}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) + const handleSetStep = useCallback( + value => { + if (value === 2 && !campaignMonitorConf?.default?.campaignMonitorLists) { + loadLists() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...campaignMonitorConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCampaignMonitorConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - refreshCampaignMonitorLists(campaignMonitorConf, setCampaignMonitorConf, setIsLoading, setSnackbar) - setstep(2) - } - - const ActiveInstructions = ` -

${__('Get Client Id & Api key', 'bit-integrations')}

-
    -
  • ${__('First go to your CampaignMonitor dashboard.', 'bit-integrations')}
  • -
  • ${__('Click on Your "Profile Image" at the top right', 'bit-integrations')}
  • -
  • ${__('Click on the "Account Settings"', 'bit-integrations')}
  • -
  • ${__('Then Click "API keys"', 'bit-integrations')}
  • -
` - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Client id:', 'bit-integrations')} -
- -
{error.client_id}
- -
- {__('Access API Key:', 'bit-integrations')} -
- -
{error.api_key}
+ setstep(value) + }, + [campaignMonitorConf, loadLists, setstep] + ) - - {__('To Get Client Id & Api Key, Please Visit', 'bit-integrations')} -   + const note = ` +

${__('Get Client Id & Api key', 'bit-integrations')}

+
    +
  • ${__('First go to your CampaignMonitor dashboard.', 'bit-integrations')}
  • +
  • ${__('Click on your profile image at the top right.', 'bit-integrations')}
  • +
  • ${__('Click on Account Settings, then API keys.', 'bit-integrations')}
  • +
  • ${__('Use your API key in the Username field.', 'bit-integrations')}
  • +
+ + ${__('To get Client Id & API key, please visit', 'bit-integrations')} - {__('Campaign Monitor API Key', 'bit-integrations')} + ${__(' Campaign Monitor API Key', 'bit-integrations')} - -
-
- {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} +
` - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorCommonFunc.js b/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorCommonFunc.js index d34d5307e..a5d18fa1f 100644 --- a/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorCommonFunc.js +++ b/frontend/src/components/AllIntegrations/CampaignMonitor/CampaignMonitorCommonFunc.js @@ -8,6 +8,11 @@ export const handleInput = (e, campaignMonitorConf, setCampaignMonitorConf) => { setCampaignMonitorConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf.connection_id + ? { connection_id: conf.connection_id } + : { client_id: conf.client_id, api_key: conf.api_key } + // refreshMappedLists export const refreshCampaignMonitorLists = ( campaignMonitorConf, @@ -15,11 +20,8 @@ export const refreshCampaignMonitorLists = ( setIsLoading, setSnackbar ) => { - setIsLoading(true) - const refreshListsRequestParams = { - client_id: campaignMonitorConf.client_id, - api_key: campaignMonitorConf.api_key - } + if (typeof setIsLoading === 'function') setIsLoading(true) + const refreshListsRequestParams = buildAuthRequestParams(campaignMonitorConf) bitsFetch(refreshListsRequestParams, 'campaign_monitor_lists') .then(result => { @@ -46,14 +48,16 @@ export const refreshCampaignMonitorLists = ( setCampaignMonitorConf({ ...newConf }) } else { - setSnackbar({ - show: true, - msg: __('CampaignMonitor Lists refresh failed. please try again', 'bit-integrations') - }) + setSnackbar({ + show: true, + msg: __('CampaignMonitor Lists refresh failed. please try again', 'bit-integrations') + }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } // refreshMappedFields @@ -63,10 +67,9 @@ export const refreshCampaignMonitorFields = ( setIsLoading, setSnackbar ) => { - setIsLoading(true) + if (typeof setIsLoading === 'function') setIsLoading(true) const refreshListsRequestParams = { - client_id: campaignMonitorConf.client_id, - api_key: campaignMonitorConf.api_key, + ...buildAuthRequestParams(campaignMonitorConf), listId: campaignMonitorConf.listId } @@ -87,9 +90,11 @@ export const refreshCampaignMonitorFields = ( msg: __('CampaignMonitor Custom fields refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } export const generateMappedField = campaignMonitorConf => { diff --git a/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubAuthorization.jsx b/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubAuthorization.jsx index b0fcb7aa7..976bb9c9a 100644 --- a/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubAuthorization.jsx @@ -1,140 +1,85 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { companyHubAuthentication } from './CompanyHubCommonFunc' +import Authorization from '../../Connections/Authorization' +import { getAllCompanies, getAllContacts } from './CompanyHubCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function CompanyHubAuthorization({ companyHubConf, setCompanyHubConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ sub_domain: '', api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadMetadata = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...companyHubConf, connection_id: connectionId } + : companyHubConf - !companyHubConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...companyHubConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCompanyHubConf(newConf) - } - - const ActiveInstructions = ` -

${__('To Get Public Id & Secret Key', 'bit-integrations')}

-
    -
  • ${__('First go to your CompanyHub dashboard.', 'bit-integrations')}
  • -
  • ${__('Click go to "Settings" from Left Bottom corner', 'bit-integrations')}
  • -
  • ${__('Then Click "Integrations"', 'bit-integrations')}
  • -
  • ${__('Then Click "Generate Api key"', 'bit-integrations')}
  • -
  • ${__('Then copy "API Authorization Credentials"', 'bit-integrations')}
  • -
` - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Sub Domain:', 'bit-integrations')} -
- -
{error.sub_domain}
+ getAllCompanies(nextConf, setCompanyHubConf, () => {}) + getAllContacts(nextConf, setCompanyHubConf, () => {}) + }, + [companyHubConf, setCompanyHubConf] + ) -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && (!companyHubConf?.companies || !companyHubConf?.contacts)) { + loadMetadata() + } + setStep(value) + }, + [companyHubConf, loadMetadata, setStep] + ) - - {__('To Get Sub Domain & API Key, Please Visit', 'bit-integrations')} -   - - {__('CompanyHub Sub Domain & API Key', 'bit-integrations')} + const note = ` +

${__('To get Sub Domain & API Key', 'bit-integrations')}

+
    +
  • ${__('First go to your CompanyHub dashboard.', 'bit-integrations')}
  • +
  • ${__('Click Settings from the left-bottom corner.', 'bit-integrations')}
  • +
  • ${__('Then click Integrations and generate API key.', 'bit-integrations')}
  • +
+ + ${__('To get Sub Domain & API Key, please visit', 'bit-integrations')} +
+ ${__(' CompanyHub Sub Domain & API Key', 'bit-integrations')} - -
-
+
` - {!isInfo && ( -
- -
- -
- )} - -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubCommonFunc.js b/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubCommonFunc.js index 26463b262..33eaa5525 100644 --- a/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubCommonFunc.js +++ b/frontend/src/components/AllIntegrations/CompanyHub/CompanyHubCommonFunc.js @@ -1,8 +1,6 @@ /* 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, salesmateConf, setSalesmateConf) => { const newConf = { ...salesmateConf } @@ -41,96 +39,66 @@ export const checkMappedFields = companyHubConf => { return true } -export const companyHubAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.sub_domain || !confTmp.api_key) { - setError({ - sub_domain: !confTmp.sub_domain ? __("Sub Domain can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - sub_domain: confTmp.sub_domain, - api_key: confTmp.api_key - } - - bitsFetch(requestParams, 'company_hub_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid Sub Domain & API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + sub_domain: confTmp.sub_domain, + api_key: confTmp.api_key + } export const getAllCompanies = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, companies: true }) - - const requestParams = { - sub_domain: confTmp.sub_domain, - api_key: confTmp.api_key + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, companies: true })) } + const requestParams = buildAuthRequestParams(confTmp) + bitsFetch(requestParams, 'company_hub_fetch_all_companies').then(result => { if (result && result.success) { if (result.data) { - setConf(prevConf => { - prevConf.companies = result.data - return prevConf - }) + setConf(prevConf => ({ ...prevConf, companies: result.data })) - setLoading({ ...setLoading, companies: false }) - toast.success(__('Companies fetched successfully', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, companies: false })) + } return } - setLoading({ ...setLoading, companies: false }) - toast.error(__('Companies Not Found!', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, companies: false })) + } return } - setLoading({ ...setLoading, companies: false }) - toast.error(__('Companies fetching failed', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, companies: false })) + } }) } export const getAllContacts = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, contact: true }) - - const requestParams = { - sub_domain: confTmp.sub_domain, - api_key: confTmp.api_key + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, contact: true })) } + const requestParams = buildAuthRequestParams(confTmp) + bitsFetch(requestParams, 'company_hub_fetch_all_contacts').then(result => { if (result && result.success) { if (result.data) { - setConf(prevConf => { - prevConf.contacts = result.data - return prevConf - }) + setConf(prevConf => ({ ...prevConf, contacts: result.data })) - setLoading({ ...setLoading, contact: false }) - toast.success(__('Contacts fetched successfully', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, contact: false })) + } return } - setLoading({ ...setLoading, contact: false }) - toast.error(__('Contacts not found!', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, contact: false })) + } return } - setLoading({ ...setLoading, contact: false }) - toast.error(__('Contacts fetching failed', 'bit-integrations')) + if (typeof setLoading === 'function') { + setLoading(prev => ({ ...prev, contact: false })) + } }) } diff --git a/frontend/src/components/AllIntegrations/HighLevel/HighLevelAuthorization.jsx b/frontend/src/components/AllIntegrations/HighLevel/HighLevelAuthorization.jsx index 4f50e424e..4d6ccaab5 100644 --- a/frontend/src/components/AllIntegrations/HighLevel/HighLevelAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/HighLevel/HighLevelAuthorization.jsx @@ -1,188 +1,98 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { highLevelAuthentication } from './HighLevelCommonFunc' +import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import toast from 'react-hot-toast' import { useRecoilValue } from 'recoil' import { $appConfigState } from '../../../GlobalStates' import { getProLabel } from '../../Utilities/ProUtilHelpers' +import { getConnection } from '../../../Utils/connectionApi' export default function HighLevelAuthorization({ - formID, highLevelConf, setHighLevelConf, step, setstep, - isInfo, - loading, - setLoading + isInfo }) { const btcbi = useRecoilValue($appConfigState) const { isPro } = btcbi -const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const handleInput = e => { - const newConf = { ...highLevelConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setHighLevelConf(newConf) - } + const hydrateConnectionExtras = useCallback( + async connectionId => { + if (!connectionId) return - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const res = await getConnection(connectionId) + const authDetails = res?.success ? res?.data?.data?.auth_details : null + if (!authDetails) return - setstep(2) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Select Version:')} -
-
- - -
- -
- - -
- - {highLevelConf?.version === 'v2' && ( - <> -
- {__('Location ID:', 'bit-integrations')} -
- -
{error.location_id}
- - {__( - 'To get location id, go to Settings > Business Profile and copy the location id from General Information.', - 'bit-integrations' - )} - - - )} + setHighLevelConf(prev => ({ + ...prev, + version: authDetails.version || prev.version || 'v1', + location_id: authDetails.location_id || prev.location_id || '' + })) + }, + [setHighLevelConf] + ) -
- {__('GoHighLevel Api Key:', 'bit-integrations')} -
- -
{error.api_key}
+ const handleSetStep = useCallback( + value => { + setstep(value) + }, + [setstep] + ) - - {!highLevelConf?.version || highLevelConf?.version === 'v1' - ? __( - 'To get API key, go to Settings > Business Profile and copy the API Key from there.', - 'bit-integrations' - ) - : __( - 'To get API key, go to Settings > Private Integration and create new integration and copy the API token.', - 'bit-integrations' - )} - -
+ const versionOptions = isPro + ? [ + { value: 'v1', label: 'HighLevel API V1' }, + { value: 'v2', label: 'HighLevel API V2' } + ] + : [{ value: 'v1', label: getProLabel('HighLevel API V1') }] - {!isInfo && ( - <> - -
- - - )} - -
+ return ( + + data?.version === 'v2' + ? `https://services.leadconnectorhq.com/locations/${data?.location_id || ''}` + : 'https://rest.gohighlevel.com/v1/contacts/?limit=1', + method: 'GET', + headers: data => ({ + Accept: 'application/json', + ...(data?.version === 'v2' ? { Version: '2021-07-28' } : {}) + }), + extraFields: [ + { + name: 'version', + label: __('Select Version', 'bit-integrations'), + required: true, + type: 'select', + placeholder: __('Select Version', 'bit-integrations'), + options: versionOptions + }, + ...(isPro + ? [ + { + name: 'location_id', + label: __('Location ID', 'bit-integrations'), + required: false, + placeholder: __('Location ID...', 'bit-integrations') + } + ] + : []) + ] + }} + noteDetails={{ note: ActiveInstructions(highLevelConf?.version) }} + onConnectionSelected={hydrateConnectionExtras} + /> ) } diff --git a/frontend/src/components/AllIntegrations/HighLevel/HighLevelCommonFunc.js b/frontend/src/components/AllIntegrations/HighLevel/HighLevelCommonFunc.js index e12b08386..2bd2344c0 100644 --- a/frontend/src/components/AllIntegrations/HighLevel/HighLevelCommonFunc.js +++ b/frontend/src/components/AllIntegrations/HighLevel/HighLevelCommonFunc.js @@ -2,7 +2,6 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import toast from 'react-hot-toast' -import { selector } from 'recoil' import { TASK_LIST_VALUES } from './highlevelConstants' export const handleInput = (e, highLevelConf, setHighLevelConf) => { @@ -11,49 +10,14 @@ export const handleInput = (e, highLevelConf, setHighLevelConf) => { setHighLevelConf({ ...newConf }) } -export const highLevelAuthentication = ( - highLevelConf, - setHighLevelConf, - setError, - setisAuthorized, - loading, - setLoading -) => { - const newConf = { ...highLevelConf } - - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("Access Api Token Key can't be empty", 'bit-integrations') : '' - }) - return - } - if (newConf?.version === 'v2' && !newConf?.location_id) { - setError({ - location_id: __("Location ID can't be empty for v2", 'bit-integrations') - }) - setLoading({ ...loading, auth: false }) - return - } - - const requestParams = { - api_key: newConf.api_key, - version: newConf?.version, - location_id: newConf?.location_id - } - - setLoading({ ...loading, auth: true }) - bitsFetch(requestParams, 'highLevel_authorization').then(result => { - if (result?.success) { - setisAuthorized(true) - toast.success('Authorized Successfully') - } else { - toast.error(result?.data?.message || __('Authorization Failed', 'bit-integrations')) - } - - setLoading({ ...loading, auth: false, accounts: false }) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + version: confTmp?.version, + location_id: confTmp?.location_id + } export const checkMappedFields = highLevelConf => { const mappedFields = highLevelConf?.field_map @@ -71,11 +35,7 @@ export const checkMappedFields = highLevelConf => { } export const getCustomFields = (confTmp, setConf, loading, setLoading) => { - const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id - } + const requestParams = buildAuthRequestParams(confTmp) setLoading({ ...loading, customFields: true }) bitsFetch(requestParams, 'get_highLevel_contact_custom_fields').then(result => { @@ -94,11 +54,7 @@ export const getCustomFields = (confTmp, setConf, loading, setLoading) => { } export const getContacts = (confTmp, setConf, loading, setLoading) => { - const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id - } + const requestParams = buildAuthRequestParams(confTmp) setLoading({ ...loading, contacts: true }) bitsFetch(requestParams, 'get_highLevel_contacts').then(result => { @@ -126,11 +82,7 @@ export const getContacts = (confTmp, setConf, loading, setLoading) => { } export const getUsers = (confTmp, setConf, loading, setLoading) => { - const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id - } + const requestParams = buildAuthRequestParams(confTmp) setLoading({ ...loading, users: true }) bitsFetch(requestParams, 'get_highLevel_users').then(result => { @@ -149,9 +101,7 @@ export const getUsers = (confTmp, setConf, loading, setLoading) => { export const getHLTasks = (confTmp, setConf, loading, setLoading) => { const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id, + ...buildAuthRequestParams(confTmp), contact_id: confTmp.selectedContact } @@ -171,11 +121,7 @@ export const getHLTasks = (confTmp, setConf, loading, setLoading) => { } export const getPipelines = (confTmp, setConf, loading, setLoading) => { - const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id - } + const requestParams = buildAuthRequestParams(confTmp) setLoading({ ...loading, pipelines: true }) bitsFetch(requestParams, 'get_highLevel_pipelines').then(result => { @@ -201,9 +147,7 @@ export const getPipelines = (confTmp, setConf, loading, setLoading) => { export const getOpportunities = (confTmp, setConf, loading, setLoading) => { const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id, + ...buildAuthRequestParams(confTmp), pipeline_id: confTmp.selectedPipeline } @@ -237,9 +181,7 @@ export const getHighLevelOptions = ( } const requestParams = { - api_key: confTmp.api_key, - version: confTmp?.version, - location_id: confTmp?.location_id + ...buildAuthRequestParams(confTmp) } setLoading({ ...loading, options: true }) diff --git a/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx b/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx index 1e82f6def..d89421ad7 100644 --- a/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx @@ -1,42 +1,18 @@ /* eslint-disable no-unused-vars */ /* eslint-disable no-unused-expressions */ /* eslint-disable no-undef */ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { hubspotAuthorization } from './HubspotCommonFunc' +import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function HubspotAuthorization({ hubspotConf, setHubspotConf, step, setstep, - isInfo, - loading, - setLoading + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) -const handleInput = e => { - const newConf = { ...hubspotConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setHubspotConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - } - const note = `

${__('Step of generating Access Token:', 'bit-integrations')}

    @@ -54,62 +30,20 @@ const handleInput = e => { ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Hubspot Access Token:', 'bit-integrations')} -
    - -
    {error.api_key}
    - - {!isInfo && ( - <> - -
    - - - )} - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Hubspot/HubspotCommonFunc.jsx b/frontend/src/components/AllIntegrations/Hubspot/HubspotCommonFunc.jsx index c12572770..bd0c6d72c 100644 --- a/frontend/src/components/AllIntegrations/Hubspot/HubspotCommonFunc.jsx +++ b/frontend/src/components/AllIntegrations/Hubspot/HubspotCommonFunc.jsx @@ -19,6 +19,9 @@ export const handleInput = (e, hubspotConf, setHubspotConf, setIsLoading) => { setHubspotConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } + export const checkMappedFields = hubspotConf => { const mappedFields = hubspotConf?.field_map ? hubspotConf.field_map.filter( @@ -42,34 +45,9 @@ export const generateMappedField = hubspotConf => { : [{ formField: '', hubspotField: '' }] } -export const hubspotAuthorization = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("Access token can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'hubSpot_authorization').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid access token', 'bit-integrations')) - }) -} - export const getAllPipelines = (confTmp, setConf, setLoading, type, loading) => { setLoading({ ...setLoading, pipelines: true }) - const requestParams = { api_key: confTmp.api_key, type } + const requestParams = { ...buildAuthRequestParams(confTmp), type } bitsFetch(requestParams, 'hubspot_pipeline').then(result => { if (result.data) { @@ -96,7 +74,7 @@ export const getAllPipelines = (confTmp, setConf, setLoading, type, loading) => export const getAllOwners = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, owners: true, hubSpotFields: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'hubspot_owners').then(result => { if (result && result.success) { @@ -118,7 +96,7 @@ export const getAllOwners = (confTmp, setConf, setLoading) => { export const getAllContacts = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, contacts: true, hubSpotFields: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'hubspot_contacts').then(result => { if (result && result.success) { @@ -140,7 +118,7 @@ export const getAllContacts = (confTmp, setConf, setLoading) => { export const getAllCompany = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, companies: true, hubSpotFields: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'hubspot_company').then(result => { if (result && result.success) { @@ -162,7 +140,7 @@ export const getAllCompany = (confTmp, setConf, setLoading) => { export const getAllIndustry = (confTmp, setConf, setLoading) => { setLoading(prevLoading => ({ ...prevLoading, industry: true })) - const requestParams = { api_key: confTmp.api_key, type: 'company' } + const requestParams = { ...buildAuthRequestParams(confTmp), type: 'company' } bitsFetch(requestParams, 'hubspot_industry').then(result => { if (result && result.success) { @@ -187,7 +165,7 @@ export const getFields = (confTmp, setConf, setLoading, type, loading, refreshCu } else { setLoading({ ...setLoading, customFields: true }) } - const requestParams = { api_key: confTmp.api_key, type } + const requestParams = { ...buildAuthRequestParams(confTmp), type } bitsFetch(requestParams, 'getFields').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx index c9fec2dba..2851134c7 100644 --- a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx @@ -1,38 +1,34 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { handleAuthorize } from './KlaviyoCommonFunc' +import Authorization from '../../Connections/Authorization' +import { getAllLists } from './KlaviyoCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' function KlaviyoAuthorization({ klaviyoConf, setKlaviyoConf, step, setStep, - isInfo, - loading, - setLoading + isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', authKey: '' }) -const handleInput = e => { - const newConf = { ...klaviyoConf } - const koError = { ...error } - koError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(koError) - setKlaviyoConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId ? { ...klaviyoConf, connection_id: connectionId } : klaviyoConf + getAllLists(nextConf, setKlaviyoConf, {}, () => {}) + }, + [klaviyoConf, setKlaviyoConf] + ) - setStep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !klaviyoConf?.default?.lists) { + loadLists() + } + setStep(value) + }, + [klaviyoConf, loadLists, setStep] + ) const note = `

    ${__('Step of get API Key:', 'bit-integrations')}

    @@ -54,87 +50,29 @@ const handleInput = e => { ` return ( -
    - - -
    -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - -
    - {__('API Key:', 'bit-integrations')} -
    - - - {error.authKey &&
    {error.authKey}
    } - - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('here.', 'bit-integrations')} - - - {!isInfo && ( -
    - -
    - -
    - )} - -
    -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoCommonFunc.js b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoCommonFunc.js index 367c85cec..82a34f59c 100644 --- a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoCommonFunc.js @@ -16,44 +16,17 @@ export const editHandleInput = (e, conf, setConf) => { setConf({ ...newConf }) } -export const handleAuthorize = (conf, setConf, setError, setisAuthorized, loading, setLoading) => { - if (!conf.authKey) { - setError({ - authKey: !conf.authKey ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { authKey: conf.authKey } - - bitsFetch(requestParams, 'klaviyo_handle_authorize').then(result => { - if (result && result.success) { - const newConf = { ...conf } - if (result.data) { - if (!newConf.default) { - newConf.default = {} - } - newConf.default.lists = result.data - } - setConf(newConf) - setisAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { authKey: conf.authKey } export const getAllLists = (conf, setConf, loading, setLoading) => { - setLoading({ ...loading, list: true }) + if (typeof setLoading === 'function') { + setLoading({ ...(loading || {}), list: true }) + } - const requestParams = { authKey: conf.authKey } + const requestParams = buildAuthRequestParams(conf) - bitsFetch(requestParams, 'klaviyo_handle_authorize').then(result => { + bitsFetch(requestParams, 'klaviyo_lists').then(result => { if (result && result.success) { const newConf = { ...conf } if (result.data) { @@ -63,12 +36,16 @@ export const getAllLists = (conf, setConf, loading, setLoading) => { newConf.default.lists = result.data } setConf(newConf) - setLoading({ ...loading, list: false }) + if (typeof setLoading === 'function') { + setLoading({ ...(loading || {}), list: false }) + } toast.success(__('List refresh successfully', 'bit-integrations')) return } - setLoading({ ...loading, list: false }) + if (typeof setLoading === 'function') { + setLoading({ ...(loading || {}), list: false }) + } toast.error(__('List refresh failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/Lemlist/LemlistAuthorization.jsx b/frontend/src/components/AllIntegrations/Lemlist/LemlistAuthorization.jsx index 02714e07f..6e08ff883 100644 --- a/frontend/src/components/AllIntegrations/Lemlist/LemlistAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Lemlist/LemlistAuthorization.jsx @@ -1,82 +1,36 @@ -import { useState } from 'react' -import { toast } from 'react-hot-toast' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' +import Authorization from '../../Connections/Authorization' import { refreshLemlistCampaign } from './LemlistCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function LemlistAuthorization({ - lemlistConf, + lemlistConf = {}, setLemlistConf, step, - setstep, + setstep = () => {}, setSnackbar, isInfo, - isLoading, setIsLoading }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const handleAuthorize = () => { - const newConf = { ...lemlistConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("Access Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - - const data = { - api_key: newConf.api_key - } + const loadCampaigns = useCallback( + connectionId => { + const nextConf = connectionId ? { ...lemlistConf, connection_id: connectionId } : lemlistConf + refreshLemlistCampaign(nextConf, setLemlistConf, setIsLoading, setSnackbar) + }, + [lemlistConf, setIsLoading, setLemlistConf, setSnackbar] + ) - bitsFetch(data, 'lemlist_authorize').then(result => { - if (result && result.success) { - const newConf = { ...lemlistConf } - newConf.tokenDetails = result.data - setLemlistConf(newConf) - setisAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) + const handleSetStep = useCallback( + value => { + if (value === 2 && !lemlistConf?.default?.lemlistCampaigns) { + loadCampaigns() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...lemlistConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setLemlistConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - refreshLemlistCampaign(lemlistConf, setLemlistConf, setIsLoading, setSnackbar) - setstep(2) - } + setstep(value) + }, + [lemlistConf, loadCampaigns, setstep] + ) const ActiveInstructions = `

    ${__('Get Api key', 'bit-integrations')}

    @@ -88,89 +42,24 @@ const handleAuthorize = () => {
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Lemlist API Token', 'bit-integrations')} - - -
-
- {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ ({ Authorization: `Basic ${btoa(`:${data?.api_key || ''}`)}` }) + }} + noteDetails={{ note: ActiveInstructions }} + onConnectionSelected={loadCampaigns} + /> ) } diff --git a/frontend/src/components/AllIntegrations/Lemlist/LemlistCommonFunc.js b/frontend/src/components/AllIntegrations/Lemlist/LemlistCommonFunc.js index ab618e82a..c4ad8fd3d 100644 --- a/frontend/src/components/AllIntegrations/Lemlist/LemlistCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Lemlist/LemlistCommonFunc.js @@ -8,12 +8,13 @@ export const handleInput = (e, lemlistConf, setLemlistConf) => { setLemlistConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf?.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } + // refreshMappedLists export const refreshLemlistCampaign = (lemlistConf, setLemlistConf, setIsLoading, setSnackbar) => { - const refreshListsRequestParams = { - account_id: lemlistConf.account_id, - api_key: lemlistConf.api_key - } + if (typeof setIsLoading === 'function') setIsLoading(true) + const refreshListsRequestParams = buildAuthRequestParams(lemlistConf) bitsFetch(refreshListsRequestParams, 'lemlist_campaigns') .then(result => { if (result && result.success) { @@ -44,9 +45,11 @@ export const refreshLemlistCampaign = (lemlistConf, setLemlistConf, setIsLoading msg: __('Lemlist campaigns refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } // refreshMappedFields @@ -83,7 +86,7 @@ export const refreshLemlistHeader = (lemlistConf, setLemlistConf, setIsLoading, msg: __('Lemlist fields refreshed', 'bit-integrations') }) setLemlistConf({ ...newConf }) - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) } export const checkMappedFields = lemlistConf => { diff --git a/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx b/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx index 75bd1fa0d..a048a864e 100644 --- a/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx @@ -1,12 +1,9 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { getAllEvents, livestormAuthentication } from './LivestormCommonFunc' +import Authorization from '../../Connections/Authorization' +import { getAllEvents } from './LivestormCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function LivestormAuthorization({ livestormConf, @@ -15,28 +12,26 @@ export default function LivestormAuthorization({ setStep, loading, setLoading, + setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !livestormConf?.default - setStep(2) - getAllEvents(livestormConf, setLivestormConf, setLoading) - } + const loadEvents = useCallback( + connectionId => { + const nextConf = connectionId ? { ...livestormConf, connection_id: connectionId } : livestormConf + getAllEvents(nextConf, setLivestormConf, loading, setLoading, setSnackbar) + }, + [livestormConf, loading, setLivestormConf, setLoading, setSnackbar] + ) - const handleInput = e => { - const newConf = { ...livestormConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setLivestormConf(newConf) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !livestormConf?.events?.length) { + loadEvents() + } + setStep(value) + }, + [livestormConf?.events?.length, loadEvents, setStep] + ) const ActiveInstructions = `

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

@@ -49,82 +44,24 @@ const nextPage = () => { ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Livestorm API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js b/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js index c1e170378..a5f37778f 100644 --- a/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js @@ -41,40 +41,12 @@ export const checkMappedFields = livestormConf => { return true } -export const livestormAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'livestorm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__(result?.data || 'Authorized failed, Please enter valid API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = conf => + conf?.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } -export const getAllEvents = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, event: true }) - const requestParams = { api_key: confTmp.api_key } +export const getAllEvents = (confTmp, setConf, loading, setLoading, setSnackbar = null) => { + setLoading({ ...loading, event: true }) + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'livestorm_fetch_all_events').then(result => { if (result && result.success) { @@ -85,24 +57,40 @@ export const getAllEvents = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, event: false }) + setLoading({ ...loading, event: false }) toast.success(__('Events fetched successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Events fetched successfully', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, event: false }) + setLoading({ ...loading, event: false }) toast.error(__('Events Not Found!', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Events Not Found!', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, event: false }) + setLoading({ ...loading, event: false }) toast.error(__('Events fetching failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Events fetching failed', 'bit-integrations') }) + } }) } -export const getAllSessions = (confTmp, setConf, event_id, setLoading) => { - setLoading({ ...setLoading, session: true }) +export const getAllSessions = ( + confTmp, + setConf, + event_id, + loading, + setLoading, + setSnackbar = null +) => { + setLoading({ ...loading, session: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), event_id: event_id } @@ -114,15 +102,24 @@ export const getAllSessions = (confTmp, setConf, event_id, setLoading) => { return prevConf }) - setLoading({ ...setLoading, session: false }) + setLoading({ ...loading, session: false }) toast.success(__('Sessions fetched successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Sessions fetched successfully', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, session: false }) + setLoading({ ...loading, session: false }) toast.error(__('Sessions Not Found!', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Sessions Not Found!', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, session: false }) + setLoading({ ...loading, session: false }) toast.error(__('Sessions fetching failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Sessions fetching failed', 'bit-integrations') }) + } }) } diff --git a/frontend/src/components/AllIntegrations/Livestorm/LivestormIntegLayout.jsx b/frontend/src/components/AllIntegrations/Livestorm/LivestormIntegLayout.jsx index 1534a6e9a..265cdd393 100644 --- a/frontend/src/components/AllIntegrations/Livestorm/LivestormIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Livestorm/LivestormIntegLayout.jsx @@ -19,7 +19,7 @@ export default function LivestormIntegLayout({ }) { const setChanges = (val, name) => { if (name === 'selectedEvent' && val !== '') { - getAllSessions(livestormConf, setLivestormConf, val, setLoading) + getAllSessions(livestormConf, setLivestormConf, val, loading, setLoading, setSnackbar) } setLivestormConf(prevConf => { @@ -55,7 +55,9 @@ export default function LivestormIntegLayout({ closeOnSelect /> -
- - - )} - - + ) } diff --git a/frontend/src/components/AllIntegrations/OmniSend/OmniSendCommonFunc.js b/frontend/src/components/AllIntegrations/OmniSend/OmniSendCommonFunc.js index d77d72651..41dfc86b4 100644 --- a/frontend/src/components/AllIntegrations/OmniSend/OmniSendCommonFunc.js +++ b/frontend/src/components/AllIntegrations/OmniSend/OmniSendCommonFunc.js @@ -1,7 +1,5 @@ /* eslint-disable no-else-return */ -import toast from 'react-hot-toast' import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' export const handleInput = ( e, @@ -47,36 +45,3 @@ export const checkMappedFields = omniSendConf => { } return true } -export const handleOmniSendAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'Omnisend_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} diff --git a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx index ed592a6ba..6ed675e4a 100644 --- a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx @@ -1,162 +1,63 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { toast } from 'react-hot-toast' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { oneHashCRMAuthentication } from './OneHashCRMCommonFunc' +import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function OneHashCRMAuthorization({ oneHashCRMConf, setOneHashCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !oneHashCRMConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...oneHashCRMConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setOneHashCRMConf(newConf) - } - - const handleApiTokenLink = () => { - oneHashCRMConf.domain - ? window.open(`${oneHashCRMConf.domain}/app/user`, '_blank', 'noreferrer') - : toast.error(__('Access API URL is required!', 'bit-integrations')) - } - - const ActiveInstructions = ` -

${__('Get API Token', 'bit-integrations')}

-
    -
  • ${__( - "Go to your OneHash CRM's user dashboard and click the profile buttom from Right top corner", - 'bit-integrations' - )}
  • -
  • ${__('Then select "My Settings"', 'bit-integrations')}
  • -
  • ${__('Then go to "API Access → Generates Keys"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get API credentials', 'bit-integrations')}

+
    +
  • ${__( + "Go to your OneHash CRM user dashboard and click profile from top-right corner.", + 'bit-integrations' + )}
  • +
  • ${__('Select My Settings.', 'bit-integrations')}
  • +
  • ${__('Then go to API Access → Generate Keys.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Access API URL:', 'bit-integrations')} -
- -
{error.domain}
- -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- -
- {__('API Secret:', 'bit-integrations')} -
- -
{error.api_secret}
- - - {__('To Get API Key & API Secret, Please Visit', 'bit-integrations')} -   - - {__('OneHashCRM API Key & API Secret', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMCommonFunc.js b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMCommonFunc.js index 2c5737e79..e452ce1c7 100644 --- a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMCommonFunc.js @@ -1,7 +1,5 @@ /* 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, oneHashCRMConf, setOneHashCRMConf) => { @@ -44,43 +42,3 @@ export const checkMappedFields = oneHashCRMConf => { } return true } - -export const oneHashCRMAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key || !confTmp.api_secret || !confTmp.domain) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '', - api_secret: !confTmp.api_secret ? __("Api Secret can't be empty", 'bit-integrations') : '', - domain: !confTmp.domain ? __("API URL can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key, - api_secret: confTmp.api_secret, - domain: confTmp.domain - } - - bitsFetch(requestParams, 'onehashcrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - __('Authorized failed, Please enter valid API Key & Secret or Access Api URL', 'bit-integrations') - ) - }) -} diff --git a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx index 2da337a14..a3ced2b74 100644 --- a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx @@ -1,14 +1,11 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import Authorization from '../../Connections/Authorization' import { refreshLists } from './SendinBlueCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function SendinBlueAuthorization({ - formID, sendinBlueConf, setSendinBlueConf, step, @@ -16,125 +13,57 @@ export default function SendinBlueAuthorization({ setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...sendinBlueConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const data = { api_key: newConf.api_key } - bitsFetch(data, 'sblue_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...sendinBlueConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSendinBlueConf(newConf) - } + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...sendinBlueConf, connection_id: connectionId } + : sendinBlueConf - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshLists(sendinBlueConf, setSendinBlueConf, setIsLoading, setSnackbar) - } + refreshLists(nextConf, setSendinBlueConf, () => {}, setSnackbar) + }, + [sendinBlueConf, setSendinBlueConf, setSnackbar] + ) - return ( -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !sendinBlueConf?.default?.sblueList) { + loadLists() + } + setstep(value) + }, + [loadLists, sendinBlueConf, setstep] + ) -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get API , Please Visit', 'bit-integrations')}{' '} + const note = ` + + ${__('To get API, please visit', 'bit-integrations')} - {__('Brevo(Sendinblue) API Console', 'bit-integrations')} + ${__(' Brevo(Sendinblue) API Console', 'bit-integrations')} - - {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} +
` - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueCommonFunc.js b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueCommonFunc.js index b77c04172..91e5f8459 100644 --- a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueCommonFunc.js @@ -7,9 +7,13 @@ export const handleInput = (e, sendinBlueConf, setSendinBlueConf) => { newConf.name = e.target.value setSendinBlueConf({ ...newConf }) } + +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } + export const refreshLists = (sendinBlueConf, setSendinBlueConf, setIsLoading, setSnackbar) => { - setIsLoading(true) - const refreshListsRequestParams = { api_key: sendinBlueConf.api_key } + if (typeof setIsLoading === 'function') setIsLoading(true) + const refreshListsRequestParams = buildAuthRequestParams(sendinBlueConf) bitsFetch(refreshListsRequestParams, 'sblue_refresh_lists') .then(result => { if (result && result.success) { @@ -20,13 +24,13 @@ export const refreshLists = (sendinBlueConf, setSendinBlueConf, setIsLoading, se if (result.data.sblueList) { newConf.default.sblueList = result.data.sblueList } - setSnackbar({ show: true, msg: __('List refreshed', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('List refreshed', 'bit-integrations') }) setSendinBlueConf({ ...newConf }) } else if ( (result && result.data && result.data.data) || (!result.success && typeof result.data === 'string') ) { - setSnackbar({ + setSnackbar?.({ show: true, msg: sprintf( __('List refresh failed Cause: %s. please try again', 'bit-integrations'), @@ -34,16 +38,18 @@ export const refreshLists = (sendinBlueConf, setSendinBlueConf, setIsLoading, se ) }) } else { - setSnackbar({ show: true, msg: __('List failed. please try again', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('List failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } export const refreshTemplate = (sendinBlueConf, setSendinBlueConf, setSnackbar) => { // setIsLoading(true) - const refreshListsRequestParams = { api_key: sendinBlueConf.api_key } + const refreshListsRequestParams = buildAuthRequestParams(sendinBlueConf) bitsFetch(refreshListsRequestParams, 'sblue_refresh_template').then(result => { if (result && result.success) { const newConf = { ...sendinBlueConf } @@ -53,13 +59,13 @@ export const refreshTemplate = (sendinBlueConf, setSendinBlueConf, setSnackbar) if (result.data.sblueTemplates) { newConf.default.sblueTemplates = result.data.sblueTemplates } - setSnackbar({ show: true, msg: __('Templates refreshed', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('Templates refreshed', 'bit-integrations') }) setSendinBlueConf({ ...newConf }) } else if ( (result && result.data && result.data.data) || (!result.success && typeof result.data === 'string') ) { - setSnackbar({ + setSnackbar?.({ show: true, msg: sprintf( __('Templates refresh failed Cause: %s. please try again', 'bit-integrations'), @@ -67,7 +73,7 @@ export const refreshTemplate = (sendinBlueConf, setSendinBlueConf, setSnackbar) ) }) } else { - setSnackbar({ show: true, msg: __('Templates failed. please try again', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('Templates failed. please try again', 'bit-integrations') }) } // setIsLoading(false) }) @@ -80,7 +86,7 @@ export const refreshSendinBlueHeader = ( setIsLoading, setSnackbar ) => { - const refreshListsRequestParams = { api_key: sendinBlueConf.api_key } + const refreshListsRequestParams = buildAuthRequestParams(sendinBlueConf) bitsFetch(refreshListsRequestParams, 'sblue_headers') .then(result => { if (result && result.success) { @@ -91,9 +97,9 @@ export const refreshSendinBlueHeader = ( newConf.field_map = Object.values(fields) .filter(f => f.required) .map(f => ({ formField: '', sendinBlueField: f.fieldId, required: true })) - setSnackbar({ show: true, msg: __('Sendinblue fields refreshed', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('Sendinblue fields refreshed', 'bit-integrations') }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __( 'No Sendinblue fields found. Try changing the header row number or try again', @@ -104,14 +110,16 @@ export const refreshSendinBlueHeader = ( setSendinBlueConf({ ...newConf }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __('Sendinblue fields refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } export const checkMappedFields = sendinBlueConf => { diff --git a/frontend/src/components/AllIntegrations/Telegram/TelegramAuthorization.jsx b/frontend/src/components/AllIntegrations/Telegram/TelegramAuthorization.jsx index 69b38c2ac..78153e9c3 100644 --- a/frontend/src/components/AllIntegrations/Telegram/TelegramAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Telegram/TelegramAuthorization.jsx @@ -1,14 +1,11 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' +import Authorization from '../../Connections/Authorization' import { refreshGetUpdates } from './TelegramCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function TelegramAuthorization({ - formID, telegramConf, setTelegramConf, step, @@ -16,120 +13,48 @@ export default function TelegramAuthorization({ setSnackbar, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', bot_api_key: '', apiError: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...telegramConf } + const loadUpdates = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...telegramConf, connection_id: connectionId } + : telegramConf - if (!newConf.name || !newConf.bot_api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - bot_api_key: !newConf.bot_api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const requestParams = { bot_api_key: newConf.bot_api_key } - bitsFetch(requestParams, 'telegram_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else { - setisAuthorized(false) - setError({ apiError: result?.data.description }) - setSnackbar({ show: true, msg: __('Authorized Filled', 'bit-integrations') }) + refreshGetUpdates(nextConf, setTelegramConf, () => {}, setSnackbar) + }, + [setSnackbar, setTelegramConf, telegramConf] + ) + + const handleSetStep = useCallback( + value => { + if (value === 2 && !telegramConf?.default?.telegramChatLists) { + loadUpdates() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...telegramConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setTelegramConf(newConf) - } + setstep(value) + }, + [loadUpdates, setstep, telegramConf] + ) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - refreshGetUpdates(telegramConf, setTelegramConf, setIsLoading, setSnackbar) - setstep(2) - } + const note = ` + + ${__('Create a Telegram bot with BotFather and copy the bot token.', 'bit-integrations')} + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
- {__('Bot API Key:', 'bit-integrations')} -
- -
{error.bot_api_key}
- {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {error.apiError} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Telegram/TelegramCommonFunc.js b/frontend/src/components/AllIntegrations/Telegram/TelegramCommonFunc.js index 10b4a6953..e1e6fae43 100644 --- a/frontend/src/components/AllIntegrations/Telegram/TelegramCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Telegram/TelegramCommonFunc.js @@ -2,10 +2,13 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import { create } from 'mutative' +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { bot_api_key: conf.bot_api_key } + export const refreshGetUpdates = (telegramConf, setTelegramConf, setIsLoading, setSnackbar) => { const newConf = { ...telegramConf } - const requestParams = { bot_api_key: newConf.bot_api_key } - setIsLoading(true) + const requestParams = buildAuthRequestParams(newConf) + if (typeof setIsLoading === 'function') setIsLoading(true) bitsFetch(requestParams, 'refresh_get_updates') .then(result => { if (result && result.success) { @@ -16,27 +19,29 @@ export const refreshGetUpdates = (telegramConf, setTelegramConf, setIsLoading, s if (result.data.telegramChatLists) { newConf.default.telegramChatLists = result.data.telegramChatLists } - setSnackbar({ show: true, msg: __('Chat list refreshed', 'bit-integrations') }) + setSnackbar?.({ show: true, msg: __('Chat list refreshed', 'bit-integrations') }) setTelegramConf({ ...newConf }) } else if ( (result && result.data && result.data.data) || (!result.success && typeof result.data === 'string') ) { - setSnackbar({ + setSnackbar?.({ show: true, msg: `${__('Chat list refresh failed Cause:', 'bit-integrations')}${ result.data.data || result.data }. ${__('please try again', 'bit-integrations')}` }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __('Chat list refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } export const handleInput = (e, telegramConf, setTelegramConf) => { diff --git a/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerAuthorization.jsx b/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerAuthorization.jsx index 51fbf9547..6373a5461 100644 --- a/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerAuthorization.jsx @@ -1,12 +1,9 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { woodpeckerAuthentication } from './WoodpeckerCommonFunc' +import Authorization from '../../Connections/Authorization' +import { getAllCampaign } from './WoodpeckerCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function WoodpeckerAuthorization({ woodpeckerConf, @@ -15,27 +12,16 @@ export default function WoodpeckerAuthorization({ setStep, loading, setLoading, - isInfo + setSnackbar, + isInfo = false }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !woodpeckerConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...woodpeckerConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setWoodpeckerConf(newConf) - } + const loadCampaigns = useCallback( + connectionId => { + const nextConf = connectionId ? { ...woodpeckerConf, connection_id: connectionId } : woodpeckerConf + getAllCampaign(nextConf, setWoodpeckerConf, loading, setLoading, setSnackbar) + }, + [loading, setLoading, setSnackbar, setWoodpeckerConf, woodpeckerConf] + ) const ActiveInstructions = `

${__('Get API Key', 'bit-integrations')}

@@ -55,75 +41,27 @@ const nextPage = () => { ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Woodpecker API Key', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ({ + Authorization: `Basic ${btoa(data?.api_key || '')}`, + 'Content-type': 'application/json' + }) + }} + noteDetails={{ note: ActiveInstructions }} + onConnectionSelected={loadCampaigns} + /> ) } diff --git a/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerCommonFunc.js b/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerCommonFunc.js index 3636df4dc..a829d8c63 100644 --- a/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Woodpecker/WoodpeckerCommonFunc.js @@ -45,38 +45,18 @@ export const checkMappedFields = woodpeckerConf => { return true } -export const woodpeckerAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key - } - - bitsFetch(requestParams, 'woodpecker_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = conf => + conf?.connection_id ? { connection_id: conf.connection_id } : { api_key: conf.api_key } -export const getAllCampaign = (woodpeckerConf, setWoodpeckerConf, loading, setLoading) => { +export const getAllCampaign = ( + woodpeckerConf, + setWoodpeckerConf, + loading, + setLoading, + setSnackbar = null +) => { setLoading({ ...loading, campaign: true }) - const requestParams = { - api_key: woodpeckerConf.api_key - } + const requestParams = buildAuthRequestParams(woodpeckerConf) bitsFetch(requestParams, 'woodpecker_fetch_all_campaigns').then(result => { if (result && result.success) { @@ -87,10 +67,16 @@ export const getAllCampaign = (woodpeckerConf, setWoodpeckerConf, loading, setLo }) toast.success(__('Campaigns fetched successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Campaigns fetched successfully', 'bit-integrations') }) + } setLoading({ ...loading, campaign: false }) return } setLoading({ ...loading, campaign: false }) toast.error(__('Campaigns fetching failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Campaigns fetching failed', 'bit-integrations') }) + } }) } diff --git a/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailAuthorization.jsx b/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailAuthorization.jsx index 9288dce80..4f0c248e2 100644 --- a/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailAuthorization.jsx @@ -1,163 +1,71 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' +import Authorization from '../../Connections/Authorization' import { refreshZagoMailList } from './ZagoMailCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function ZagoMailAuthorization({ - formID, zagoMailConf, setZagoMailConf, step, setstep, setSnackbar, - isInfo, - isLoading, - setIsLoading + isInfo }) { -const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_public_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId ? { ...zagoMailConf, connection_id: connectionId } : zagoMailConf + refreshZagoMailList(nextConf, setZagoMailConf, () => {}, setSnackbar) + }, + [setSnackbar, setZagoMailConf, zagoMailConf] + ) - const handleAuthorize = () => { - const newConf = { ...zagoMailConf } - if (!newConf.name || !newConf.api_public_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_public_key: !newConf.api_public_key - ? __("API Public Key can't be empty", 'bit-integrations') - : '' - }) - return - } - setIsLoading('auth') - const data = { - api_public_key: newConf.api_public_key - } - bitsFetch(data, 'zagoMail_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) + const handleSetStep = useCallback( + value => { + if (value === 2 && !zagoMailConf?.default?.zagoMailLists) { + loadLists() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...zagoMailConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setZagoMailConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - refreshZagoMailList(zagoMailConf, setZagoMailConf, setIsLoading, setSnackbar) - setstep(2) - } - - const ActiveInstructions = ` -

${__('Get API Public Key', 'bit-integrations')}

-
    -
  • ${__('First go to your ZagoMail dashboard.', 'bit-integrations')}
  • -
  • ${__('Click on the top top right corner', 'bit-integrations')}
  • -
  • ${__('Then click on API', 'bit-integrations')}
  • -
` - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
- -
- {__('Access API Public Key Key:', 'bit-integrations')} -
- -
{error.api_public_key}
+ setstep(value) + }, + [loadLists, setstep, zagoMailConf] + ) - - {__('To Get API Public Key Key, Please Visit', 'bit-integrations')} -   + const note = ` +

${__('Get API Public Key', 'bit-integrations')}

+
    +
  • ${__('First go to your ZagoMail dashboard.', 'bit-integrations')}
  • +
  • ${__('Click on the top top right corner', 'bit-integrations')}
  • +
  • ${__('Then click on API', 'bit-integrations')}
  • +
+ + ${__('To get API Public Key, please visit', 'bit-integrations')} - {__('ZagoMail API Token', 'bit-integrations')} + ${__(' ZagoMail API Token', 'bit-integrations')} - -
-
+
` - {isLoading === 'auth' && ( -
- - {__('Checking API Public Key Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, API Public Key key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} - -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailCommonFunc.js b/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailCommonFunc.js index 71433d2e9..f641489c0 100644 --- a/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZagoMail/ZagoMailCommonFunc.js @@ -8,11 +8,12 @@ export const handleInput = (e, zagoMailConf, setZagoMailConf) => { setZagoMailConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf.connection_id ? { connection_id: conf.connection_id } : { api_public_key: conf.api_public_key } + export const refreshZagoMailList = (zagoMailConf, setZagoMailConf, setIsLoading, setSnackbar) => { - setIsLoading(true) - const refreshListsRequestParams = { - api_public_key: zagoMailConf.api_public_key - } + if (typeof setIsLoading === 'function') setIsLoading(true) + const refreshListsRequestParams = buildAuthRequestParams(zagoMailConf) bitsFetch(refreshListsRequestParams, 'zagoMail_lists') .then(result => { if (result && result.success) { @@ -22,12 +23,12 @@ export const refreshZagoMailList = (zagoMailConf, setZagoMailConf, setIsLoading, newConf.default = {} } newConf.default.zagoMailLists = result.data.zagoMailLists - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail lists refreshed', 'bit-integrations') }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __( 'No ZagoMail lists found. Try changing the header row number or try again', @@ -38,21 +39,21 @@ export const refreshZagoMailList = (zagoMailConf, setZagoMailConf, setIsLoading, setZagoMailConf({ ...newConf }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail lists refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } // refreshZagoMailTags export const refreshZagoMailTags = (zagoMailConf, setZagoMailConf, setIsLoading, setSnackbar) => { - setIsLoading({ tags: true }) - const refreshListsRequestParams = { - api_public_key: zagoMailConf.api_public_key - } + if (typeof setIsLoading === 'function') setIsLoading({ tags: true }) + const refreshListsRequestParams = buildAuthRequestParams(zagoMailConf) bitsFetch(refreshListsRequestParams, 'zagoMail_tags') .then(result => { if (result && result.success) { @@ -63,12 +64,12 @@ export const refreshZagoMailTags = (zagoMailConf, setZagoMailConf, setIsLoading, } newConf.tags = result.data.zagoMailTags setZagoMailConf({ ...newConf }) - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail tags refreshed', 'bit-integrations') }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __( 'No ZagoMail tags found. Try changing the header row number or try again', @@ -79,20 +80,22 @@ export const refreshZagoMailTags = (zagoMailConf, setZagoMailConf, setIsLoading, // console.log('newConf', newConf) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail tags refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } // refreshMappedFields export const refreshZagoMailFields = (zagoMailConf, setZagoMailConf, setIsLoading, setSnackbar) => { - setIsLoading(true) + if (typeof setIsLoading === 'function') setIsLoading(true) const refreshListsRequestParams = { - api_public_key: zagoMailConf.api_public_key, + ...buildAuthRequestParams(zagoMailConf), listId: zagoMailConf.listId } // console.log('zagoMailConf', zagoMailConf) @@ -113,12 +116,12 @@ export const refreshZagoMailFields = (zagoMailConf, setZagoMailConf, setIsLoadin zagoMailField: f.fieldId, required: true })) - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail fields refreshed', 'bit-integrations') }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __( 'No ZagoMail fields found. Try changing the header row number or try again', @@ -129,14 +132,16 @@ export const refreshZagoMailFields = (zagoMailConf, setZagoMailConf, setIsLoadin setZagoMailConf({ ...newConf }) } else { - setSnackbar({ + setSnackbar?.({ show: true, msg: __('ZagoMail fields refresh failed. please try again', 'bit-integrations') }) } - setIsLoading(false) + if (typeof setIsLoading === 'function') setIsLoading(false) + }) + .catch(() => { + if (typeof setIsLoading === 'function') setIsLoading(false) }) - .catch(() => setIsLoading(false)) } export const checkMappedFields = zagoMailConf => { From a621046850b36d3c1de7dd34a54bbdf977f6e6a2 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 11:38:43 +0600 Subject: [PATCH 32/58] Refactor MailRelay, Mailercloud, Mailify, Nimble, and PipeDrive integrations to streamline authentication and field fetching - Updated MailRelay integration to use refreshCustomFields for field mapping. - Simplified Mailercloud configuration by renaming authKey to api_key and removing unnecessary error states. - Enhanced Mailify integration with a new Authorization component for better API key handling. - Refactored Nimble integration to utilize Authorization component and improved field fetching logic. - Updated PipeDrive integration to streamline API token handling and improve request parameter building. - Generalized authentication request parameters across integrations for consistency. - Added snackbar notifications for success and error messages during field refresh operations. --- backend/Actions/LMFWC/LMFWCController.php | 29 +-- backend/Actions/LMFWC/Routes.php | 1 - .../Actions/MailRelay/MailRelayController.php | 50 ++++- backend/Actions/MailRelay/Routes.php | 2 +- .../Mailercloud/MailercloudController.php | 55 ++--- backend/Actions/Mailercloud/Routes.php | 1 - backend/Actions/Mailify/MailifyController.php | 57 ++--- backend/Actions/Mailify/Routes.php | 1 - backend/Actions/Mailjet/MailjetController.php | 16 +- backend/Actions/Moosend/MoosendController.php | 7 +- backend/Actions/Nimble/NimbleController.php | 34 ++- backend/Actions/Nimble/Routes.php | 1 - .../Actions/PipeDrive/PipeDriveController.php | 25 ++- .../LMFWC/LMFWCAuthorization.jsx | 160 ++++---------- .../AllIntegrations/LMFWC/LMFWCCommonFunc.js | 118 +++-------- .../LMFWC/LMFWCIntegLayout.jsx | 30 ++- .../MailRelay/MailRelayActions.jsx | 14 +- .../MailRelay/MailRelayAuthorization.jsx | 176 ++++++--------- .../MailRelay/MailRelayCommonFunc.js | 84 +++----- .../MailRelay/MailRelayIntegLayout.jsx | 13 +- .../Mailercloud/EditMailercloud.jsx | 2 +- .../Mailercloud/Mailercloud.jsx | 2 +- .../Mailercloud/MailercloudAuthorization.jsx | 109 ++++------ .../Mailercloud/MailercloudCommonFunc.js | 45 ++-- .../Mailify/MailifyAuthorization.jsx | 200 +++--------------- .../Mailify/MailifyCommonFunc.js | 15 +- .../Mailjet/MailjetCommonFunc.js | 5 +- .../Moosend/MoosendCommonFunc.js | 4 +- .../Nimble/NimbleAuthorization.jsx | 147 ++++--------- .../Nimble/NimbleCommonFunc.js | 55 ++--- .../PipeDrive/PipeDriveAuthorization.jsx | 116 ++-------- .../PipeDrive/PipeDriveCommonFunc.js | 21 +- .../components/Connections/ApiConnection.jsx | 2 +- 33 files changed, 562 insertions(+), 1035 deletions(-) diff --git a/backend/Actions/LMFWC/LMFWCController.php b/backend/Actions/LMFWC/LMFWCController.php index 34eb266a1..8fb9d7ab2 100644 --- a/backend/Actions/LMFWC/LMFWCController.php +++ b/backend/Actions/LMFWC/LMFWCController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\LMFWC; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,25 +15,17 @@ */ class LMFWCController { - protected $_defaultHeader; - - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key, $fieldsRequestParams->api_secret); - - $apiEndpoint = $fieldsRequestParams->base_url . '/wp-json/lmfwc/v2/licenses'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader, ['sslverify' => false]); + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'lmfwc', + 'fields' => [ + 'api_key' => 'value', + 'api_secret' => 'api_secret', + 'base_url' => 'base_url', + ], + ]; - if (is_wp_error($response)) { - wp_send_json_error($response->get_error_message(), HttpHelper::$responseCode); - } - if ((isset($response->code) && $response->code === 'lmfwc_rest_data_error') || (isset($response->success) && $response->success)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } - - wp_send_json_error(!empty($response->message) ? $response->message : __('Please enter valid Consumer key & Consumer secret', 'bit-integrations'), 400); - } + protected $_defaultHeader; public function getAllCustomer($fieldsRequestParams) { diff --git a/backend/Actions/LMFWC/Routes.php b/backend/Actions/LMFWC/Routes.php index d109e350c..640defc4e 100644 --- a/backend/Actions/LMFWC/Routes.php +++ b/backend/Actions/LMFWC/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\LMFWC\LMFWCController; use BitApps\Integrations\Core\Util\Route; -Route::post('lmfwc_authentication', [LMFWCController::class, 'authentication']); Route::post('lmfwc_fetch_all_customer', [LMFWCController::class, 'getAllCustomer']); Route::post('lmfwc_fetch_all_product', [LMFWCController::class, 'getAllProduct']); Route::post('lmfwc_fetch_all_order', [LMFWCController::class, 'getAllOrder']); diff --git a/backend/Actions/MailRelay/MailRelayController.php b/backend/Actions/MailRelay/MailRelayController.php index 62162a734..12a286542 100644 --- a/backend/Actions/MailRelay/MailRelayController.php +++ b/backend/Actions/MailRelay/MailRelayController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\MailRelay; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,11 +15,23 @@ */ class MailRelayController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'mailrelay', + 'fields' => [ + 'auth_token' => 'value', + 'domain' => 'domain', + ], + ]; + protected $_defaultHeader; - public function authentication($fieldsRequestParams) + public function getCustomFields($fieldsRequestParams) { - if (empty($fieldsRequestParams->auth_token) && empty($fieldsRequestParams->domain)) { + $authToken = $this->extractAuthToken($fieldsRequestParams); + $domain = $this->extractDomain($fieldsRequestParams); + + if (empty($authToken) || empty($domain)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -28,12 +41,10 @@ public function authentication($fieldsRequestParams) ); } - $domain = $fieldsRequestParams->domain; $baseUrl = "https://{$domain}.ipzmarketing.com/api/v1/"; $apiEndpoints = $baseUrl . 'custom_fields'; - $apiKey = $fieldsRequestParams->auth_token; $header = [ - 'X-AUTH-TOKEN' => $apiKey + 'X-AUTH-TOKEN' => $authToken ]; $response = HttpHelper::get($apiEndpoints, null, $header); @@ -56,7 +67,10 @@ public function authentication($fieldsRequestParams) public function getAllGroups($fieldsRequestParams) { - if (empty($fieldsRequestParams->auth_token) && empty($fieldsRequestParams->domain)) { + $authToken = $this->extractAuthToken($fieldsRequestParams); + $domain = $this->extractDomain($fieldsRequestParams); + + if (empty($authToken) || empty($domain)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -66,12 +80,10 @@ public function getAllGroups($fieldsRequestParams) ); } - $domain = $fieldsRequestParams->domain; $baseUrl = "https://{$domain}.ipzmarketing.com/api/v1/"; $apiEndpoints = $baseUrl . 'groups?page=1&&per_page=1000'; - $apiKey = $fieldsRequestParams->auth_token; $header = [ - 'X-AUTH-TOKEN' => $apiKey + 'X-AUTH-TOKEN' => $authToken ]; $response = HttpHelper::get($apiEndpoints, null, $header); @@ -95,7 +107,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $auth_token = $integrationDetails->auth_token; + $auth_token = !empty($integrationDetails->auth_token) ? $integrationDetails->auth_token : (isset($integrationDetails->api_key) ? $integrationDetails->api_key : ''); $selectedGroups = $integrationDetails->selectedGroups; $fieldMap = $integrationDetails->field_map; $status = $integrationDetails->status; @@ -122,4 +134,22 @@ public function execute($integrationData, $fieldValues) return $mailRelayApiResponse; } + + private function extractAuthToken($fieldsRequestParams) + { + if (!empty($fieldsRequestParams->auth_token)) { + return $fieldsRequestParams->auth_token; + } + + if (!empty($fieldsRequestParams->api_key)) { + return $fieldsRequestParams->api_key; + } + + return ''; + } + + private function extractDomain($fieldsRequestParams) + { + return !empty($fieldsRequestParams->domain) ? $fieldsRequestParams->domain : ''; + } } diff --git a/backend/Actions/MailRelay/Routes.php b/backend/Actions/MailRelay/Routes.php index f3a04dd97..070efc323 100644 --- a/backend/Actions/MailRelay/Routes.php +++ b/backend/Actions/MailRelay/Routes.php @@ -7,5 +7,5 @@ use BitApps\Integrations\Actions\MailRelay\MailRelayController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailRelay_authentication', [MailRelayController::class, 'authentication']); +Route::post('mailRelay_fetch_custom_fields', [MailRelayController::class, 'getCustomFields']); Route::post('mailRelay_fetch_all_groups', [MailRelayController::class, 'getAllGroups']); diff --git a/backend/Actions/Mailercloud/MailercloudController.php b/backend/Actions/Mailercloud/MailercloudController.php index 318b03596..c4fd6a3b8 100644 --- a/backend/Actions/Mailercloud/MailercloudController.php +++ b/backend/Actions/Mailercloud/MailercloudController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Mailercloud; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,40 +15,20 @@ */ class MailercloudController { - private $baseUrl = 'https://cloudapi.mailercloud.com/v1/'; + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'mailercloud', + 'fields' => [ + 'api_key' => 'value', + ], + ]; - public function handleAuthorize($requestParams) - { - if (empty($requestParams->authKey)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoints = $this->baseUrl . 'client/plan'; - $headers = [ - 'Content-Type' => 'application/json', - 'Authorization' => $requestParams->authKey - ]; - $response = HttpHelper::get($apiEndpoints, null, $headers); - if ($response->code === 'invalid_api_key') { - wp_send_json_error( - __( - 'Invalid token', - 'bit-integrations' - ), - 400 - ); - } - wp_send_json_success($response, 200); - } + private $baseUrl = 'https://cloudapi.mailercloud.com/v1/'; public function getAllLists($requestParams) { - if (empty($requestParams->authKey)) { + $apiKey = $this->extractApiKey($requestParams); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -59,7 +40,7 @@ public function getAllLists($requestParams) $apiEndpoints = $this->baseUrl . 'lists/search'; $headers = [ 'Content-Type' => 'application/json', - 'Authorization' => $requestParams->authKey + 'Authorization' => $apiKey ]; $body = [ 'limit' => 100, @@ -82,7 +63,8 @@ public function getAllLists($requestParams) public function getAllFields($requestParams) { - if (empty($requestParams->authKey)) { + $apiKey = $this->extractApiKey($requestParams); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -94,7 +76,7 @@ public function getAllFields($requestParams) $apiEndpoints = $this->baseUrl . 'contact/property/search'; $headers = [ 'Content-Type' => 'application/json', - 'Authorization' => $requestParams->authKey + 'Authorization' => $apiKey ]; $body = [ 'limit' => 100, @@ -136,7 +118,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $authKey = $integrationDetails->authKey; + $authKey = !empty($integrationDetails->api_key) ? $integrationDetails->api_key : (isset($integrationDetails->authKey) ? $integrationDetails->authKey : ''); $listId = $integrationDetails->listId; $contactType = $integrationDetails->contactType; $field_map = $integrationDetails->field_map; @@ -163,4 +145,9 @@ public function execute($integrationData, $fieldValues) return $mailercloudApiResponse; } + + private function extractApiKey($requestParams) + { + return !empty($requestParams->api_key) ? $requestParams->api_key : (isset($requestParams->authKey) ? $requestParams->authKey : ''); + } } diff --git a/backend/Actions/Mailercloud/Routes.php b/backend/Actions/Mailercloud/Routes.php index 1372cf5e0..04844f1a3 100644 --- a/backend/Actions/Mailercloud/Routes.php +++ b/backend/Actions/Mailercloud/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Mailercloud\MailercloudController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailercloud_handle_authorize', [MailercloudController::class, 'handleAuthorize']); Route::post('mailercloud_get_all_lists', [MailercloudController::class, 'getAllLists']); Route::post('mailercloud_get_all_fields', [MailercloudController::class, 'getAllFields']); diff --git a/backend/Actions/Mailify/MailifyController.php b/backend/Actions/Mailify/MailifyController.php index edda4acf4..86d692eb5 100644 --- a/backend/Actions/Mailify/MailifyController.php +++ b/backend/Actions/Mailify/MailifyController.php @@ -2,11 +2,21 @@ namespace BitApps\Integrations\Actions\Mailify; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class MailifyController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BASIC_AUTH, + 'slug' => 'mailify', + 'fields' => [ + 'account_id' => 'username', + 'api_key' => 'password', + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -14,31 +24,10 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) - { - if (empty($requestParams->account_id) || empty($requestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiEndpoint = 'https://mailifyapis.com/v1/users'; - $header['Authorization'] = 'Basic ' . base64_encode("{$requestParams->account_id}:{$requestParams->api_key}"); - - $response = HttpHelper::get($apiEndpoint, null, $header); - - if (!isset($response->users)) { - wp_send_json_error( - empty($response->message) ? 'Unknown' : $response->message, - 400 - ); - } - wp_send_json_success(true); - } - public static function mailifyHeaders($requestParams) { - if ( - empty($requestParams->account_id) || empty($requestParams->api_key) - ) { + $credentials = self::extractCredentials($requestParams); + if (empty($credentials['account_id']) || empty($credentials['api_key'])) { wp_send_json_error( __( 'Requested parameter is empty', @@ -51,8 +40,8 @@ public static function mailifyHeaders($requestParams) $listId = $requestParams->list_id; $apiEndpoint = "https://mailifyapis.com/v1/lists/{$listId}/fields"; $headers = [ - 'accountId' => $requestParams->account_id, - 'apiKey' => $requestParams->api_key, + 'accountId' => $credentials['account_id'], + 'apiKey' => $credentials['api_key'], ]; $mailifyResponse = HttpHelper::get($apiEndpoint, null, $headers); @@ -78,13 +67,14 @@ public static function mailifyHeaders($requestParams) public static function getAllList($requestParams) { - if (empty($requestParams->account_id) || empty($requestParams->api_key)) { + $credentials = self::extractCredentials($requestParams); + if (empty($credentials['account_id']) || empty($credentials['api_key'])) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } $headers = [ - 'accountId' => $requestParams->account_id, - 'apiKey' => $requestParams->api_key, + 'accountId' => $credentials['account_id'], + 'apiKey' => $credentials['api_key'], ]; $apiEndpoint = 'https://mailifyapis.com/v1/lists'; $apiResponse = HttpHelper::get($apiEndpoint, null, $headers); @@ -133,4 +123,15 @@ public function execute($integrationData, $fieldValues) return $mailifyApiResponse; } + + private static function extractCredentials($requestParams) + { + $accountId = !empty($requestParams->account_id) ? $requestParams->account_id : (!empty($requestParams->username) ? $requestParams->username : ''); + $apiKey = !empty($requestParams->api_key) ? $requestParams->api_key : (!empty($requestParams->password) ? $requestParams->password : ''); + + return [ + 'account_id' => $accountId, + 'api_key' => $apiKey, + ]; + } } diff --git a/backend/Actions/Mailify/Routes.php b/backend/Actions/Mailify/Routes.php index d8b315df6..b54aeb38b 100644 --- a/backend/Actions/Mailify/Routes.php +++ b/backend/Actions/Mailify/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Mailify\MailifyController; use BitApps\Integrations\Core\Util\Route; -Route::post('mailify_authorize', [MailifyController::class, 'authorization']); Route::post('mailify_lists', [MailifyController::class, 'getAllList']); Route::post('mailify_headers', [MailifyController::class, 'mailifyHeaders']); diff --git a/backend/Actions/Mailjet/MailjetController.php b/backend/Actions/Mailjet/MailjetController.php index 5261be0c2..670be4f4f 100644 --- a/backend/Actions/Mailjet/MailjetController.php +++ b/backend/Actions/Mailjet/MailjetController.php @@ -26,13 +26,13 @@ class MailjetController public function getAllLists($fieldsRequestParams) { - if (empty($fieldsRequestParams->secretKey) || empty($fieldsRequestParams->apiKey)) { + $apiKey = !empty($fieldsRequestParams->apiKey) ? $fieldsRequestParams->apiKey : (!empty($fieldsRequestParams->username) ? $fieldsRequestParams->username : ''); + $secretKey = !empty($fieldsRequestParams->secretKey) ? $fieldsRequestParams->secretKey : (!empty($fieldsRequestParams->password) ? $fieldsRequestParams->password : ''); + if (empty($secretKey) || empty($apiKey)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } $apiEndpoints = 'https://api.mailjet.com/v3/REST/contactslist?Limit=1000'; - $apiKey = $fieldsRequestParams->apiKey; - $secretKey = $fieldsRequestParams->secretKey; $header = [ 'Authorization' => 'Basic ' . base64_encode("{$apiKey}:{$secretKey}") ]; @@ -54,13 +54,13 @@ public function getAllLists($fieldsRequestParams) public function getCustomFields($fieldsRequestParams) { - if (empty($fieldsRequestParams->secretKey) || empty($fieldsRequestParams->apiKey)) { + $apiKey = !empty($fieldsRequestParams->apiKey) ? $fieldsRequestParams->apiKey : (!empty($fieldsRequestParams->username) ? $fieldsRequestParams->username : ''); + $secretKey = !empty($fieldsRequestParams->secretKey) ? $fieldsRequestParams->secretKey : (!empty($fieldsRequestParams->password) ? $fieldsRequestParams->password : ''); + if (empty($secretKey) || empty($apiKey)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } $apiEndpoints = 'https://api.mailjet.com/v3/REST/contactmetadata?Limit=1000'; - $apiKey = $fieldsRequestParams->apiKey; - $secretKey = $fieldsRequestParams->secretKey; $header = [ 'Authorization' => 'Basic ' . base64_encode("{$apiKey}:{$secretKey}") ]; @@ -86,8 +86,8 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $apiKey = $integrationDetails->apiKey; - $secretKey = $integrationDetails->secretKey; + $apiKey = !empty($integrationDetails->apiKey) ? $integrationDetails->apiKey : (isset($integrationDetails->username) ? $integrationDetails->username : ''); + $secretKey = !empty($integrationDetails->secretKey) ? $integrationDetails->secretKey : (isset($integrationDetails->password) ? $integrationDetails->password : ''); $selectedLists = $integrationDetails->selectedLists; $fieldMap = $integrationDetails->field_map; diff --git a/backend/Actions/Moosend/MoosendController.php b/backend/Actions/Moosend/MoosendController.php index 1c11a7038..acc6c7450 100644 --- a/backend/Actions/Moosend/MoosendController.php +++ b/backend/Actions/Moosend/MoosendController.php @@ -27,7 +27,8 @@ class MoosendController public function getAllLists($requestParams) { - if (empty($requestParams->authKey)) { + $authKey = !empty($requestParams->authKey) ? $requestParams->authKey : (!empty($requestParams->api_key) ? $requestParams->api_key : ''); + if (empty($authKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -36,7 +37,7 @@ public function getAllLists($requestParams) 400 ); } - $apiEndpoints = $this->baseUrl . 'lists/1/1000.json?apikey=' . $requestParams->authKey; + $apiEndpoints = $this->baseUrl . 'lists/1/1000.json?apikey=' . $authKey; $headers = [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', @@ -58,7 +59,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $authKey = $integrationDetails->authKey; + $authKey = !empty($integrationDetails->authKey) ? $integrationDetails->authKey : (isset($integrationDetails->api_key) ? $integrationDetails->api_key : ''); $listId = $integrationDetails->listId; $method = $integrationDetails->method; $field_map = $integrationDetails->field_map; diff --git a/backend/Actions/Nimble/NimbleController.php b/backend/Actions/Nimble/NimbleController.php index 7dfdf02e7..c170d8159 100644 --- a/backend/Actions/Nimble/NimbleController.php +++ b/backend/Actions/Nimble/NimbleController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Nimble; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class NimbleController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'nimble', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,24 +32,11 @@ public function __construct() $this->_apiEndpoint = 'https://app.nimble.com/api/v1'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key); - $apiEndpoint = $this->_apiEndpoint . '/myself'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (isset($response->user_id)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getAllFields($fieldsRequestParams) { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key); + $apiKey = !empty($fieldsRequestParams->api_key) ? $fieldsRequestParams->api_key : (!empty($fieldsRequestParams->value) ? $fieldsRequestParams->value : ''); + $this->checkValidation($apiKey); + $this->setHeaders($apiKey); $apiEndpoint = $this->_apiEndpoint . '/contacts/fields'; $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); @@ -114,7 +110,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $apiKey = $integrationDetails->api_key; + $apiKey = !empty($integrationDetails->api_key) ? $integrationDetails->api_key : (isset($integrationDetails->value) ? $integrationDetails->value : ''); $fieldMap = $integrationDetails->field_map; $actionName = $integrationDetails->actionName; @@ -133,9 +129,9 @@ public function execute($integrationData, $fieldValues) return $nimbleApiResponse; } - private function checkValidation($fieldsRequestParams, $customParam = '**') + private function checkValidation($apiKey, $customParam = '**') { - if (empty($fieldsRequestParams->api_key) || empty($customParam)) { + if (empty($apiKey) || empty($customParam)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } } diff --git a/backend/Actions/Nimble/Routes.php b/backend/Actions/Nimble/Routes.php index 326fdc406..048e87d8a 100644 --- a/backend/Actions/Nimble/Routes.php +++ b/backend/Actions/Nimble/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Nimble\NimbleController; use BitApps\Integrations\Core\Util\Route; -Route::post('nimble_authentication', [NimbleController::class, 'authentication']); Route::post('nimble_fetch_all_fields', [NimbleController::class, 'getAllFields']); Route::post('nimble_fetch_all_sessions', [NimbleController::class, 'getAllSessions']); diff --git a/backend/Actions/PipeDrive/PipeDriveController.php b/backend/Actions/PipeDrive/PipeDriveController.php index 47932fe95..fb79ce073 100644 --- a/backend/Actions/PipeDrive/PipeDriveController.php +++ b/backend/Actions/PipeDrive/PipeDriveController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\PipeDrive; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\Hooks; use BitApps\Integrations\Core\Util\HttpHelper; @@ -16,11 +17,20 @@ */ class PipeDriveController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'pipedrive', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + private $baseUrl = 'https://api.pipedrive.com/v1/'; public function getMetaData($requestParams) { - if (empty($requestParams->api_key)) { + $apiKey = !empty($requestParams->api_key) ? $requestParams->api_key : (!empty($requestParams->value) ? $requestParams->value : ''); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -30,7 +40,7 @@ public function getMetaData($requestParams) ); } - $apiEndpoints = $this->baseUrl . $requestParams->type . '?api_token=' . $requestParams->api_key; + $apiEndpoints = $this->baseUrl . $requestParams->type . '?api_token=' . $apiKey; $response = HttpHelper::get($apiEndpoints, null); $formattedResponse = []; @@ -63,7 +73,8 @@ public function getMetaData($requestParams) public function getFields($requestParams) { - if (empty($requestParams->api_key)) { + $apiKey = !empty($requestParams->api_key) ? $requestParams->api_key : (!empty($requestParams->value) ? $requestParams->value : ''); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -106,7 +117,7 @@ public function getFields($requestParams) ]; - $apiEndpoints = $this->baseUrl . $requestModule . '?limit=500&api_token=' . $requestParams->api_key; + $apiEndpoints = $this->baseUrl . $requestModule . '?limit=500&api_token=' . $apiKey; $response = HttpHelper::get($apiEndpoints, null); $formattedResponse = []; @@ -160,7 +171,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $api_key = $integrationDetails->api_key; + $api_key = !empty($integrationDetails->api_key) ? $integrationDetails->api_key : (isset($integrationDetails->value) ? $integrationDetails->value : ''); $fieldMap = $integrationDetails->field_map; $module = strtolower($integrationDetails->moduleData->module); @@ -183,13 +194,13 @@ public function execute($integrationData, $fieldValues) } if (isset($pipeDriveApiResponse->success, $pipeDriveApiResponse->data) && $pipeDriveApiResponse->success && \count($integrationDetails->relatedlists)) { - Hooks::run(Config::withPrefix('pipedrive_store_related_list'), $pipeDriveApiResponse, $integrationDetails, $fieldValues, $module, $integrationDetails->api_key, $integId); + Hooks::run(Config::withPrefix('pipedrive_store_related_list'), $pipeDriveApiResponse, $integrationDetails, $fieldValues, $module, $api_key, $integId); /** * @deprecated 2.7.8 Use `bit_integrations_pipedrive_store_related_list` action instead. * @since 2.7.8 */ - Hooks::run('btcbi_pipedrive_store_related_list', $pipeDriveApiResponse, $integrationDetails, $fieldValues, $module, $integrationDetails->api_key, $integId); + Hooks::run('btcbi_pipedrive_store_related_list', $pipeDriveApiResponse, $integrationDetails, $fieldValues, $module, $api_key, $integId); } return $pipeDriveApiResponse; diff --git a/frontend/src/components/AllIntegrations/LMFWC/LMFWCAuthorization.jsx b/frontend/src/components/AllIntegrations/LMFWC/LMFWCAuthorization.jsx index ce197a7b5..b117c8f73 100644 --- a/frontend/src/components/AllIntegrations/LMFWC/LMFWCAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LMFWC/LMFWCAuthorization.jsx @@ -1,44 +1,17 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { lmfwcAuthentication } from './LMFWCCommonFunc' +import Authorization from '../../Connections/Authorization' export default function LMFWCAuthorization({ licenseManagerConf, setLicenseManagerConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) -const [error, setError] = useState({ api_key: '', api_secret: '' }) - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - if (isAuthorized) { - setStep(2) - } - } - - const handleInput = e => { - const newConf = { ...licenseManagerConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setLicenseManagerConf(newConf) - } - const ActiveInstructions = ` ${__('Requirements', 'bit-integrations')}

${__('WordPress permalinks must be enabled at', 'bit-integrations')}: ${__( @@ -59,98 +32,41 @@ const [error, setError] = useState({ api_key: '', api_secret: '' }) ` return ( -

- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- -
{error.base_url}
- -
- {__('Consumer key:', 'bit-integrations')} -
- -
{error.api_key}
- -
- {__('Consumer secret:', 'bit-integrations')} -
- -
{error.api_secret}
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ({ + Authorization: `Basic ${btoa(`${authData?.api_key || ''}:${authData?.api_secret || ''}`)}`, + 'Content-Type': 'application/json' + }), + extraFields: [ + { + name: 'base_url', + label: __('Homepage URL', 'bit-integrations'), + required: true, + placeholder: __('Homepage URL...', 'bit-integrations') + }, + { + name: 'api_secret', + label: __('Consumer Secret', 'bit-integrations'), + required: true, + placeholder: __('Consumer secret...', 'bit-integrations') + } + ], + encryptKeys: ['value', 'api_secret'] + }} + noteDetails={{ note: ActiveInstructions }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/LMFWC/LMFWCCommonFunc.js b/frontend/src/components/AllIntegrations/LMFWC/LMFWCCommonFunc.js index 4229ef731..3353b55c2 100644 --- a/frontend/src/components/AllIntegrations/LMFWC/LMFWCCommonFunc.js +++ b/frontend/src/components/AllIntegrations/LMFWC/LMFWCCommonFunc.js @@ -41,56 +41,22 @@ export const checkMappedFields = licenseManagerConf => { return true } -export const lmfwcAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key || !confTmp.api_secret) { - setError({ - base_url: !confTmp.base_url ? __("Consumer key can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("Consumer key can't be empty", 'bit-integrations') : '', - api_secret: !confTmp.api_secret ? __("Consumer secret can't be empty", 'bit-integrations') : '' - }) - return +const buildAuthRequestParams = confTmp => { + if (confTmp?.connection_id) { + return { connection_id: confTmp.connection_id } } - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { + return { base_url: confTmp.base_url, api_key: confTmp.api_key, api_secret: confTmp.api_secret } - - bitsFetch(requestParams, 'lmfwc_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error( - result?.data && typeof result.data === 'string' - ? result.data - : __('Authorized failed, Please enter valid Consumer key & Consumer secret', 'bit-integrations') - ) - }) } -export const getAllCustomer = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, customer: true }) +export const getAllCustomer = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, customer: true }) - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'lmfwc_fetch_all_customer').then(result => { if (result && result.success) { @@ -100,27 +66,23 @@ export const getAllCustomer = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, customer: false }) + setLoading({ ...loading, customer: false }) toast.success(__('Customers fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, customer: false }) + setLoading({ ...loading, customer: false }) toast.error(__('Customers Not Found!', 'bit-integrations')) return } - setLoading({ ...setLoading, customer: false }) + setLoading({ ...loading, customer: false }) toast.error(__('Customers fetching failed', 'bit-integrations')) }) } -export const getAllProduct = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, product: true }) +export const getAllProduct = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, product: true }) - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'lmfwc_fetch_all_product').then(result => { if (result && result.success) { @@ -130,27 +92,23 @@ export const getAllProduct = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, product: false }) + setLoading({ ...loading, product: false }) toast.success(__('Product fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, product: false }) + setLoading({ ...loading, product: false }) toast.error(__('Product Not Found!', 'bit-integrations')) return } - setLoading({ ...setLoading, product: false }) + setLoading({ ...loading, product: false }) toast.error(__('Product fetching failed', 'bit-integrations')) }) } -export const getAllOrder = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, order: true }) +export const getAllOrder = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, order: true }) - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'lmfwc_fetch_all_order').then(result => { if (result && result.success) { @@ -160,27 +118,23 @@ export const getAllOrder = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, order: false }) + setLoading({ ...loading, order: false }) toast.success(__('Order fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, order: false }) + setLoading({ ...loading, order: false }) toast.error(__('Order Not Found!', 'bit-integrations')) return } - setLoading({ ...setLoading, order: false }) + setLoading({ ...loading, order: false }) toast.error(__('Order fetching failed', 'bit-integrations')) }) } -export const getAllLicense = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, license: true }) +export const getAllLicense = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, license: true }) - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'lmfwc_fetch_all_license').then(result => { if (result && result.success) { @@ -190,27 +144,23 @@ export const getAllLicense = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, license: false }) + setLoading({ ...loading, license: false }) toast.success(__('License fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, license: false }) + setLoading({ ...loading, license: false }) toast.error(__('License Not Found!', 'bit-integrations')) return } - setLoading({ ...setLoading, license: false }) + setLoading({ ...loading, license: false }) toast.error(__('License fetching failed', 'bit-integrations')) }) } -export const getAllGenerator = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, generator: true }) +export const getAllGenerator = (confTmp, setConf, loading, setLoading) => { + setLoading({ ...loading, generator: true }) - const requestParams = { - base_url: confTmp.base_url, - api_key: confTmp.api_key, - api_secret: confTmp.api_secret - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'lmfwc_fetch_all_generator').then(result => { if (result && result.success) { @@ -220,15 +170,15 @@ export const getAllGenerator = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, generator: false }) + setLoading({ ...loading, generator: false }) toast.success(__('Generator fetched successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, generator: false }) + setLoading({ ...loading, generator: false }) toast.error(__('Generator Not Found!', 'bit-integrations')) return } - setLoading({ ...setLoading, generator: false }) + setLoading({ ...loading, generator: false }) toast.error(__('Generator fetching failed', 'bit-integrations')) }) } diff --git a/frontend/src/components/AllIntegrations/LMFWC/LMFWCIntegLayout.jsx b/frontend/src/components/AllIntegrations/LMFWC/LMFWCIntegLayout.jsx index 6b3d24504..770ecf252 100644 --- a/frontend/src/components/AllIntegrations/LMFWC/LMFWCIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/LMFWC/LMFWCIntegLayout.jsx @@ -38,12 +38,12 @@ export default function LMFWCIntegLayout({ draftConf[name] = val if (name === 'module' && (val === 'create_license' || val === 'update_license')) { - getAllCustomer(licenseManagerConf, setLicenseManagerConf, setLoading) - getAllProduct(licenseManagerConf, setLicenseManagerConf, setLoading) - getAllOrder(licenseManagerConf, setLicenseManagerConf, setLoading) + getAllCustomer(licenseManagerConf, setLicenseManagerConf, loading, setLoading) + getAllProduct(licenseManagerConf, setLicenseManagerConf, loading, setLoading) + getAllOrder(licenseManagerConf, setLicenseManagerConf, loading, setLoading) if (val === 'update_license') { - getAllLicense(licenseManagerConf, setLicenseManagerConf, setLoading) + getAllLicense(licenseManagerConf, setLicenseManagerConf, loading, setLoading) draftConf.lmfwcFields = [ { label: __('License key', 'bit-integrations'), key: 'license_key', required: false }, ...draftConf.generalFields @@ -68,7 +68,7 @@ export default function LMFWCIntegLayout({ draftConf.field_map = generateMappedField(draftConf.lmfwcFields) } else if (name === 'module' && (val === 'create_generator' || val === 'update_generator')) { if (val === 'update_generator') { - getAllGenerator(licenseManagerConf, setLicenseManagerConf, setLoading) + getAllGenerator(licenseManagerConf, setLicenseManagerConf, loading, setLoading) draftConf.lmfwcFields = draftConf.generatorFields.map(fields => { return { ...fields, required: false } }) @@ -131,7 +131,9 @@ export default function LMFWCIntegLayout({ closeOnSelect /> -
- - - )} - + ${__('Example domain:', 'bit-integrations')} bitapps${ + domain + ? `${__('To get API token, visit', 'bit-integrations')} ${__('MailRelay API Token', 'bit-integrations')}` + : '' + }` + }} + onConnectionSelected={loadCustomFields} + /> ) } diff --git a/frontend/src/components/AllIntegrations/MailRelay/MailRelayCommonFunc.js b/frontend/src/components/AllIntegrations/MailRelay/MailRelayCommonFunc.js index 9f3a21589..b231ed97b 100644 --- a/frontend/src/components/AllIntegrations/MailRelay/MailRelayCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailRelay/MailRelayCommonFunc.js @@ -37,70 +37,46 @@ export const checkMappedFields = mailRelayConf => { return true } -export const mailRelayAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading, - type -) => { - if (!confTmp.domain) { - setError({ - domain: !confTmp.domain ? __("Account Name can't be empty", 'bit-integrations') : '' - }) - return - } - if (!confTmp.auth_token) { - setError({ - auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' - }) - return - } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + auth_token: conf?.auth_token || conf?.api_key, + domain: conf?.domain + } - setError({}) +export const refreshCustomFields = (confTmp, setConf, loading, setLoading, setSnackbar = null) => { + setLoading({ ...loading, customFields: true }) - if (type === 'authentication') { - setLoading({ ...loading, auth: true }) - } - if (type === 'refreshCustomFields') { - setLoading({ ...loading, customFields: true }) - } - const requestParams = { auth_token: confTmp.auth_token, domain: confTmp.domain } + const requestParams = buildAuthRequestParams(confTmp) - bitsFetch(requestParams, 'mailRelay_authentication').then(result => { + bitsFetch(requestParams, 'mailRelay_fetch_custom_fields').then(result => { if (result && result.success) { const newConf = { ...confTmp } if (result.data) { - newConf.campaigns = result.data + newConf.customFields = result.data } setConf(newConf) - setIsAuthorized(true) - if (type === 'authentication') { - if (result.data) { - newConf.customFields = result.data - } - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if (type === 'refreshCustomFields') { - if (result.data) { - newConf.customFields = result.data - } - setLoading({ ...loading, customFields: false }) - toast.success(__('Custom fields fectched successfully', 'bit-integrations')) + setLoading({ ...loading, customFields: false }) + toast.success(__('Custom fields fetched successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Custom fields fetched successfully', 'bit-integrations') }) } return } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid domain name & API key', 'bit-integrations')) + + setLoading({ ...loading, customFields: false }) + toast.error(__('Custom fields fetch failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Custom fields fetch failed', 'bit-integrations') }) + } }) } -export const getAllGroups = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, groups: true }) +export const getAllGroups = (confTmp, setConf, loading, setLoading, setSnackbar = null) => { + setLoading({ ...loading, groups: true }) - const requestParams = { auth_token: confTmp.auth_token, domain: confTmp.domain } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'mailRelay_fetch_all_groups').then(result => { if (result && result.success) { @@ -109,12 +85,18 @@ export const getAllGroups = (confTmp, setConf, setLoading) => { newConf.groups = result.data } setConf(newConf) - setLoading({ ...setLoading, groups: false }) + setLoading({ ...loading, groups: false }) toast.success(__('Groups fetch successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Groups fetch successfully', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, groups: false }) + setLoading({ ...loading, groups: false }) toast.error(__('Groups fetch failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Groups fetch failed', 'bit-integrations') }) + } }) } diff --git a/frontend/src/components/AllIntegrations/MailRelay/MailRelayIntegLayout.jsx b/frontend/src/components/AllIntegrations/MailRelay/MailRelayIntegLayout.jsx index ef8797a98..3ddb06aab 100644 --- a/frontend/src/components/AllIntegrations/MailRelay/MailRelayIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/MailRelay/MailRelayIntegLayout.jsx @@ -1,8 +1,7 @@ -import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import MailRelayActions from './MailRelayActions' -import { mailRelayAuthentication } from './MailRelayCommonFunc' +import { refreshCustomFields } from './MailRelayCommonFunc' import MailRelayFieldMap from './MailRelayFieldMap' import { addFieldMap } from './IntegrationHelpers' import Note from '../../Utilities/Note' @@ -16,9 +15,6 @@ export default function MailRelayIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setIsAuthorized] = useState(false) - const info = `

${__( 'Phone numbers from the following countries will work only in the Mailrelay SMS Phone number field:', 'bit-integrations' @@ -64,14 +60,12 @@ export default function MailRelayIntegLayout({ {__('Field Map', 'bit-integrations')} -
- - - )} - - + ) } diff --git a/frontend/src/components/AllIntegrations/Mailify/MailifyCommonFunc.js b/frontend/src/components/AllIntegrations/Mailify/MailifyCommonFunc.js index cf5b2933b..a99d0c07e 100644 --- a/frontend/src/components/AllIntegrations/Mailify/MailifyCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailify/MailifyCommonFunc.js @@ -8,12 +8,15 @@ export const handleInput = (e, mailifyConf, setMailifyConf) => { setMailifyConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { account_id: conf.account_id, api_key: conf.api_key } + // refreshMappedLists export const refreshMailifyList = (mailifyConf, setMailifyConf, setIsLoading, setSnackbar) => { - const refreshListsRequestParams = { - account_id: mailifyConf.account_id, - api_key: mailifyConf.api_key - } + if (typeof setIsLoading === 'function') setIsLoading(true) + const refreshListsRequestParams = buildAuthRequestParams(mailifyConf) bitsFetch(refreshListsRequestParams, 'mailify_lists') .then(result => { if (result && result.success) { @@ -51,9 +54,9 @@ export const refreshMailifyList = (mailifyConf, setMailifyConf, setIsLoading, se // refreshMappedFields export const refreshMailifyHeader = (mailifyConf, setMailifyConf, setIsLoading, setSnackbar) => { + if (typeof setIsLoading === 'function') setIsLoading(true) const refreshListsRequestParams = { - account_id: mailifyConf.account_id, - api_key: mailifyConf.api_key, + ...buildAuthRequestParams(mailifyConf), list_id: mailifyConf.listId } diff --git a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js index 71dc56b01..7e12e06ce 100644 --- a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js @@ -43,7 +43,10 @@ export const checkMappedFields = mailjetConf => { const buildAuthRequestParams = confTmp => confTmp.connection_id ? { connection_id: confTmp.connection_id } - : { apiKey: confTmp.apiKey, secretKey: confTmp.secretKey } + : { + apiKey: confTmp.apiKey || confTmp.username, + secretKey: confTmp.secretKey || confTmp.password + } export const mailjetAuthentication = ( confTmp, diff --git a/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js b/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js index 860d28572..3681cde9a 100644 --- a/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Moosend/MoosendCommonFunc.js @@ -22,7 +22,9 @@ export const handleAuthorize = (conf, setError, setAuthorized, loading, setLoadi } const buildAuthRequestParams = conf => - conf.connection_id ? { connection_id: conf.connection_id } : { authKey: conf.authKey } + conf.connection_id + ? { connection_id: conf.connection_id } + : { authKey: conf.authKey || conf.api_key } export const getAllLists = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, list: true }) diff --git a/frontend/src/components/AllIntegrations/Nimble/NimbleAuthorization.jsx b/frontend/src/components/AllIntegrations/Nimble/NimbleAuthorization.jsx index 7e544e86f..cfea4b411 100644 --- a/frontend/src/components/AllIntegrations/Nimble/NimbleAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Nimble/NimbleAuthorization.jsx @@ -1,42 +1,36 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { getAllFields, nimbleAuthentication } from './NimbleCommonFunc' +import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import { getAllFields } from './NimbleCommonFunc' export default function NimbleAuthorization({ nimbleConf, setNimbleConf, step, setStep, - loading, setLoading, - isInfo + isInfo, + setSnackbar }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !nimbleConf?.default - setStep(2) - getAllFields(nimbleConf, setNimbleConf, setLoading) - } + const loadFields = useCallback( + connectionId => { + const nextConf = connectionId ? { ...nimbleConf, connection_id: connectionId } : nimbleConf + getAllFields(nextConf, setNimbleConf, setLoading, setSnackbar) + }, + [nimbleConf, setLoading, setNimbleConf, setSnackbar] + ) - const handleInput = e => { - const newConf = { ...nimbleConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setNimbleConf(newConf) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !nimbleConf?.peopleFields?.length && !nimbleConf?.companyFields?.length) { + loadFields() + } + setStep(value) + }, + [loadFields, nimbleConf?.companyFields?.length, nimbleConf?.peopleFields?.length, setStep] + ) const ActiveInstructions = `

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

@@ -48,79 +42,28 @@ const nextPage = () => { ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Nimble API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js b/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js index cc88490fa..1c2625ca5 100644 --- a/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js @@ -41,40 +41,14 @@ export const checkMappedFields = nimbleConf => { return true } -export const nimbleAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'nimble_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp?.connection_id + ? { connection_id: confTmp.connection_id } + : { api_key: confTmp.api_key } -export const getAllFields = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, allFields: true }) - const requestParams = { api_key: confTmp.api_key } +export const getAllFields = (confTmp, setConf, setLoading, setSnackbar = null) => { + setLoading(prev => ({ ...prev, allFields: true })) + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'nimble_fetch_all_fields').then(result => { if (result && result.success) { @@ -90,15 +64,24 @@ export const getAllFields = (confTmp, setConf, setLoading) => { return prevConf }) - setLoading({ ...setLoading, event: false }) + setLoading(prev => ({ ...prev, allFields: false })) toast.success(__('Fields fetched successfully', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Fields fetched successfully', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, event: false }) + setLoading(prev => ({ ...prev, allFields: false })) toast.error(__('Fields Not Found!', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Fields Not Found!', 'bit-integrations') }) + } return } - setLoading({ ...setLoading, event: false }) + setLoading(prev => ({ ...prev, allFields: false })) toast.error(__('Fields fetching failed', 'bit-integrations')) + if (typeof setSnackbar === 'function') { + setSnackbar({ show: true, msg: __('Fields fetching failed', 'bit-integrations') }) + } }) } diff --git a/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveAuthorization.jsx b/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveAuthorization.jsx index 4dd2dc61e..40a4b20ef 100644 --- a/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveAuthorization.jsx @@ -1,38 +1,15 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { handleAuthorize } from './PipeDriveCommonFunc' +import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' export default function PipeDriveAuthorization({ pipeDriveConf, setPipeDriveConf, step, setstep, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - } - - const handleInput = e => { - const newConf = { ...pipeDriveConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setPipeDriveConf(newConf) - } const note = `

${__('Step of generate API token:', 'bit-integrations')}

    @@ -49,77 +26,22 @@ const nextPage = () => { ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('API Token:', 'bit-integrations')} -
    - -
    {error.api_key}
    - - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('PipeDrive API Token', 'bit-integrations')} - - -
    -
    - - {!isInfo && ( -
    - -
    - -
    - )} - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveCommonFunc.js b/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveCommonFunc.js index f695fab25..09bab7780 100644 --- a/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveCommonFunc.js +++ b/frontend/src/components/AllIntegrations/PipeDrive/PipeDriveCommonFunc.js @@ -4,6 +4,9 @@ import toast from 'react-hot-toast' import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = confTmp => + confTmp?.connection_id ? { connection_id: confTmp.connection_id } : { api_key: confTmp?.api_key } + export const handleInput = ( e, recordTab, @@ -105,7 +108,7 @@ const moduleChange = (recordTab, formID, pipeDriveConf, setPipeDriveConf, setIsL } const refreshFields = (module, pipeDriveConf, setPipeDriveConf, recordTab) => { - const requestParams = { api_key: pipeDriveConf.api_key, module } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), module } bitsFetch(requestParams, 'PipeDrive_refresh_fields').then(result => { if (result && result.success) { @@ -132,7 +135,7 @@ const refreshFields = (module, pipeDriveConf, setPipeDriveConf, recordTab) => { export const refreshOrganizations = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - api_key: pipeDriveConf.api_key, + ...buildAuthRequestParams(pipeDriveConf), type: 'organizations' } @@ -173,7 +176,7 @@ export const refreshOrganizations = (pipeDriveConf, setPipeDriveConf, setIsLoadi export const refreshPersons = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { api_key: pipeDriveConf.api_key, type: 'persons' } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), type: 'persons' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data') .then(result => { @@ -212,7 +215,7 @@ export const refreshPersons = (pipeDriveConf, setPipeDriveConf, setIsLoading, se export const getAllOwners = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { api_key: pipeDriveConf.api_key, type: 'users' } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), type: 'users' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data') .then(result => { @@ -251,7 +254,7 @@ export const getAllOwners = (pipeDriveConf, setPipeDriveConf, setIsLoading, setS export const getAllLeadLabels = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { api_key: pipeDriveConf.api_key, type: 'leadLabels' } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), type: 'leadLabels' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data') .then(result => { @@ -289,7 +292,7 @@ export const getAllLeadLabels = (pipeDriveConf, setPipeDriveConf, setIsLoading, } export const getAllCurrencies = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { api_key: pipeDriveConf.api_key, type: 'currencies' } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), type: 'currencies' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data') .then(result => { @@ -328,7 +331,7 @@ export const getAllCurrencies = (pipeDriveConf, setPipeDriveConf, setIsLoading, export const getDealStages = (pipeDriveConf, setPipeDriveConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { api_key: pipeDriveConf.api_key, type: 'stages' } + const requestParams = { ...buildAuthRequestParams(pipeDriveConf), type: 'stages' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data') .then(result => { @@ -422,7 +425,7 @@ export const checkRequired = pipeDriveConf => { } export const handleAuthorize = (confTmp, setError, setisAuthorized, setIsLoading) => { - if (!confTmp.api_key) { + if (!confTmp.connection_id && !confTmp.api_key) { setError({ api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' }) @@ -430,7 +433,7 @@ export const handleAuthorize = (confTmp, setError, setisAuthorized, setIsLoading } setError({}) setIsLoading(true) - const requestParams = { api_key: confTmp.api_key, type: 'persons' } + const requestParams = { ...buildAuthRequestParams(confTmp), type: 'persons' } bitsFetch(requestParams, 'PipeDrive_fetch_meta_data').then(result => { if (result && result.success) { diff --git a/frontend/src/components/Connections/ApiConnection.jsx b/frontend/src/components/Connections/ApiConnection.jsx index 59d6953cf..ebecbadb1 100644 --- a/frontend/src/components/Connections/ApiConnection.jsx +++ b/frontend/src/components/Connections/ApiConnection.jsx @@ -233,7 +233,7 @@ export default function ApiConnection({ connection_name: authData.connectionName, account_name: authData.connectionName, auth_details: payload.auth_details, - encrypt_keys: defaultEncryptKeys[authType] || [] + encrypt_keys: authDetails?.encryptKeys || defaultEncryptKeys[authType] || [] }) if (!saveRes?.success) { From 322568a02678f7e8b40161ae33becf03a8f323af Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 13:00:11 +0600 Subject: [PATCH 33/58] Refactor FreshSales, Freshdesk, Getgist, Selzy, and Sendy integrations to use a unified authorization structure - Updated FreshSalesCommonFunc.js to streamline authorization request parameters. - Refactored FreshdeskAuthorization.jsx to utilize a new Authorization component for handling API key and domain input. - Simplified FreshdeskCommonFunc.js by consolidating authorization checks and request parameter building. - Replaced manual input handling in GetgistAuthorization.jsx with the new Authorization component. - Adjusted Selzy and Sendy components to follow the new authorization pattern, improving code readability and maintainability. - Enhanced error handling and loading states across all integrations. --- .../FreshSales/FreshSalesController.php | 42 +--- backend/Actions/FreshSales/Routes.php | 1 - .../Actions/Freshdesk/FreshdeskController.php | 53 +---- backend/Actions/Freshdesk/RecordApiHelper.php | 9 +- backend/Actions/Freshdesk/Routes.php | 1 - backend/Actions/Getgist/GetgistController.php | 36 +-- backend/Actions/Getgist/Routes.php | 2 - backend/Actions/Selzy/Routes.php | 2 +- backend/Actions/Selzy/SelzyController.php | 34 ++- backend/Actions/Sendy/RecordApiHelper.php | 2 +- backend/Actions/Sendy/Routes.php | 1 - backend/Actions/Sendy/SendyController.php | 63 ++--- .../FreshSales/FreshSalesAuthorization.jsx | 154 ++++-------- .../FreshSales/FreshSalesCommonFunc.js | 56 ++--- .../Freshdesk/FreshdeskAuthorization.jsx | 223 +++++++----------- .../Freshdesk/FreshdeskCommonFunc.js | 95 ++------ .../Getgist/GetgistAuthorization.jsx | 159 +++---------- .../AllIntegrations/Selzy/Selzy.jsx | 2 +- .../Selzy/SelzyAuthorization.jsx | 115 +++++---- .../AllIntegrations/Selzy/SelzyCommonFunc.js | 69 ++---- .../Sendy/SendyAuthorization.jsx | 199 +++++----------- .../AllIntegrations/Sendy/SendyCommonFunc.jsx | 22 +- 22 files changed, 435 insertions(+), 905 deletions(-) diff --git a/backend/Actions/FreshSales/FreshSalesController.php b/backend/Actions/FreshSales/FreshSalesController.php index bcc9ddc80..a71e51957 100644 --- a/backend/Actions/FreshSales/FreshSalesController.php +++ b/backend/Actions/FreshSales/FreshSalesController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\FreshSales; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,37 +15,14 @@ */ class FreshSalesController { - public function authorization($requestParams) - { - if (empty($requestParams->api_key) || empty($requestParams->bundle_alias)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoints = 'https://' . $requestParams->bundle_alias . '/api/settings/sales_accounts/fields'; - $headers = ['Authorization' => 'Token token=' . $requestParams->api_key]; - $response = HttpHelper::get($apiEndpoints, null, $headers); - - if (isset($response->fields)) { - wp_send_json_success(__( - 'Authorization Success', - 'bit-integrations' - ), 200); - } else { - wp_send_json_error( - __( - 'The token is invalid', - 'bit-integrations' - ), - 400 - ); - } - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'freshsales', + 'fields' => [ + 'api_key' => 'value', + 'bundle_alias' => 'bundle_alias', + ], + ]; public function getMetaData($requestParams) { @@ -172,7 +150,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $api_key = $integrationDetails->api_key; + $api_key = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $bundle_alias = $integrationDetails->bundle_alias; $fieldMap = $integrationDetails->field_map; $module = strtolower($integrationDetails->moduleData->module); diff --git a/backend/Actions/FreshSales/Routes.php b/backend/Actions/FreshSales/Routes.php index c4032c3c0..4bf2397ba 100644 --- a/backend/Actions/FreshSales/Routes.php +++ b/backend/Actions/FreshSales/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\FreshSales\FreshSalesController; use BitApps\Integrations\Core\Util\Route; -Route::post('FreshSales_authorization', [FreshSalesController::class, 'authorization']); Route::post('FreshSales_refresh_fields', [FreshSalesController::class, 'getFields']); Route::post('FreshSales_fetch_meta_data', [FreshSalesController::class, 'getMetaData']); diff --git a/backend/Actions/Freshdesk/FreshdeskController.php b/backend/Actions/Freshdesk/FreshdeskController.php index a720dc03c..ee6a5b8db 100644 --- a/backend/Actions/Freshdesk/FreshdeskController.php +++ b/backend/Actions/Freshdesk/FreshdeskController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Freshdesk; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -15,45 +16,14 @@ */ class FreshdeskController { - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to authorize - * @param mixed $tokenRequestParams - * - * @return JSON Freshdesk api response and status - */ - public function checkAuthorizationAndFetchTickets($tokenRequestParams) - { - if ( - empty($tokenRequestParams->app_domain) - || empty($tokenRequestParams->api_key) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $header = [ - 'Authorization' => base64_encode("{$tokenRequestParams->api_key}"), - 'Content-Type' => 'application/json' - ]; - - $apiEndpoint = $tokenRequestParams->app_domain . '/api/v2/tickets'; - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (is_wp_error($apiResponse) || HttpHelper::$responseCode !== 200) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - - wp_send_json_success($apiResponse, 200); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'freshdesk', + 'fields' => [ + 'api_key' => 'value', + 'app_domain' => 'app_domain', + ], + ]; /** * Process ajax request for Fetch Ticket fields @@ -186,15 +156,17 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integrationId = $integrationData->id; - $api_key = $integrationDetails->api_key; + $api_key = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $status = (int) $integrationDetails->status; $priority = (int) $integrationDetails->priority; $fieldMap = $integrationDetails->field_map; $fieldMapContact = $integrationDetails->field_map_contact; + $app_base_domamin = $integrationDetails->app_domain; if ( empty($api_key) || empty($integrationDetails) + || empty($app_base_domamin) || empty($status) || empty($priority) || empty($fieldMap) @@ -203,7 +175,6 @@ public function execute($integrationData, $fieldValues) // translators: %s: Placeholder value return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Freshdesk')); } - $app_base_domamin = $integrationDetails->app_domain; $apiEndpoint = $integrationDetails->app_domain . '/api/v2/tickets'; $recordApiHelper = new RecordApiHelper($api_key, $integrationId); $freshdeskApiResponse = $recordApiHelper->execute( diff --git a/backend/Actions/Freshdesk/RecordApiHelper.php b/backend/Actions/Freshdesk/RecordApiHelper.php index f8d7dde7b..183803c72 100644 --- a/backend/Actions/Freshdesk/RecordApiHelper.php +++ b/backend/Actions/Freshdesk/RecordApiHelper.php @@ -202,6 +202,7 @@ public function execute( $integrationDetails, $app_base_domamin ) { + $apiKey = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $finalData = $this->generateReqDataFromFieldMap($fieldValues, $fieldMap); $finalData = $finalData + ['status' => json_decode($integrationDetails->status)] + ['priority' => json_decode($integrationDetails->priority)]; @@ -225,15 +226,15 @@ public function execute( $finalDataContact = $this->generateReqDataFromFieldMapContact($fieldValues, $fieldMapContact); $avatarFieldName = $integrationDetails->actions->attachments; $avatar = $fieldValues[$avatarFieldName]; - $apiResponseFetchContact = $this->fetchContact($app_base_domamin, $finalDataContact['email'], $integrationDetails->api_key); + $apiResponseFetchContact = $this->fetchContact($app_base_domamin, $finalDataContact['email'], $apiKey); if (empty($apiResponseFetchContact)) { $typeName = 'create-contact'; - $apiResponseContact = $this->insertContact($app_base_domamin, $finalDataContact, $integrationDetails->api_key, $avatar); + $apiResponseContact = $this->insertContact($app_base_domamin, $finalDataContact, $apiKey, $avatar); } elseif ($integrationDetails->updateContact) { $typeName = 'update-contact'; $contactId = $apiResponseFetchContact[0]->id; - $apiResponseContact = $this->updateContact($app_base_domamin, $finalDataContact, $integrationDetails->api_key, $contactId); + $apiResponseContact = $this->updateContact($app_base_domamin, $finalDataContact, $apiKey, $contactId); } else { $finalData['requester_id'] = $apiResponseFetchContact[0]->id; $typeName = 'fetch-contact'; @@ -253,7 +254,7 @@ public function execute( $attachmentsFieldName = $integrationDetails->actions->file; $fileTicket = $fieldValues[$attachmentsFieldName]; - $apiResponse = $this->insertTicket($apiEndpoint, $finalData, $integrationDetails->api_key, $fileTicket); + $apiResponse = $this->insertTicket($apiEndpoint, $finalData, $apiKey, $fileTicket); if (property_exists($apiResponse, 'errors')) { LogHandler::save($this->_integrationID, wp_json_encode(['type' => 'ticket', 'type_name' => 'create-ticket']), 'error', wp_json_encode($apiResponse)); diff --git a/backend/Actions/Freshdesk/Routes.php b/backend/Actions/Freshdesk/Routes.php index f28fdca91..24693f3ee 100644 --- a/backend/Actions/Freshdesk/Routes.php +++ b/backend/Actions/Freshdesk/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Freshdesk\FreshdeskController; use BitApps\Integrations\Core\Util\Route; -Route::post('freshdesk_authorization_and_fetch_tickets', [FreshdeskController::class, 'checkAuthorizationAndFetchTickets']); Route::post('freshdesk_fetch_ticket_fields', [FreshdeskController::class, 'getAllTicketFields']); Route::post('freshdesk_fetch_Contact_fields', [FreshdeskController::class, 'getAllContactFields']); diff --git a/backend/Actions/Getgist/GetgistController.php b/backend/Actions/Getgist/GetgistController.php index c60984ded..abb5ab6df 100644 --- a/backend/Actions/Getgist/GetgistController.php +++ b/backend/Actions/Getgist/GetgistController.php @@ -2,45 +2,27 @@ namespace BitApps\Integrations\Actions\Getgist; -use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use WP_Error; class GetgistController { public const APIENDPOINT = 'https://api.getgist.com'; - public static function getgistAuthorize($requestsParams) - { - if (empty($requestsParams->api_key)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = self::APIENDPOINT . '/contacts'; - $authorizationHeader['Authorization'] = "Bearer {$requestsParams->api_key}"; - $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); - - if (is_wp_error($apiResponse) || $apiResponse->code === 'authentication_failed') { - wp_send_json_error( - empty($apiResponse->code) ? 'Unknown' : $apiResponse->message, - 400 - ); - } - - wp_send_json_success(true); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'getgist', + 'fields' => [ + 'api_key' => 'value', + ], + ]; public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $api_key = $integrationDetails->api_key; + $api_key = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $fieldMap = $integrationDetails->field_map; $actions = $integrationDetails->actions; if (empty($api_key) diff --git a/backend/Actions/Getgist/Routes.php b/backend/Actions/Getgist/Routes.php index 6b7eeed6b..d59d7bee0 100644 --- a/backend/Actions/Getgist/Routes.php +++ b/backend/Actions/Getgist/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\Getgist\GetgistController; use BitApps\Integrations\Core\Util\Route; - -Route::post('getgist_authorize', [GetgistController::class, 'getgistAuthorize']); diff --git a/backend/Actions/Selzy/Routes.php b/backend/Actions/Selzy/Routes.php index 646082810..d5a8cbabe 100644 --- a/backend/Actions/Selzy/Routes.php +++ b/backend/Actions/Selzy/Routes.php @@ -7,6 +7,6 @@ use BitApps\Integrations\Actions\Selzy\SelzyController; use BitApps\Integrations\Core\Util\Route; -Route::post('selzy_handle_authorize', [SelzyController::class, 'handleAuthorize']); +Route::post('selzy_get_all_lists', [SelzyController::class, 'getAllLists']); Route::post('selzy_get_all_tags', [SelzyController::class, 'getAllTags']); Route::post('selzy_get_all_custom_fields', [SelzyController::class, 'getAllCustomFields']); diff --git a/backend/Actions/Selzy/SelzyController.php b/backend/Actions/Selzy/SelzyController.php index b9aca8ba2..4be850b00 100644 --- a/backend/Actions/Selzy/SelzyController.php +++ b/backend/Actions/Selzy/SelzyController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Selzy; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -16,9 +17,19 @@ class SelzyController { private $baseUrl = 'https://api.selzy.com/en/api/'; - public function handleAuthorize($requestParams) + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'selzy', + 'fields' => [ + 'authKey' => 'value', + 'api_key' => 'value', + ], + ]; + + public function getAllLists($requestParams) { - if (empty($requestParams->authKey)) { + $apiKey = $this->resolveApiKey($requestParams); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -27,7 +38,7 @@ public function handleAuthorize($requestParams) 400 ); } - $apiEndpoints = $this->baseUrl . 'getLists?format=json&api_key=' . $requestParams->authKey; + $apiEndpoints = $this->baseUrl . 'getLists?format=json&api_key=' . $apiKey; $response = HttpHelper::get($apiEndpoints, null); if ($response->code === 'invalid_api_key') { wp_send_json_error( @@ -43,7 +54,8 @@ public function handleAuthorize($requestParams) public function getAllTags($requestParams) { - if (empty($requestParams->authKey)) { + $apiKey = $this->resolveApiKey($requestParams); + if (empty($apiKey)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -52,7 +64,7 @@ public function getAllTags($requestParams) 400 ); } - $apiEndpoints = $this->baseUrl . 'getTags?format=json&api_key=' . $requestParams->authKey; + $apiEndpoints = $this->baseUrl . 'getTags?format=json&api_key=' . $apiKey; $response = HttpHelper::get($apiEndpoints, null); if ($response->code === 'invalid_api_key') { wp_send_json_error( @@ -68,11 +80,12 @@ public function getAllTags($requestParams) public function getAllCustomFields($requestParams) { - if (empty($requestParams->authKey)) { + $apiKey = $this->resolveApiKey($requestParams); + if (empty($apiKey)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $apiEndpoint = "https://api.selzy.com/en/api/getFields?format=json&api_key={$requestParams->authKey}"; + $apiEndpoint = "https://api.selzy.com/en/api/getFields?format=json&api_key={$apiKey}"; $response = HttpHelper::get($apiEndpoint, null); @@ -96,7 +109,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $authKey = $integrationDetails->authKey; + $authKey = $integrationDetails->authKey ?: ($integrationDetails->api_key ?: ($integrationDetails->value ?? '')); $listIds = $integrationDetails->listIds; $tags = $integrationDetails->tags; $method = $integrationDetails->method; @@ -137,4 +150,9 @@ public function execute($integrationData, $fieldValues) return $selzyApiResponse; } + + private function resolveApiKey($requestParams) + { + return $requestParams->authKey ?: ($requestParams->api_key ?: ($requestParams->value ?? '')); + } } diff --git a/backend/Actions/Sendy/RecordApiHelper.php b/backend/Actions/Sendy/RecordApiHelper.php index 7282f9c40..11853db88 100644 --- a/backend/Actions/Sendy/RecordApiHelper.php +++ b/backend/Actions/Sendy/RecordApiHelper.php @@ -60,7 +60,7 @@ public function execute($integId, $integrationDetails, $fieldValues, $fieldMap, $listId = $integrationDetails->list_id; $sendyUrl = $integrationDetails->sendy_url; - $apiKey = $integrationDetails->api_key; + $apiKey = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $finalData['list'] = $listId; $finalData['boolean'] = true; $finalData['api_key'] = $apiKey; diff --git a/backend/Actions/Sendy/Routes.php b/backend/Actions/Sendy/Routes.php index 615dfb60d..b6c25277d 100644 --- a/backend/Actions/Sendy/Routes.php +++ b/backend/Actions/Sendy/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Sendy\SendyController; use BitApps\Integrations\Core\Util\Route; -Route::post('sendy_authorize', [SendyController::class, 'sendyAuthorize']); Route::post('get_all_brands', [SendyController::class, 'getAllBrands']); Route::post('get_all_lists_from_sendy', [SendyController::class, 'getAllLists']); // Route::post('mautic_get_fields', [ MauticController::class, 'getAllFields']); diff --git a/backend/Actions/Sendy/SendyController.php b/backend/Actions/Sendy/SendyController.php index 799e4fa32..b669e72cf 100644 --- a/backend/Actions/Sendy/SendyController.php +++ b/backend/Actions/Sendy/SendyController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Sendy; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Log\LogHandler; use WP_Error; @@ -15,46 +16,23 @@ */ class SendyController { - /** - * Process ajax request for generate_token. - * - * @param object $requestsParams Params for generate token - * - * @return JSON zoho crm api response and status - */ - public static function sendyAuthorize($requestsParams) - { - if (empty($requestsParams->api_key) || empty($requestsParams->sendy_url)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = "{$requestsParams->sendy_url}/api/brands/get-brands.php"; - $authorizationHeader = ['Accept' => 'application/json']; - $requestsParams = ['api_key' => $requestsParams->api_key]; - - $apiResponse = HttpHelper::post($apiEndpoint, $requestsParams, $authorizationHeader); - - if (HttpHelper::$responseCode !== 200 && (is_wp_error($apiResponse) || !\is_array($apiResponse))) { - wp_send_json_error( - empty($apiResponse->message) ? $apiResponse : $apiResponse->message, - 400 - ); - } - - wp_send_json_success(true); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'sendy', + 'fields' => [ + 'api_key' => 'value', + 'sendy_url' => 'sendy_url', + ], + ]; public function getAllBrands($queryParams) { + $apiKey = $queryParams->api_key ?: ($queryParams->value ?? ''); + $sendy_url = $queryParams->sendy_url ?? ''; + if ( - empty($queryParams->api_key) - || empty($queryParams->sendy_url) + empty($apiKey) + || empty($sendy_url) ) { wp_send_json_error( __( @@ -64,8 +42,6 @@ public function getAllBrands($queryParams) 400 ); } - $apiKey = $queryParams->api_key; - $sendy_url = $queryParams->sendy_url; $apiEndpoint = "{$sendy_url}/api/brands/get-brands.php"; $authorizationHeader['Accept'] = 'application/json'; $requestsParams = [ @@ -85,9 +61,12 @@ public function getAllBrands($queryParams) public function getAllLists($queryParams) { + $apiKey = $queryParams->api_key ?: ($queryParams->value ?? ''); + $sendy_url = $queryParams->sendy_url ?? ''; + if ( - empty($queryParams->api_key) - || empty($queryParams->sendy_url) + empty($apiKey) + || empty($sendy_url) ) { wp_send_json_error( __( @@ -97,8 +76,6 @@ public function getAllLists($queryParams) 400 ); } - $apiKey = $queryParams->api_key; - $sendy_url = $queryParams->sendy_url; $brand_id = $queryParams->brand_id; $apiEndpoint = "{$sendy_url}/api/lists/get-lists.php"; $authorizationHeader['Accept'] = 'application/json'; @@ -123,7 +100,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $fieldMap = $integrationDetails->field_map; - $apiKey = $integrationDetails->api_key; + $apiKey = $integrationDetails->api_key ?: ($integrationDetails->value ?? ''); $integId = $integrationData->id; if ( diff --git a/frontend/src/components/AllIntegrations/FreshSales/FreshSalesAuthorization.jsx b/frontend/src/components/AllIntegrations/FreshSales/FreshSalesAuthorization.jsx index b20d45537..d0afe3f79 100644 --- a/frontend/src/components/AllIntegrations/FreshSales/FreshSalesAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FreshSales/FreshSalesAuthorization.jsx @@ -1,134 +1,64 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import TutorialLink from '../../Utilities/TutorialLink' -import { handleAuthorize } from './FreshSalesCommonFunc' +import Authorization from '../../Connections/Authorization' export default function FreshSalesAuthorization({ freshSalesConf, setFreshSalesConf, step, setstep, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - } - - const handleInput = e => { - const newConf = { ...freshSalesConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setFreshSalesConf(newConf) - } const note = `

    ${__('Step of generate API token:', 'bit-integrations')}

    + + ${__('Example: name.myfreshworks.com/crm/sales', 'bit-integrations')} + ` return ( -
    - - -
    - {__('Bundle Alias(Your Account URL):', 'bit-integrations')} -
    - -
    {error.bundle_alias}
    - - {__('Example: name.myfreshworks.com/crm/sales', 'bit-integrations')} - - -
    - {__('API Token:', 'bit-integrations')} -
    - -
    {error.api_key}
    - - {freshSalesConf.bundle_alias && ( - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('FreshSales API Token', 'bit-integrations')} - - - )} -
    -
    - - {!isInfo && ( -
    - -
    - -
    - )} - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/FreshSales/FreshSalesCommonFunc.js b/frontend/src/components/AllIntegrations/FreshSales/FreshSalesCommonFunc.js index 3cd3d6ab4..514715a10 100644 --- a/frontend/src/components/AllIntegrations/FreshSales/FreshSalesCommonFunc.js +++ b/frontend/src/components/AllIntegrations/FreshSales/FreshSalesCommonFunc.js @@ -1,10 +1,17 @@ /* eslint-disable radix */ /* eslint-disable no-unused-expressions */ -import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' -import { __, sprintf } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' import { create } from 'mutative' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + api_key: conf.api_key, + bundle_alias: conf.bundle_alias + } + export const handleInput = ( e, freshSalesConf, @@ -113,8 +120,7 @@ const contactViewChange = ( const refreshFields = (module, freshSalesConf, setFreshSalesConf, setIsLoading, setSnackbar) => { const requestParams = { - api_key: freshSalesConf.api_key, - bundle_alias: freshSalesConf.bundle_alias, + ...buildAuthRequestParams(freshSalesConf), module } @@ -147,8 +153,7 @@ const refreshFields = (module, freshSalesConf, setFreshSalesConf, setIsLoading, export const accountRefreshViews = (freshSalesConf, setFreshSalesConf, setIsLoading, setSnackbar) => { const requestParams = { - api_key: freshSalesConf.api_key, - bundle_alias: freshSalesConf.bundle_alias, + ...buildAuthRequestParams(freshSalesConf), module: 'filters', type: 'sales_accounts' } @@ -177,8 +182,7 @@ export const accountRefreshViews = (freshSalesConf, setFreshSalesConf, setIsLoad export const contactRefreshViews = (freshSalesConf, setFreshSalesConf, setIsLoading, setSnackbar) => { const requestParams = { - api_key: freshSalesConf.api_key, - bundle_alias: freshSalesConf.bundle_alias, + ...buildAuthRequestParams(freshSalesConf), module: 'filters', type: 'contacts' } @@ -213,8 +217,7 @@ export const refreshAccounts = ( setSnackbar ) => { const requestParams = { - api_key: freshSalesConf.api_key, - bundle_alias: freshSalesConf.bundle_alias, + ...buildAuthRequestParams(freshSalesConf), account_view_id: accountViewId, contact_view_id: freshSalesConf.moduleData.contact_view_id, module: 'sales_accounts' @@ -250,8 +253,7 @@ export const refreshContacts = ( setSnackbar ) => { const requestParams = { - api_key: freshSalesConf.api_key, - bundle_alias: freshSalesConf.bundle_alias, + ...buildAuthRequestParams(freshSalesConf), contact_view_id: contactViewId, account_view_id: freshSalesConf.moduleData.account_view_id, module: 'contacts' @@ -333,33 +335,3 @@ export const checkRequired = freshSalesConf => { } return true } - -export const handleAuthorize = (confTmp, setError, setisAuthorized, setIsLoading) => { - if (!confTmp.bundle_alias || !confTmp.api_key) { - setError({ - bundle_alias: !confTmp.bundle_alias - ? __("Bundle Alias (Account URL) can't be empty", 'bit-integrations') - : '', - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setIsLoading(true) - const requestParams = { - api_key: confTmp.api_key, - bundle_alias: confTmp.bundle_alias, - module: 'filters' - } - - bitsFetch(requestParams, 'FreshSales_authorization').then(result => { - if (result && result.success) { - setisAuthorized(true) - setIsLoading(false) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} diff --git a/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskAuthorization.jsx b/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskAuthorization.jsx index 85fe17a49..79bec6d29 100644 --- a/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskAuthorization.jsx @@ -1,174 +1,113 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getAllTicketFields, handleAuthorize } from './FreshdeskCommonFunc' -import Note from '../../Utilities/Note' +import { getAllTicketFields } from './FreshdeskCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function FreshdeskAuthorization({ - formID, freshdeskConf, setFreshdeskConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadTicketFields = useCallback( + connectionId => { + const nextConf = connectionId ? { ...freshdeskConf, connection_id: connectionId } : freshdeskConf + getAllTicketFields(nextConf, setFreshdeskConf, setIsLoading, setSnackbar) + }, + [freshdeskConf, setFreshdeskConf, setIsLoading, setSnackbar] + ) + + const handleSetStep = useCallback( + value => { + if (value === 2 && !freshdeskConf?.ticketFields?.length) { + loadTicketFields() + } - getAllTicketFields(freshdeskConf, setFreshdeskConf, setIsLoading, setSnackbar) - setstep(2) - } - const handleInput = e => { - const newConf = { ...freshdeskConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setFreshdeskConf(newConf) - } + setstep(value) + }, + [freshdeskConf?.ticketFields?.length, loadTicketFields, setstep] + ) const freshdeskInstructions = `

    ${__('Locate Your App Domain', 'bit-integrations')}

    • ${__('Access your Freshdesk account.', 'bit-integrations')}
    • ${__( - 'Copy the URL displayed in your browser’s address bar', - 'bit-integrations' - )} (e.g., https://domain.freshdesk.com/)
    • + 'Copy the URL displayed in your browser’s address bar', + 'bit-integrations' + )} (e.g., https://domain.freshdesk.com/)
    • ${__( - 'Paste the copied App Domain into the designated “App Domain” field within the integrations you’re setting up.', - 'bit-integrations' - )}
    • + 'Paste the copied App Domain into the designated “App Domain” field within the integrations you’re setting up.', + 'bit-integrations' + )}

    ${__('Retrieve Your App API Key', 'bit-integrations')}

    • ${__( - 'Within your Freshdesk account, click on your profile icon, situated in the top right corner.', - 'bit-integrations' - )}
    • + 'Within your Freshdesk account, click on your profile icon, situated in the top right corner.', + 'bit-integrations' + )}
    • ${__( - 'Select “Profile Settings” from the options that appear.', - 'bit-integrations' - )}
    • + 'Select “Profile Settings” from the options that appear.', + 'bit-integrations' + )}
    • ${__( - 'Locate your App API key, prominently displayed on the top right side of the Profile Settings page.', - 'bit-integrations' - )}
    • + 'Locate your App API key, prominently displayed on the top right side of the Profile Settings page.', + 'bit-integrations' + )}
    • ${__('Copy this key.', 'bit-integrations')}
    • ${__( - 'Paste the copied App API key into the designated “App API key” field within the integrations you’re configuring.', - 'bit-integrations' - )}
    • -
    ` + 'Paste the copied App API key into the designated “App API key” field within the integrations you’re configuring.', + 'bit-integrations' + )} +
+ + ${__('To get access Token , Please Visit', 'bit-integrations')}${' '} + + ${__('FreshDesk Console', 'bit-integrations')} + + +` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Your App Domain:', 'bit-integrations')} -
- - {`${__( - 'App Domain Example', - 'bit-integrations' - )}: https://domain.freshdesk.com`} -
{error.app_domain}
- -
- {__('App api key:', 'bit-integrations')} -
- - - {__('To get access Token , Please Visit', 'bit-integrations')}{' '} - - {__('FreshDesk Console', 'bit-integrations')} - - -
{error.api_key}
- - {!isInfo && ( - <> - -
- - - )} - - -
+ ({ + Authorization: btoa(`${authData.api_key}`), + 'Content-Type': 'application/json' + }), + extraFields: [ + { + name: 'app_domain', + label: __('Your App Domain', 'bit-integrations'), + required: true, + placeholder: __('https://domain.freshdesk.com', 'bit-integrations') + } + ] + }} + noteDetails={{ note: freshdeskInstructions }} + onConnectionSelected={loadTicketFields} + /> ) } diff --git a/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskCommonFunc.js b/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskCommonFunc.js index 9b4d8751d..89ef5a875 100644 --- a/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Freshdesk/FreshdeskCommonFunc.js @@ -2,85 +2,38 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' -export const handleInput = (e, slackConf, setSlackConf) => { - const newConf = { ...slackConf } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + app_domain: conf.app_domain, + api_key: conf.api_key + } + +const hasAuthParams = conf => Boolean(conf?.connection_id || (conf?.app_domain && conf?.api_key)) + +export const handleInput = (e, freshdeskConf, setFreshdeskConf) => { + const newConf = { ...freshdeskConf } const { name } = e.target if (e.target.value !== '') { newConf[name] = e.target.value } else { delete newConf[name] } - setSlackConf({ ...newConf }) -} - -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setIsLoading(true) - - const tokenRequestParams = { - app_domain: confTmp.app_domain, - api_key: confTmp.api_key - } - - bitsFetch(tokenRequestParams, 'freshdesk_authorization_and_fetch_tickets') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) + setFreshdeskConf({ ...newConf }) } export const getAllTicketFields = (confTmp, setConf, setIsLoading, setSnackbar) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' + if (!hasAuthParams(confTmp)) { + setSnackbar({ + show: true, + msg: __('Authorization info is missing. please authorize again', 'bit-integrations') }) return } setIsLoading(true) - const tokenRequestParams = { - app_domain: confTmp.app_domain, - api_key: confTmp.api_key - } + const tokenRequestParams = buildAuthRequestParams(confTmp) bitsFetch(tokenRequestParams, 'freshdesk_fetch_ticket_fields') .then(result => result) @@ -110,18 +63,16 @@ export const getAllTicketFields = (confTmp, setConf, setIsLoading, setSnackbar) } export const getAllContactFields = (confTmp, setConf, setIsLoading, setSnackbar) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' + if (!hasAuthParams(confTmp)) { + setSnackbar({ + show: true, + msg: __('Authorization info is missing. please authorize again', 'bit-integrations') }) return } setIsLoading(true) - const tokenRequestParams = { - app_domain: confTmp.app_domain, - api_key: confTmp.api_key - } + const tokenRequestParams = buildAuthRequestParams(confTmp) bitsFetch(tokenRequestParams, 'freshdesk_fetch_Contact_fields') .then(result => result) diff --git a/frontend/src/components/AllIntegrations/Getgist/GetgistAuthorization.jsx b/frontend/src/components/AllIntegrations/Getgist/GetgistAuthorization.jsx index 5b7865506..dd5163a68 100644 --- a/frontend/src/components/AllIntegrations/Getgist/GetgistAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Getgist/GetgistAuthorization.jsx @@ -1,131 +1,42 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function GetgistAuthorization({ getgistConf, setGetgistConf, step, setstep, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...getgistConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const data = { api_key: newConf.api_key } - bitsFetch(data, 'getgist_authorize').then(result => { - if (result?.success) { - setisAuthorized(true) - // setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...getgistConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setGetgistConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - // refreshLists(getgistConf, setGetgistConf, setIsLoading) - } - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get API , Please Visit', 'bit-integrations')}{' '} - - {__('Getgist API Console', 'bit-integrations')} - - - {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ + {__('To get API , Please Visit', 'bit-integrations')}{' '} + + {__('Getgist API Console', 'bit-integrations')} + + + ) + }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/Selzy/Selzy.jsx b/frontend/src/components/AllIntegrations/Selzy/Selzy.jsx index e62571bf3..092848033 100644 --- a/frontend/src/components/AllIntegrations/Selzy/Selzy.jsx +++ b/frontend/src/components/AllIntegrations/Selzy/Selzy.jsx @@ -21,7 +21,7 @@ function Selzy({ formFields, setFlow, flow, allIntegURL }) { const [selzyConf, setSelzyConf] = useState({ name: 'Selzy', type: 'Selzy', - authKey: '', + api_key: '', field_map: [{ formFields: '', selzyFormField: '' }], listIds: '', tags: '', diff --git a/frontend/src/components/AllIntegrations/Selzy/SelzyAuthorization.jsx b/frontend/src/components/AllIntegrations/Selzy/SelzyAuthorization.jsx index b83e0f78f..434141dfb 100644 --- a/frontend/src/components/AllIntegrations/Selzy/SelzyAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Selzy/SelzyAuthorization.jsx @@ -1,29 +1,40 @@ -import { useState } from 'react' -import AuthorizeButton from '../../Utilities/AuthorizeButton' -import ErrorField from '../../Utilities/ErrorField' -import GetInfo from '../../Utilities/GetInfo' -import Input from '../../Utilities/Input' -import Note from '../../Utilities/Note' -import StepPage from '../../Utilities/StepPage' -import { getAllTags, handleAuthorize, handleInput } from './SelzyCommonFunc' -import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' +import { getAllCustomFields, getAllLists, getAllTags } from './SelzyCommonFunc' -function SelzyAuthorization({ selzyConf, setSelzyConf, step, setStep, loading, setLoading, isInfo }) { - const [authorized, setAuthorized] = useState(false) - const [error, setError] = useState({ name: '', authKey: '' }) -const nextPage = async () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setStep(2) - setLoading({ ...loading, page: true }) - const data = await getAllTags(selzyConf, setSelzyConf) - if (data) { - setLoading({ ...loading, page: false }) - } - } +function SelzyAuthorization({ + selzyConf, + setSelzyConf, + step, + setStep, + loading, + setLoading, + isInfo +}) { + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...selzyConf, connection_id: connectionId } : selzyConf + await getAllLists(nextConf, setSelzyConf, loading, setLoading) + }, + [loading, selzyConf, setLoading, setSelzyConf] + ) + + const handleSetStep = useCallback( + async value => { + if (value === 2) { + setLoading({ ...loading, page: true }) + const nextConf = selzyConf?.connection_id ? selzyConf : { ...selzyConf } + await getAllTags(nextConf, setSelzyConf) + await getAllCustomFields(nextConf, setSelzyConf) + setLoading({ ...loading, page: false }) + } + setStep(value) + }, + [loading, selzyConf, setLoading, setSelzyConf, setStep] + ) const note = `

${__('Step of get API Key:', 'bit-integrations')}

@@ -46,44 +57,24 @@ const nextPage = async () => { ` return ( - - - -
- {/* SelzyAuthorization */} - handleInput(e, selzyConf, setSelzyConf, error, setError)} - /> - handleInput(e, selzyConf, setSelzyConf, error, setError)} - /> - - - {!isInfo && ( - - handleAuthorize(selzyConf, setSelzyConf, setError, setAuthorized, loading, setLoading) - } - nextPage={nextPage} - auth={authorized} - loading={loading.auth} - /> - )} -
- - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Selzy/SelzyCommonFunc.js b/frontend/src/components/AllIntegrations/Selzy/SelzyCommonFunc.js index 19ac93597..a95dcb8ba 100644 --- a/frontend/src/components/AllIntegrations/Selzy/SelzyCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Selzy/SelzyCommonFunc.js @@ -6,6 +6,11 @@ import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' import { saveActionConf, saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' +const getApiKeyFromConf = conf => conf?.api_key || conf?.authKey || '' + +const buildAuthRequestParams = conf => + conf?.connection_id ? { connection_id: conf.connection_id } : { api_key: getApiKeyFromConf(conf) } + export const handleInput = (e, conf, setConf, error, setError) => { const newConf = { ...conf } const inputError = { ...error } @@ -15,45 +20,14 @@ export const handleInput = (e, conf, setConf, error, setError) => { setConf(newConf) } -export const handleAuthorize = (conf, setConf, setError, setAuthorized, loading, setLoading) => { - if (!conf.authKey) { - setError({ authKey: !conf.authKey ? __("API Key can't be empty") : '' }) - return +export const getAllLists = async (conf, setConf, loading, setLoading) => { + if (setLoading) { + setLoading({ ...loading, list: true }) } - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { authKey: conf.authKey } - - bitsFetch(requestParams, 'selzy_handle_authorize').then(result => { - if (result.success && result.data) { - const newConf = { ...conf } - if (result.data) { - if (!newConf.default) { - newConf.default = {} - } - const data = result.data.result?.map(v => ({ - ...v, - id: String(v.id) - })) - newConf.default.lists = data - } - getAllCustomFields(newConf, setConf, loading, setLoading) - setConf(newConf) - setAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed')) - }) -} -export const getAllLists = async (conf, setConf, loading, setLoading) => { - setLoading({ ...loading, list: true }) - const requestParams = { authKey: conf.authKey } - const result = await bitsFetch(requestParams, 'selzy_handle_authorize') + const requestParams = buildAuthRequestParams(conf) + const result = await bitsFetch(requestParams, 'selzy_get_all_lists') + if (result.success) { const data = result.data.result?.map(v => ({ ...v, id: String(v.id) })) const newConf = { ...conf } @@ -63,18 +37,25 @@ export const getAllLists = async (conf, setConf, loading, setLoading) => { } newConf.default.lists = data setConf(newConf) - setLoading({ ...loading, list: false }) - toast.success(__('List refresh successfully')) - return + if (setLoading) { + setLoading({ ...loading, list: false }) + toast.success(__('List refresh successfully')) + } + return true } } - setLoading({ ...loading, list: false }) - toast.success(__('List refresh failed')) + + if (setLoading) { + setLoading({ ...loading, list: false }) + toast.success(__('List refresh failed')) + } + + return false } export const getAllTags = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, tag: true }) - const requestParams = { authKey: conf.authKey } + const requestParams = buildAuthRequestParams(conf) const result = await bitsFetch(requestParams, 'selzy_get_all_tags') if (result.success) { const data = result.data.result @@ -101,7 +82,7 @@ export const getAllTags = async (conf, setConf, loading, setLoading) => { export const getAllCustomFields = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, customFields: true }) - const requestParams = { authKey: conf.authKey } + const requestParams = buildAuthRequestParams(conf) const result = await bitsFetch(requestParams, 'selzy_get_all_custom_fields') if (result.success) { const newConf = { ...conf } diff --git a/frontend/src/components/AllIntegrations/Sendy/SendyAuthorization.jsx b/frontend/src/components/AllIntegrations/Sendy/SendyAuthorization.jsx index 1a74c2428..ee476f61a 100644 --- a/frontend/src/components/AllIntegrations/Sendy/SendyAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Sendy/SendyAuthorization.jsx @@ -1,144 +1,73 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-undef */ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' 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 { getAllList } from './ElasticEmailCommonFunc' +import Authorization from '../../Connections/Authorization' +import { getAllBrand } from './SendyCommonFunc' -export default function SendyAuthorization({ sendyConf, setSendyConf, step, setstep, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ name: '', api_key: '' }) - const [showAuthMsg, setShowAuthMsg] = useState(false) - const [isLoading, setIsLoading] = useState(false) -const handleAuthorize = () => { - const newConf = { ...sendyConf } - if (!newConf.name || !newConf.api_key) { - setError({ - name: !newConf.name ? __("Integration name can't be empty", 'bit-integrations') : '', - api_key: !newConf.api_key ? __("API Key can't be empty", 'bit-integrations') : '', - sendy_url: !newConf.sendy_url ? __("Sendy URL can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading('auth') - const values = { api_key: newConf.api_key, sendy_url: newConf.sendy_url } - bitsFetch(values, 'sendy_authorize').then(result => { - if (result.success) { - setisAuthorized(true) +export default function SendyAuthorization({ + sendyConf, + setSendyConf, + step, + setstep, + setIsLoading, + setSnackbar, + isInfo +}) { + const loadBrands = useCallback( + connectionId => { + const nextConf = connectionId ? { ...sendyConf, connection_id: connectionId } : sendyConf + getAllBrand(nextConf, setSendyConf, setIsLoading, setSnackbar) + }, + [sendyConf, setSendyConf, setIsLoading, setSnackbar] + ) + + const handleSetStep = useCallback( + value => { + if (value === 2 && !sendyConf?.default?.brandList) { + loadBrands() } - setShowAuthMsg(true) - setIsLoading(false) - }) - } - const handleInput = e => { - const newConf = { ...sendyConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSendyConf(newConf) - } + setstep(value) + }, + [loadBrands, sendyConf?.default?.brandList, setstep] + ) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - // !sendyConf?.default && getAllList(sendyConf, setSendyConf, setIsLoading) - setstep(2) - } + const note = ` + + ${__('To get API , Please Visit', 'bit-integrations')} + + ${__('Sendy API Console', 'bit-integrations')} + + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
{error.name}
-
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get API , Please Visit', 'bit-integrations')}{' '} - - {__('Sendy API Console', 'bit-integrations')} - - -
- {__('Sendy URL:', 'bit-integrations')} -
- -
{error.sendy_url}
- {isLoading === 'auth' && ( -
- - {__('Checking API Key!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
- - × - - {__('Sorry, Api key is invalid', 'bit-integrations')} -
- )} - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Sendy/SendyCommonFunc.jsx b/frontend/src/components/AllIntegrations/Sendy/SendyCommonFunc.jsx index 4741fe9cb..00cf7585a 100644 --- a/frontend/src/components/AllIntegrations/Sendy/SendyCommonFunc.jsx +++ b/frontend/src/components/AllIntegrations/Sendy/SendyCommonFunc.jsx @@ -1,8 +1,16 @@ /* eslint-disable no-else-return */ import toast from 'react-hot-toast' -import { __, sprintf } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + api_key: conf.api_key, + sendy_url: conf.sendy_url + } + export const handleInput = ( e, sendyConf, @@ -25,10 +33,8 @@ export const handleInput = ( export const getAllBrand = (sendyConf, setSendyConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const queryParams = { - api_key: sendyConf.api_key, - sendy_url: sendyConf.sendy_url - } + const queryParams = buildAuthRequestParams(sendyConf) + const loadPostTypes = bitsFetch(queryParams, 'get_all_brands').then(result => { if (result && result.success) { const newConf = { ...sendyConf } @@ -49,16 +55,15 @@ export const getAllBrand = (sendyConf, setSendyConf, setIsLoading, setSnackbar) error: __('Error Occurred', 'bit-integrations'), loading: __('Loading Brand...') }) - // .catch(() => setIsLoading(false)) } export const getAllList = (sendyConf, setSendyConf, setIsLoading, setSnackbar) => { setIsLoading(true) const queryParams = { - api_key: sendyConf.api_key, - sendy_url: sendyConf.sendy_url, + ...buildAuthRequestParams(sendyConf), brand_id: sendyConf.brand_id } + const loadPostTypes = bitsFetch(queryParams, 'get_all_lists_from_sendy').then(result => { if (result && result.success) { const newConf = { ...sendyConf } @@ -79,7 +84,6 @@ export const getAllList = (sendyConf, setSendyConf, setIsLoading, setSnackbar) = error: __('Error Occurred', 'bit-integrations'), loading: __('Loading Lists...') }) - // .catch(() => setIsLoading(false)) } export const generateMappedField = sendyConf => { From 9f3a96025ac35c712903d655915f7ddd5d909c84 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 16:31:16 +0600 Subject: [PATCH 34/58] Refactor integration authorization components to use a unified Authorization component - Replaced individual authorization logic in Gravitec, Salesflare, SendGrid, and Zendesk components with a shared Authorization component. - Simplified state management and error handling by removing redundant code. - Updated API request structures to accommodate new authentication methods. - Improved user instructions and notes for obtaining API keys and tokens. - Removed unused imports and cleaned up code for better readability and maintainability. --- backend/Actions/Flowlu/FlowluController.php | 25 +-- backend/Actions/Flowlu/Routes.php | 1 - .../Actions/Gravitec/GravitecController.php | 40 +--- backend/Actions/Gravitec/Routes.php | 2 - backend/Actions/Salesflare/Routes.php | 1 - .../Salesflare/SalesflareController.php | 24 +-- backend/Actions/SendGrid/Routes.php | 2 +- .../Actions/SendGrid/SendGridController.php | 14 +- backend/Actions/Zendesk/Routes.php | 1 - backend/Actions/Zendesk/ZendeskController.php | 30 +-- .../Flowlu/FlowluAuthorization.jsx | 180 ++++-------------- .../Flowlu/FlowluCommonFunc.js | 95 ++------- .../Gravitec/GravitecAuthorization.jsx | 180 ++++-------------- .../Gravitec/GravitecCommonFunc.js | 44 ----- .../Salesflare/SalesflareAuthorization.jsx | 133 +++---------- .../Salesflare/SalesflareCommonFunc.js | 46 +---- .../SendGrid/SendGridAuthorization.jsx | 145 +++++--------- .../SendGrid/SendGridCommonFunc.js | 58 ++---- .../SendGrid/SendGridIntegLayout.jsx | 18 +- .../Zendesk/ZendeskAuthorization.jsx | 128 +++---------- .../Zendesk/ZendeskCommonFunc.js | 57 ++---- 21 files changed, 287 insertions(+), 937 deletions(-) diff --git a/backend/Actions/Flowlu/FlowluController.php b/backend/Actions/Flowlu/FlowluController.php index a545eaa75..12e5a33e7 100644 --- a/backend/Actions/Flowlu/FlowluController.php +++ b/backend/Actions/Flowlu/FlowluController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Flowlu; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class FlowluController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'flowlu', + 'fields' => [ + 'api_key' => 'value', + 'company_name' => 'company_name', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -25,21 +35,6 @@ public function __construct() $this->_defaultHeader = ['Content-type' => 'application/json']; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->comapnyName = $fieldsRequestParams->company_name; - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->setApiEndpoint() . "/module/crm/account?api_key={$apiKey}"; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (!isset($response->error)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Session Token or Link Name', 'bit-integrations'), 400); - } - } - public function getAllFields($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams, $fieldsRequestParams->action_name); diff --git a/backend/Actions/Flowlu/Routes.php b/backend/Actions/Flowlu/Routes.php index 67a493e64..9ae250fb1 100644 --- a/backend/Actions/Flowlu/Routes.php +++ b/backend/Actions/Flowlu/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Flowlu\FlowluController; use BitApps\Integrations\Core\Util\Route; -Route::post('flowlu_authentication', [FlowluController::class, 'authentication']); Route::post('Flowlu_all_fields', [FlowluController::class, 'getAllFields']); Route::post('flowlu_fetch_all_account_categories', [FlowluController::class, 'getAllAccountCategories']); Route::post('flowlu_fetch_all_industries', [FlowluController::class, 'getAllIndustries']); diff --git a/backend/Actions/Gravitec/GravitecController.php b/backend/Actions/Gravitec/GravitecController.php index 3ca1a96c7..b97fe511e 100644 --- a/backend/Actions/Gravitec/GravitecController.php +++ b/backend/Actions/Gravitec/GravitecController.php @@ -6,7 +6,7 @@ namespace BitApps\Integrations\Actions\Gravitec; -use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use WP_Error; /** @@ -14,35 +14,15 @@ */ class GravitecController { - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->site_url) || empty($fieldsRequestParams->app_key) || empty($fieldsRequestParams->app_secret)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $headers = [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Basic ' . base64_encode("{$fieldsRequestParams->app_key}:{$fieldsRequestParams->app_secret}") - ]; - - $data = [ - 'payload' => [ - 'title' => 'Authorization', - 'message' => __('Authorized Successfully', 'bit-integrations'), - 'icon' => BTCBI_ASSET_URI . '/gravitec.jpg', - 'redirect_url' => $fieldsRequestParams->site_url - ] - ]; - - $apiEndpoint = 'https://uapi.gravitec.net/api/v3/push'; - $response = HttpHelper::post($apiEndpoint, wp_json_encode($data), $headers); - - if (isset($response->id)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid Site Url, App Key & App Secret', 'bit-integrations'), 400); - } - } + public static array $authConfig = [ + 'authType' => AuthorizationType::BASIC_AUTH, + 'slug' => 'gravitec', + 'fields' => [ + 'app_key' => 'username', + 'app_secret' => 'password', + 'site_url' => 'site_url', + ], + ]; public function execute($integrationData, $fieldValues) { diff --git a/backend/Actions/Gravitec/Routes.php b/backend/Actions/Gravitec/Routes.php index b755c128a..3f11357c5 100644 --- a/backend/Actions/Gravitec/Routes.php +++ b/backend/Actions/Gravitec/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\Gravitec\GravitecController; use BitApps\Integrations\Core\Util\Route; - -Route::post('gravitec_authentication', [GravitecController::class, 'authentication']); diff --git a/backend/Actions/Salesflare/Routes.php b/backend/Actions/Salesflare/Routes.php index f6148765a..2390a216b 100644 --- a/backend/Actions/Salesflare/Routes.php +++ b/backend/Actions/Salesflare/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Salesflare\SalesflareController; use BitApps\Integrations\Core\Util\Route; -Route::post('salesflare_authentication', [SalesflareController::class, 'authentication']); Route::post('Salesflare_custom_fields', [SalesflareController::class, 'customFields']); Route::post('Salesflare_fetch_all_tags', [SalesflareController::class, 'getAllTags']); Route::post('Salesflare_fetch_all_account', [SalesflareController::class, 'getAllAccounts']); diff --git a/backend/Actions/Salesflare/SalesflareController.php b/backend/Actions/Salesflare/SalesflareController.php index d0292ff41..cdb574613 100644 --- a/backend/Actions/Salesflare/SalesflareController.php +++ b/backend/Actions/Salesflare/SalesflareController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Salesflare; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,27 +15,20 @@ */ class SalesflareController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'salesflare', + 'fields' => [ + 'api_key' => 'token', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; protected $domain; - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->setApiEndpoint() . '/accounts'; - $headers = $this->setHeaders($apiKey); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (!isset($response->error)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function customFields($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams, $fieldsRequestParams->action_name); diff --git a/backend/Actions/SendGrid/Routes.php b/backend/Actions/SendGrid/Routes.php index f639a9bde..8469e4be0 100644 --- a/backend/Actions/SendGrid/Routes.php +++ b/backend/Actions/SendGrid/Routes.php @@ -7,5 +7,5 @@ use BitApps\Integrations\Actions\SendGrid\SendGridController; use BitApps\Integrations\Core\Util\Route; -Route::post('sendGrid_authentication', [SendGridController::class, 'authentication']); +Route::post('sendGrid_fetch_custom_fields', [SendGridController::class, 'getCustomFields']); Route::post('sendGrid_fetch_all_lists', [SendGridController::class, 'getLists']); diff --git a/backend/Actions/SendGrid/SendGridController.php b/backend/Actions/SendGrid/SendGridController.php index 3854ac84f..e6d24b054 100644 --- a/backend/Actions/SendGrid/SendGridController.php +++ b/backend/Actions/SendGrid/SendGridController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\SendGrid; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,7 +15,15 @@ */ class SendGridController { - public function authentication($fieldsRequestParams) + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'sendgrid', + 'fields' => [ + 'apiKey' => 'token', + ], + ]; + + public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->apiKey)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); @@ -28,6 +37,7 @@ public function authentication($fieldsRequestParams) $response = HttpHelper::get($apiEndpoints, null, $header); if (!isset($response->errors)) { + $customFields = []; foreach ($response->custom_fields as $customField) { $customFields[] = [ 'key' => $customField->id, @@ -37,7 +47,7 @@ public function authentication($fieldsRequestParams) } wp_send_json_success($customFields, 200); } else { - wp_send_json_error($response->errors[0]->message ?? __('Please enter valid API key', 'bit-integrations'), 400); + wp_send_json_error($response->errors[0]->message ?? __('Custom fields fetch failed', 'bit-integrations'), 400); } } diff --git a/backend/Actions/Zendesk/Routes.php b/backend/Actions/Zendesk/Routes.php index dbec0f977..05e3e38fe 100644 --- a/backend/Actions/Zendesk/Routes.php +++ b/backend/Actions/Zendesk/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Zendesk\ZendeskController; use BitApps\Integrations\Core\Util\Route; -Route::post('zendesk_authentication', [ZendeskController::class, 'authentication']); Route::post('zendesk_fetch_custom_fields', [ZendeskController::class, 'getCustomFields']); Route::post('zendesk_fetch_all_leads', [ZendeskController::class, 'getAllLeads']); Route::post('zendesk_fetch_all_parentOrganizations', [ZendeskController::class, 'getAllParentOrganizations']); diff --git a/backend/Actions/Zendesk/ZendeskController.php b/backend/Actions/Zendesk/ZendeskController.php index b158d8eb2..7db24d4b6 100644 --- a/backend/Actions/Zendesk/ZendeskController.php +++ b/backend/Actions/Zendesk/ZendeskController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Zendesk; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class ZendeskController { + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'zendesk', + 'fields' => [ + 'api_key' => 'token', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,27 +32,6 @@ public function __construct() $this->apiEndpoint = 'https://api.getbase.com/v2/'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->apiEndpoint . 'accounts/self'; - $headers = [ - 'Authorization' => 'Bearer ' . $apiKey, - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response->data)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/frontend/src/components/AllIntegrations/Flowlu/FlowluAuthorization.jsx b/frontend/src/components/AllIntegrations/Flowlu/FlowluAuthorization.jsx index f3d61f9f9..f276e2aa4 100644 --- a/frontend/src/components/AllIntegrations/Flowlu/FlowluAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Flowlu/FlowluAuthorization.jsx @@ -1,158 +1,50 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { toast } from 'react-hot-toast' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { flowluAuthentication } from './FlowluCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function FlowluAuthorization({ flowluConf, setFlowluConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', company_name: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !flowluConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...flowluConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setFlowluConf(newConf) - } - - const handleSessionTokenLink = () => { - flowluConf.company_name - ? window.open( - `https://${flowluConf.company_name}.flowlu.com/cabinet/all_settings?section=api`, - '_blank', - 'noreferrer' - ) - : toast.error(__('Company Name is required!', 'bit-integrations')) - } - - const ActiveInstructions = ` -

${__('Get the API Key', 'bit-integrations')}

-
    -
  • ${__('First go to your Flowlu dashboard.', 'bit-integrations')}
  • -
  • ${__('Click go to your "Profile" from Right top corner', 'bit-integrations')}
  • -
  • ${__('Then Click "Portal Settings"', 'bit-integrations')}
  • -
  • ${__('Click go to "API Settings" from "Main Settings"', 'bit-integrations')}
  • -
  • ${__('Then click "create", Then Copy', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get the API Key', 'bit-integrations')}

+
    +
  • ${__('First go to your Flowlu dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Profile from the top-right corner.', 'bit-integrations')}
  • +
  • ${__('Open Portal Settings, then API Settings.', 'bit-integrations')}
  • +
  • ${__('Create and copy your API key.', 'bit-integrations')}
  • +
  • ${__('Use your workspace subdomain as Company Name.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- -
- {__('Company Name:', 'bit-integrations')} -
- -
-
https://
-
- -
-
.flowlu.com
-
-
{error.company_name}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Flowlu API Key', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Flowlu/FlowluCommonFunc.js b/frontend/src/components/AllIntegrations/Flowlu/FlowluCommonFunc.js index fa7748dc6..35a9f6364 100644 --- a/frontend/src/components/AllIntegrations/Flowlu/FlowluCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Flowlu/FlowluCommonFunc.js @@ -16,11 +16,18 @@ export const handleInput = (e, flowluConf, setFlowluConf) => { setFlowluConf({ ...newConf }) } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + company_name: confTmp.company_name + } + export const getAllFields = (flowluConf, setFlowluConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - api_key: flowluConf.api_key, - company_name: flowluConf.company_name, + ...buildAuthRequestParams(flowluConf), action_name: flowluConf.actionName, selectedAccountType: flowluConf?.selectedAccountType } @@ -79,49 +86,10 @@ export const checkMappedFields = flowluConf => { return true } -export const flowluAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key || !confTmp.company_name) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '', - company_name: !confTmp.company_name ? __("Company Name can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } - - bitsFetch(requestParams, 'flowlu_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API Key or Company Name', 'bit-integrations')) - }) -} - export const getAllAccountCategories = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, accountCategories: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_account_categories').then(result => { if (result && result.success) { @@ -143,10 +111,7 @@ export const getAllAccountCategories = (confTmp, setConf, setLoading) => { export const getAllIndustry = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, industry: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_industries').then(result => { if (result && result.success) { @@ -168,10 +133,7 @@ export const getAllIndustry = (confTmp, setConf, setLoading) => { export const getAllPipeline = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, pipeline: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_pipelines').then(result => { if (result && result.success) { @@ -194,8 +156,7 @@ export const getAllStage = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, stage: true }) const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name, + ...buildAuthRequestParams(confTmp), pipeline_id: confTmp.selectedPipeline } @@ -219,10 +180,7 @@ export const getAllStage = (confTmp, setConf, setLoading) => { export const getAllSource = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, source: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_sources').then(result => { if (result && result.success) { @@ -244,10 +202,7 @@ export const getAllSource = (confTmp, setConf, setLoading) => { export const getAllCustomer = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customer: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_customers').then(result => { if (result && result.success) { @@ -269,10 +224,7 @@ export const getAllCustomer = (confTmp, setConf, setLoading) => { export const getAllManagers = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, manager: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_managers').then(result => { if (result && result.success) { @@ -294,10 +246,7 @@ export const getAllManagers = (confTmp, setConf, setLoading) => { export const getAllProjectStage = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, projectStage: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_project_tages').then(result => { if (result && result.success) { @@ -319,10 +268,7 @@ export const getAllProjectStage = (confTmp, setConf, setLoading) => { export const getAllPortfolio = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, portfolio: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_portfolio').then(result => { if (result && result.success) { @@ -344,10 +290,7 @@ export const getAllPortfolio = (confTmp, setConf, setLoading) => { export const getAllProjectOpportunity = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, projectOpportunity: true }) - const requestParams = { - api_key: confTmp.api_key, - company_name: confTmp.company_name - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'flowlu_fetch_all_project_opportunity').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx b/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx index c6dbb2f6b..d27b38980 100644 --- a/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx @@ -1,156 +1,52 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { gravitecAuthentication } from './GravitecCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function GravitecAuthorization({ gravitecConf, setGravitecConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ site_url: '', app_key: '', app_secret: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !gravitecConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...gravitecConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setGravitecConf(newConf) - } - - const ActiveInstructions = ` -

${__('To Get App key & App Secret', 'bit-integrations')}

-
    -
  • ${__('First go to your Gravitec dashboard.', 'bit-integrations')}
  • -
  • ${__('Click go to your "YOUR SITES" from left SideBar', 'bit-integrations')}
  • -
  • ${__('Then click "Settings"', 'bit-integrations')}
  • -
  • ${__('Then Click "REST API"', 'bit-integrations')}
  • -
` + const note = ` +

${__('To Get App key & App Secret', 'bit-integrations')}

+
    +
  • ${__('First go to your Gravitec dashboard.', 'bit-integrations')}
  • +
  • ${__('Open your site from the left sidebar.', 'bit-integrations')}
  • +
  • ${__('Open Settings, then REST API.', 'bit-integrations')}
  • +
  • ${__('Use App key as Username and App secret as Password here.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Site Url:', 'bit-integrations')} -
- -
{error.site_url}
- -
- {__('App key:', 'bit-integrations')} -
- -
{error.app_key}
- -
- {__('App Secret:', 'bit-integrations')} -
- -
{error.app_secret}
- - - {__('To Get App key & App Secret, Please Visit', 'bit-integrations')} -   - - {__('Gravitec App key & Secret', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Gravitec/GravitecCommonFunc.js b/frontend/src/components/AllIntegrations/Gravitec/GravitecCommonFunc.js index c61a77b58..ee1a2f640 100644 --- a/frontend/src/components/AllIntegrations/Gravitec/GravitecCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Gravitec/GravitecCommonFunc.js @@ -1,9 +1,3 @@ -/* 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, salesmateConf, setSalesmateConf) => { const newConf = { ...salesmateConf } const { name } = e.target @@ -40,41 +34,3 @@ export const checkMappedFields = gravitecConf => { } return true } - -export const gravitecAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.site_url || !confTmp.app_key || !confTmp.app_secret) { - setError({ - site_url: !confTmp.site_url ? __("Site Url can't be empty", 'bit-integrations') : '', - app_key: !confTmp.app_key ? __("App Key can't be empty", 'bit-integrations') : '', - app_secret: !confTmp.app_secret ? __("App Secret can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - site_url: confTmp.site_url, - app_key: confTmp.app_key, - app_secret: confTmp.app_secret - } - - bitsFetch(requestParams, 'gravitec_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid Public Id & Secret Key', 'bit-integrations')) - }) -} diff --git a/frontend/src/components/AllIntegrations/Salesflare/SalesflareAuthorization.jsx b/frontend/src/components/AllIntegrations/Salesflare/SalesflareAuthorization.jsx index 4b1194012..836380a40 100644 --- a/frontend/src/components/AllIntegrations/Salesflare/SalesflareAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Salesflare/SalesflareAuthorization.jsx @@ -1,117 +1,44 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { salesflareAuthentication } from './SalesflareCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function SalesflareAuthorization({ salesflareConf, setSalesflareConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_token: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !salesflareConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...salesflareConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSalesflareConf(newConf) - } - - const ActiveInstructions = ` -

${__('Get API Key', 'bit-integrations')}

-
    -
  • ${__('Go to your Salesflare user dashboard', 'bit-integrations')}
  • -
  • ${__('Then click "Settings"', 'bit-integrations')}
  • -
  • ${__('Then click "API Keys → Generates Keys"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get API Key', 'bit-integrations')}

+
    +
  • ${__('Go to your Salesflare user dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Settings.', 'bit-integrations')}
  • +
  • ${__('Open API Keys, then generate/copy your key.', 'bit-integrations')}
  • +
+ + ${__('To get API key, please visit', 'bit-integrations')} + + ${__('Salesflare API Key', 'bit-integrations')} + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To get API key, please visit', 'bit-integrations')} -   - - {__('Salesflare API Key', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Salesflare/SalesflareCommonFunc.js b/frontend/src/components/AllIntegrations/Salesflare/SalesflareCommonFunc.js index 6b5ec127a..63b6e2307 100644 --- a/frontend/src/components/AllIntegrations/Salesflare/SalesflareCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Salesflare/SalesflareCommonFunc.js @@ -44,37 +44,17 @@ export const checkMappedFields = salesflareConf => { return true } -export const salesflareAuthentication = (confTmp, setError, setIsAuthorized, loading, setLoading) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key - } - - bitsFetch(requestParams, 'salesflare_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API Key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key + } export const salesflareFields = (salesflareConf, setSalesflareConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - api_key: salesflareConf.api_key, + ...buildAuthRequestParams(salesflareConf), action_name: salesflareConf.actionName } @@ -108,9 +88,7 @@ export const salesflareFields = (salesflareConf, setSalesflareConf, setIsLoading export const getAllTags = (salesflareConf, setSalesflareConf, setLoading) => { setLoading({ ...setLoading, tags: true }) - const requestParams = { - api_key: salesflareConf.api_key - } + const requestParams = buildAuthRequestParams(salesflareConf) bitsFetch(requestParams, 'Salesflare_fetch_all_tags').then(result => { if (result && result.success) { @@ -131,9 +109,7 @@ export const getAllTags = (salesflareConf, setSalesflareConf, setLoading) => { export const getallAccounts = (salesflareConf, setSalesflareConf, loading, setLoading) => { setLoading({ ...loading, account: true }) - const requestParams = { - api_key: salesflareConf.api_key - } + const requestParams = buildAuthRequestParams(salesflareConf) bitsFetch(requestParams, 'Salesflare_fetch_all_account').then(result => { if (result && result.success) { @@ -153,9 +129,7 @@ export const getallAccounts = (salesflareConf, setSalesflareConf, loading, setLo } export const getallPipelines = (salesflareConf, setSalesflareConf, loading, setLoading) => { setLoading({ ...loading, pipeline: true }) - const requestParams = { - api_key: salesflareConf.api_key - } + const requestParams = buildAuthRequestParams(salesflareConf) bitsFetch(requestParams, 'Salesflare_fetch_all_pipelines').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/SendGrid/SendGridAuthorization.jsx b/frontend/src/components/AllIntegrations/SendGrid/SendGridAuthorization.jsx index 5286746c0..e1547310b 100644 --- a/frontend/src/components/AllIntegrations/SendGrid/SendGridAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendGrid/SendGridAuthorization.jsx @@ -1,11 +1,9 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { sendGridAuthentication } from './SendGridCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchSendGridCustomFields } from './SendGridCommonFunc' export default function SendGridAuthorization({ sendGridConf, @@ -16,105 +14,50 @@ export default function SendGridAuthorization({ setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ name: '', secretKey: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const refreshCustomFields = useCallback( + connectionId => { + const nextConf = connectionId ? { ...sendGridConf, connection_id: connectionId } : sendGridConf - !sendGridConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...sendGridConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSendGridConf(newConf) - } + fetchSendGridCustomFields(nextConf, setSendGridConf, loading, setLoading) + }, + [loading, sendGridConf, setLoading, setSendGridConf] + ) - return ( -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !sendGridConf?.customFields?.length) { + refreshCustomFields() + } -
- {__('Integration Name:', 'bit-integrations')} -
- + setStep(value) + }, + [refreshCustomFields, sendGridConf?.customFields?.length, setStep] + ) -
- {__('API Key:', 'bit-integrations')} -
- -
- {error.apiKey} -
- - {__('To Get API key & Secret Key, Please Visit', 'bit-integrations')} -   - - {__('SendGrid API Token', 'bit-integrations')} - - -
-
+ const note = ` + + ${__('To Get API key, Please Visit', 'bit-integrations')} + + ${__('SendGrid API Token', 'bit-integrations')} + + ` - {!isInfo && ( -
- -
- -
- )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/SendGrid/SendGridCommonFunc.js b/frontend/src/components/AllIntegrations/SendGrid/SendGridCommonFunc.js index 70c580750..0aac6f1cd 100644 --- a/frontend/src/components/AllIntegrations/SendGrid/SendGridCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SendGrid/SendGridCommonFunc.js @@ -37,60 +37,38 @@ export const checkMappedFields = sendGridConf => { return true } -export const sendGridAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading, - type -) => { - if (!confTmp.apiKey) { - setError({ apiKey: !confTmp.apiKey ? __("API key can't be empty", 'bit-integrations') : '' }) - return - } +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + apiKey: confTmp.apiKey + } - setError({}) +export const fetchSendGridCustomFields = (confTmp, setConf, loading, setLoading) => { + const requestParams = buildAuthRequestParams(confTmp) + setLoading({ ...loading, customFields: true }) - if (type === 'authentication') { - setLoading({ ...loading, auth: true }) - } - if (type === 'refreshLists') { - setLoading({ ...loading, customFields: true }) - } - const requestParams = { apiKey: confTmp.apiKey } - - bitsFetch(requestParams, 'sendGrid_authentication').then(result => { + bitsFetch(requestParams, 'sendGrid_fetch_custom_fields').then(result => { if (result && result.success) { const newConf = { ...confTmp } - setIsAuthorized(true) - if (type === 'authentication') { - if (result.data) { - newConf.customFields = result.data - } + + if (result.data) { + newConf.customFields = result.data setConf(newConf) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if (type === 'refreshLists') { - if (result.data) { - newConf.customFields = result.data - setConf(newConf) - } - setLoading({ ...loading, customFields: false }) - toast.success(__('Custom fields fetched successfully', 'bit-integrations')) } + setLoading({ ...loading, customFields: false }) + toast.success(__('Custom fields fetched successfully', 'bit-integrations')) return } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorization Failed', 'bit-integrations')) + setLoading({ ...loading, customFields: false }) + toast.error(__('Custom fields fetching failed', 'bit-integrations')) }) } export const getLists = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, lists: true }) - const requestParams = { apiKey: confTmp.apiKey } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'sendGrid_fetch_all_lists').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/SendGrid/SendGridIntegLayout.jsx b/frontend/src/components/AllIntegrations/SendGrid/SendGridIntegLayout.jsx index 35791fe9a..dae31d337 100644 --- a/frontend/src/components/AllIntegrations/SendGrid/SendGridIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/SendGrid/SendGridIntegLayout.jsx @@ -1,12 +1,11 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react-hooks/exhaustive-deps */ -import { useState } from 'react' import MultiSelect from 'react-multiple-select-dropdown-lite' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import SendGridActions from './SendGridActions' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { sendGridAuthentication } from './SendGridCommonFunc' +import { fetchSendGridCustomFields } from './SendGridCommonFunc' import SendGridFieldMap from './SendGridFieldMap' import { addFieldMap } from './IntegrationHelpers' @@ -18,9 +17,6 @@ export default function SendGridIntegLayout({ setLoading, setSnackbar }) { - const [error, setError] = useState({ name: '', auth_token: '' }) - const [isAuthorized, setIsAuthorized] = useState(false) - const setChanges = val => { const newConf = { ...sendGridConf } newConf.selectedLists = val @@ -33,17 +29,7 @@ export default function SendGridIntegLayout({ {__('Field Map', 'bit-integrations')} -
- - - )} - + ) } diff --git a/frontend/src/components/AllIntegrations/Zendesk/ZendeskCommonFunc.js b/frontend/src/components/AllIntegrations/Zendesk/ZendeskCommonFunc.js index 4f42c6a4d..58bd69406 100644 --- a/frontend/src/components/AllIntegrations/Zendesk/ZendeskCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Zendesk/ZendeskCommonFunc.js @@ -51,43 +51,18 @@ export const checkMappedFields = zendeskConf => { return true } -export const zendeskAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'zendesk_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API key', 'bit-integrations')) - }) -} +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key + } export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customFields: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action: confTmp.actionName } @@ -114,7 +89,7 @@ export const getCustomFields = (confTmp, setConf, setLoading) => { export const getAllLeads = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, leads: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'zendesk_fetch_all_leads').then(result => { if (result && result.success) { @@ -136,7 +111,7 @@ export const getAllLeads = (confTmp, setConf, setLoading) => { export const getAllParentOrganizations = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, parentOrganizations: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'zendesk_fetch_all_parentOrganizations').then(result => { if (result && result.success) { @@ -158,7 +133,7 @@ export const getAllParentOrganizations = (confTmp, setConf, setLoading) => { export const getAllTeams = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, teams: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'zendesk_fetch_all_teams').then(result => { if (result && result.success) { @@ -180,9 +155,7 @@ export const getAllTeams = (confTmp, setConf, setLoading) => { export const getAllCurrencies = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, currencies: true }) - const requestParams = { - api_key: confTmp.api_key - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'zendesk_fetch_all_currencies').then(result => { if (result && result.success) { @@ -205,7 +178,7 @@ export const getAllStages = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, stages: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -230,7 +203,7 @@ export const getAllCRMCompanies = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMCompanies: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -263,7 +236,7 @@ export const getAllCRMContacts = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMContacts: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -288,7 +261,7 @@ export const getAllCRMSources = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMSources: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } From 90dc1e030294553b10b3010869a98eb50e1f164a Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 17:12:46 +0600 Subject: [PATCH 35/58] refactor: authentication methods and integrate AuthorizationType for ClinchPad, CopperCRM, Insightly, and SystemeIO - Removed authentication methods from ClinchPadController, CopperCRMController, InsightlyController, and SystemeIOController. - Added static authConfig arrays to each controller to define authentication type and required fields. - Updated routes to remove authentication endpoints for ClinchPad, CopperCRM, Insightly, and SystemeIO. - Refactored frontend components for ClinchPad, CopperCRM, and Insightly to use a new Authorization component for handling authentication. - Simplified request parameter building in common functions for each integration. --- .../Actions/ClinchPad/ClinchPadController.php | 30 +--- backend/Actions/ClinchPad/Routes.php | 1 - .../Actions/CopperCRM/CopperCRMController.php | 35 ++-- backend/Actions/CopperCRM/Routes.php | 1 - .../Actions/Insightly/InsightlyController.php | 32 ++-- backend/Actions/Insightly/Routes.php | 1 - backend/Actions/SystemeIO/Routes.php | 1 - .../Actions/SystemeIO/SystemeIOController.php | 23 +-- .../ClinchPad/ClinchPadAuthorization.jsx | 140 ++++----------- .../ClinchPad/ClinchPadCommonFunc.js | 43 +---- .../CopperCRM/CopperCRMAuthorization.jsx | 159 +++++------------- .../CopperCRM/CopperCRMCommonFunc.js | 78 ++------- .../Insightly/InsightlyAuthorization.jsx | 156 ++++------------- .../Insightly/InsightlyCommonFunc.js | 58 ++----- .../SystemeIO/SystemeIOCommonFunc.js | 47 +----- 15 files changed, 198 insertions(+), 607 deletions(-) diff --git a/backend/Actions/ClinchPad/ClinchPadController.php b/backend/Actions/ClinchPad/ClinchPadController.php index f52b4748c..650c0c242 100644 --- a/backend/Actions/ClinchPad/ClinchPadController.php +++ b/backend/Actions/ClinchPad/ClinchPadController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ClinchPad; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class ClinchPadController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'clinchpad', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,27 +32,6 @@ public function __construct() $this->apiEndpoint = 'https://www.clinchpad.com/api/v1'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = $this->apiEndpoint . '/users'; - $headers = [ - 'Authorization' => 'Basic ' . base64_encode("api-key:{$apiKey}") - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getAllParentOrganizations($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/ClinchPad/Routes.php b/backend/Actions/ClinchPad/Routes.php index 4c2735431..012a54786 100644 --- a/backend/Actions/ClinchPad/Routes.php +++ b/backend/Actions/ClinchPad/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ClinchPad\ClinchPadController; use BitApps\Integrations\Core\Util\Route; -Route::post('clinchPad_authentication', [ClinchPadController::class, 'authentication']); Route::post('clinchPad_fetch_custom_fields', [ClinchPadController::class, 'getCustomFields']); Route::post('clinchPad_fetch_all_leads', [ClinchPadController::class, 'getAllLeads']); Route::post('clinchPad_fetch_all_parentOrganizations', [ClinchPadController::class, 'getAllParentOrganizations']); diff --git a/backend/Actions/CopperCRM/CopperCRMController.php b/backend/Actions/CopperCRM/CopperCRMController.php index 0a0747fb7..29b8c7b7c 100644 --- a/backend/Actions/CopperCRM/CopperCRMController.php +++ b/backend/Actions/CopperCRM/CopperCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\CopperCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,15 @@ */ class CopperCRMController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'coppercrm', + 'fields' => [ + 'api_key' => 'value', + 'api_email' => 'api_email', + ], + ]; + protected $_defaultHeader; protected $apiEndpoint; @@ -23,31 +33,6 @@ public function __construct() $this->apiEndpoint = 'https://api.copper.com/developer_api/v1'; } - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiKey = $fieldsRequestParams->api_key; - $apiEmail = $fieldsRequestParams->api_email; - $apiEndpoint = $this->apiEndpoint . '/account'; - $headers = [ - 'X-PW-AccessToken' => $apiKey, - 'X-PW-Application' => 'developer_api', - 'X-PW-UserEmail' => $apiEmail, - 'Content-Type' => 'application/json' - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (!isset($response->error)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API key', 'bit-integrations'), 400); - } - } - public function getCustomFields($fieldsRequestParams) { if (empty($fieldsRequestParams->api_key)) { diff --git a/backend/Actions/CopperCRM/Routes.php b/backend/Actions/CopperCRM/Routes.php index cea1b67d7..eaff590b0 100644 --- a/backend/Actions/CopperCRM/Routes.php +++ b/backend/Actions/CopperCRM/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\CopperCRM\CopperCRMController; use BitApps\Integrations\Core\Util\Route; -Route::post('coppercrm_authentication', [CopperCRMController::class, 'authentication']); Route::post('coppercrm_fetch_custom_fields', [CopperCRMController::class, 'getCustomFields']); Route::post('coppercrm_fetch_all_opportunities', [CopperCRMController::class, 'getAllOpportunities']); Route::post('coppercrm_fetch_all_owners', [CopperCRMController::class, 'getAllOwners']); diff --git a/backend/Actions/Insightly/InsightlyController.php b/backend/Actions/Insightly/InsightlyController.php index 8b8aca2d9..c977cea7c 100644 --- a/backend/Actions/Insightly/InsightlyController.php +++ b/backend/Actions/Insightly/InsightlyController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Insightly; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,29 +15,16 @@ */ class InsightlyController { - protected $_defaultHeader; - - public function authentication($fieldsRequestParams) - { - if (empty($fieldsRequestParams->api_key) || empty($fieldsRequestParams->api_url)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiUrl = $fieldsRequestParams->api_url; - $apiKey = $fieldsRequestParams->api_key; - $apiEndpoint = 'https://api.' . $apiUrl . '/v3.1/Users'; - $headers = [ - 'Authorization' => 'Basic ' . base64_encode("{$apiKey}:"), - ]; - - $response = HttpHelper::get($apiEndpoint, null, $headers); + public static array $authConfig = [ + 'authType' => AuthorizationType::BASIC_AUTH, + 'slug' => 'insightly', + 'fields' => [ + 'api_key' => 'username', + 'api_url' => 'api_url', + ], + ]; - if (\is_array($response) && isset($response[0]->USER_ID)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API URL & API key', 'bit-integrations'), 400); - } - } + protected $_defaultHeader; public function getAllOrganisations($fieldsRequestParams) { diff --git a/backend/Actions/Insightly/Routes.php b/backend/Actions/Insightly/Routes.php index eebb96fcd..28d433d48 100644 --- a/backend/Actions/Insightly/Routes.php +++ b/backend/Actions/Insightly/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Insightly\InsightlyController; use BitApps\Integrations\Core\Util\Route; -Route::post('insightly_authentication', [InsightlyController::class, 'authentication']); Route::post('insightly_fetch_all_organisations', [InsightlyController::class, 'getAllOrganisations']); Route::post('insightly_fetch_all_categories', [InsightlyController::class, 'getAllCategories']); Route::post('insightly_fetch_all_statuses', [InsightlyController::class, 'getAllStatuses']); diff --git a/backend/Actions/SystemeIO/Routes.php b/backend/Actions/SystemeIO/Routes.php index e0a3897b6..9cb7c6541 100644 --- a/backend/Actions/SystemeIO/Routes.php +++ b/backend/Actions/SystemeIO/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\SystemeIO\SystemeIOController; use BitApps\Integrations\Core\Util\Route; -Route::post('systemeIO_authentication', [SystemeIOController::class, 'authentication']); Route::post('systemeIO_fetch_all_fields', [SystemeIOController::class, 'getAllFields']); Route::post('systemeIO_fetch_all_tags', [SystemeIOController::class, 'getAllTags']); diff --git a/backend/Actions/SystemeIO/SystemeIOController.php b/backend/Actions/SystemeIO/SystemeIOController.php index 33c185a70..4f3f5cfc2 100644 --- a/backend/Actions/SystemeIO/SystemeIOController.php +++ b/backend/Actions/SystemeIO/SystemeIOController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\SystemeIO; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class SystemeIOController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'systemeio', + 'fields' => [ + 'api_key' => 'value', + ], + ]; + protected $_defaultHeader; protected $_apiEndpoint; @@ -23,20 +32,6 @@ public function __construct() $this->_apiEndpoint = 'https://api.systeme.io/api'; } - public function authentication($fieldsRequestParams) - { - $this->checkValidation($fieldsRequestParams); - $this->setHeaders($fieldsRequestParams->api_key); - $apiEndpoint = $this->_apiEndpoint . '/contacts'; - $response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader); - - if (isset($response->items)) { - wp_send_json_success(__('Authentication successful', 'bit-integrations'), 200); - } else { - wp_send_json_error(__('Please enter valid API Key & API Secret', 'bit-integrations'), 400); - } - } - public function getAllTags($fieldsRequestParams) { $this->checkValidation($fieldsRequestParams); diff --git a/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadAuthorization.jsx b/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadAuthorization.jsx index 639d49539..911c18cca 100644 --- a/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadAuthorization.jsx @@ -1,118 +1,50 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { clinchPadAuthentication } from './ClinchPadCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function ClinchPadAuthorization({ clinchPadConf, setClinchPadConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !clinchPadConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...clinchPadConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setClinchPadConf(newConf) - } + const note = ` +

${__('Get API token', 'bit-integrations')}

+
    +
  • ${__('Go to your ClinchPad account settings.', 'bit-integrations')}
  • +
  • ${__('Open API token section and copy the token.', 'bit-integrations')}
  • +
  • ${__('Paste the token and authorize.', 'bit-integrations')}
  • +
+ + ${__('To get API token, visit', 'bit-integrations')} + + ${__('ClinchPad Settings', 'bit-integrations')} + + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('ClinchPad API Token', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} -
+ ({ + Authorization: `Basic ${btoa(`api-key:${authData.api_key || ''}`)}` + }) + }} + noteDetails={{ note }} + /> ) } diff --git a/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadCommonFunc.js b/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadCommonFunc.js index 061861e89..1ce72b970 100644 --- a/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ClinchPad/ClinchPadCommonFunc.js @@ -4,6 +4,11 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { api_key: confTmp.api_key } + export const handleInput = (e, clinchPadConf, setClinchPadConf) => { const newConf = { ...clinchPadConf } const { name } = e.target @@ -49,42 +54,10 @@ export const checkMappedFields = clinchPadConf => { return true } -export const clinchPadAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key } - - bitsFetch(requestParams, 'clinchPad_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid API key', 'bit-integrations')) - }) -} - export const getAllParentOrganizations = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, parentOrganizations: true }) - const requestParams = { api_key: confTmp.api_key } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'clinchPad_fetch_all_parentOrganizations').then(result => { if (result && result.success) { @@ -107,7 +80,7 @@ export const getAllCRMPipelines = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMPipelines: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -132,7 +105,7 @@ export const getAllCRMContacts = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMContacts: true }) const requestParams = { - api_key: confTmp.api_key, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } diff --git a/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMAuthorization.jsx index 5b84e2ba2..ecc17a211 100644 --- a/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMAuthorization.jsx @@ -1,132 +1,55 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import { coppercrmAuthentication } from './CopperCRMCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function CopperCRMAuthorization({ copperCRMConf, setCopperCRMConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', api_email: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !copperCRMConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...copperCRMConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCopperCRMConf(newConf) - } - const ActiveInstructions = ` -

${__('Get api secret key', 'bit-integrations')}

-
    -
  • ${__('First go to your Copper dashboard.', 'bit-integrations')}
  • -
  • ${__('Then click Settings from Navbar.', 'bit-integrations')}
  • -
  • ${__('Click "Integrations", Then click "API Keys"', 'bit-integrations')}
  • -
` + const note = ` +

${__('Get API credentials', 'bit-integrations')}

+
    +
  • ${__('Go to your Copper dashboard.', 'bit-integrations')}
  • +
  • ${__('Open Settings > Integrations > API Keys.', 'bit-integrations')}
  • +
  • ${__('Copy your API key and account email, then authorize.', 'bit-integrations')}
  • +
+ ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Your API Email:', 'bit-integrations')} -
- -
{error.api_email}
- {/* - {__('Example: {name}.coppercrm.com', 'bit-integrations')} - */} -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMCommonFunc.js b/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMCommonFunc.js index 88f32d758..6896c1f90 100644 --- a/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/CopperCRM/CopperCRMCommonFunc.js @@ -4,6 +4,14 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + api_email: confTmp.api_email + } + export const handleInput = (e, copperCRMConf, setCopperCRMConf) => { const newConf = { ...copperCRMConf } const { name } = e.target @@ -51,48 +59,11 @@ export const checkMappedFields = copperCRMConf => { return true } -export const coppercrmAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_email || !confTmp.api_key) { - setError({ - api_email: !confTmp.api_email ? __("API Email can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } - - bitsFetch(requestParams, 'coppercrm_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid api_email name & API key', 'bit-integrations')) - }) -} - export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, customFields: true }) const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email, + ...buildAuthRequestParams(confTmp), action: confTmp.actionName } @@ -119,10 +90,7 @@ export const getCustomFields = (confTmp, setConf, setLoading) => { export const getAllOpportunities = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, opportunities: true }) - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'coppercrm_fetch_all_opportunities').then(result => { if (result && result.success) { @@ -144,10 +112,7 @@ export const getAllOpportunities = (confTmp, setConf, setLoading) => { export const getAllOwners = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, owners: true }) - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'coppercrm_fetch_all_owners').then(result => { if (result && result.success) { @@ -169,10 +134,7 @@ export const getAllOwners = (confTmp, setConf, setLoading) => { export const getAllCompanies = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, companies: true }) - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'coppercrm_fetch_all_companies').then(result => { if (result && result.success) { @@ -194,10 +156,7 @@ export const getAllCompanies = (confTmp, setConf, setLoading) => { export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tags: true }) - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'coppercrm_fetch_all_tags').then(result => { if (result && result.success) { @@ -220,8 +179,7 @@ export const getAllPipelineStages = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, pipelineStages: true }) const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -246,8 +204,7 @@ export const getAllCRMPeoples = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMPeoples: true }) const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -271,10 +228,7 @@ export const getAllCRMPeoples = (confTmp, setConf, setLoading) => { export const getAllCRMPipelines = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMPipelines: true }) - const requestParams = { - api_key: confTmp.api_key, - api_email: confTmp.api_email - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'coppercrm_fetch_all_CRMPipelines').then(result => { if (result && result.success) { diff --git a/frontend/src/components/AllIntegrations/Insightly/InsightlyAuthorization.jsx b/frontend/src/components/AllIntegrations/Insightly/InsightlyAuthorization.jsx index d1c84e565..18a8c3165 100644 --- a/frontend/src/components/AllIntegrations/Insightly/InsightlyAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Insightly/InsightlyAuthorization.jsx @@ -1,133 +1,51 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { insightlyAuthentication } from './InsightlyCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' export default function InsightlyAuthorization({ insightlyConf, setInsightlyConf, step, setStep, - loading, - setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '', api_url: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !insightlyConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...insightlyConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setInsightlyConf(newConf) - } + const note = ` +

${__('Get Insightly API credentials', 'bit-integrations')}

+
    +
  • ${__('Open your Insightly account settings.', 'bit-integrations')}
  • +
  • ${__('Copy your API key and account host (without https://api.).', 'bit-integrations')}
  • +
  • ${__('Use API key in Username field, leave password empty, then authorize.', 'bit-integrations')}
  • +
+ + ${__('Example host:', 'bit-integrations')} name.insightly.com + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Your API URL:', 'bit-integrations')} -
- -
{error.api_url}
- {__('Example: {name}.insightly.com', 'bit-integrations')} -
- {__('API Key:', 'bit-integrations')} -
- -
{error.api_key}
- {insightlyConf.api_url && ( - - {__('To Get API Token, Please Visit', 'bit-integrations')} -   - - {__('Insightly API Token', 'bit-integrations')} - - - )} -
-
- - {!isInfo && ( -
- -
- -
- )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Insightly/InsightlyCommonFunc.js b/frontend/src/components/AllIntegrations/Insightly/InsightlyCommonFunc.js index 568aede59..3b5328b6d 100644 --- a/frontend/src/components/AllIntegrations/Insightly/InsightlyCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Insightly/InsightlyCommonFunc.js @@ -4,6 +4,14 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { + api_key: confTmp.api_key, + api_url: confTmp.api_url + } + export const handleInput = (e, insightlyConf, setInsightlyConf) => { const newConf = { ...insightlyConf } const { name } = e.target @@ -61,43 +69,10 @@ export const checkRequiredFields = insightlyConf => { return false } -export const insightlyAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_url || !confTmp.api_key) { - setError({ - api_url: !confTmp.api_url ? __("API URL can't be empty", 'bit-integrations') : '', - api_key: !confTmp.api_key ? __("Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } - - bitsFetch(requestParams, 'insightly_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid api_url name & API key', 'bit-integrations')) - }) -} - export const getAllOrganisations = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, organisations: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'insightly_fetch_all_organisations').then(result => { if (result && result.success) { @@ -120,8 +95,7 @@ export const getAllCategories = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, categories: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -145,7 +119,7 @@ export const getAllCategories = (confTmp, setConf, setLoading) => { export const getAllStatuses = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, statuses: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'insightly_fetch_all_statuses').then(result => { if (result && result.success) { @@ -167,7 +141,7 @@ export const getAllStatuses = (confTmp, setConf, setLoading) => { export const getLeadSources = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, LeadSources: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'insightly_fetch_all_LeadSources').then(result => { if (result && result.success) { @@ -194,7 +168,7 @@ export const getLeadSources = (confTmp, setConf, setLoading) => { export const getLeadStatuses = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, LeadStatues: true }) - const requestParams = { api_key: confTmp.api_key, api_url: confTmp.api_url } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'insightly_fetch_all_LeadStatuses').then(result => { if (result && result.success) { @@ -223,8 +197,7 @@ export const getAllCRMPipelines = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMPipelines: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), action_name: confTmp.actionName } @@ -249,8 +222,7 @@ export const getAllCRMPipelineStages = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, CRMPipelineStages: true }) const requestParams = { - api_key: confTmp.api_key, - api_url: confTmp.api_url, + ...buildAuthRequestParams(confTmp), selectedCRMPipeline: confTmp.selectedCRMPipeline } diff --git a/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOCommonFunc.js b/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOCommonFunc.js index 526d1c1e3..554be3ffe 100644 --- a/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SystemeIO/SystemeIOCommonFunc.js @@ -4,6 +4,11 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = confTmp => + confTmp.connection_id + ? { connection_id: confTmp.connection_id } + : { api_key: confTmp.api_key } + export const handleInput = (e, salesmateConf, setSalesmateConf) => { const newConf = { ...salesmateConf } const { name } = e.target @@ -41,46 +46,10 @@ export const checkMappedFields = systemeIOConf => { return true } -export const systemeIOAuthentication = ( - confTmp, - setConf, - setError, - setIsAuthorized, - loading, - setLoading -) => { - if (!confTmp.api_key) { - setError({ - api_key: !confTmp.api_key ? __("API Key can't be empty", 'bit-integrations') : '' - }) - return - } - - setError({}) - setLoading({ ...loading, auth: true }) - - const requestParams = { - api_key: confTmp.api_key - } - - bitsFetch(requestParams, 'systemeIO_authentication').then(result => { - if (result && result.success) { - setIsAuthorized(true) - setLoading({ ...loading, auth: false }) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setLoading({ ...loading, auth: false }) - toast.error(__('Authorized failed, Please enter valid Sub Domain & API Key', 'bit-integrations')) - }) -} - export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tag: true }) - const requestParams = { - api_key: confTmp.api_key - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'systemeIO_fetch_all_tags').then(result => { if (result && result.success) { @@ -106,9 +75,7 @@ export const getAllTags = (confTmp, setConf, setLoading) => { export const getAllFields = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, fields: true }) - const requestParams = { - api_key: confTmp.api_key - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'systemeIO_fetch_all_fields').then(result => { if (result && result.success) { From 8d09575305cc27c849db9fc00f98c415c4d330f0 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 17:29:04 +0600 Subject: [PATCH 36/58] refactor: Mautic and Salesforce integration components - Removed unused token generation functions from Mautic and Salesforce controllers. - Updated Mautic and Salesforce routes to remove deprecated token generation endpoints. - Refactored Mautic and Salesforce authorization components to use a unified Authorization component. - Simplified field fetching logic in Mautic and Salesforce by introducing a helper function to build request parameters. - Improved token expiration handling in Salesforce controller. - Cleaned up unused imports and state management in Mautic and Salesforce components. --- backend/Actions/Mautic/MauticController.php | 91 ++++---- backend/Actions/Mautic/Routes.php | 2 - backend/Actions/Salesforce/Routes.php | 1 - .../Salesforce/SalesforceController.php | 82 +++---- .../AllIntegrations/Mautic/Mautic.jsx | 10 +- .../Mautic/MauticAuthorization.jsx | 210 +++++------------ .../Mautic/MauticCommonFunc.js | 152 ++---------- .../AllIntegrations/Salesforce/Salesforce.jsx | 7 +- .../Salesforce/SalesforceAuthorization.jsx | 172 +++----------- .../Salesforce/SalesforceCommonFunc.js | 221 +++--------------- 10 files changed, 218 insertions(+), 730 deletions(-) diff --git a/backend/Actions/Mautic/MauticController.php b/backend/Actions/Mautic/MauticController.php index 85e31f71b..939fd5c49 100644 --- a/backend/Actions/Mautic/MauticController.php +++ b/backend/Actions/Mautic/MauticController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Mautic; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,17 @@ */ class MauticController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'mautic', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + 'baseUrl' => 'baseUrl', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -21,51 +33,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params for generate token - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->baseUrl) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $baseUrl = $requestsParams->baseUrl; - $apiEndpoint = "{$baseUrl}/oauth/v2/token"; - $authorizationHeader['Content-Type'] = 'application/x-www-form-urlencoded'; - $requestParams = [ - 'code' => $requestsParams->code, - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => $requestsParams->redirectURI, - 'grant_type' => 'authorization_code' - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams, $authorizationHeader); - if (is_wp_error($apiResponse) || !empty($apiResponse->errors)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - /** * Process ajax request for refresh Mautic Audience Fields * @@ -86,7 +53,7 @@ public static function getAllFields($queryParams) } $mauticUrl = $queryParams->baseUrl; $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (static::isTokenExpired($queryParams->tokenDetails)) { $response['tokenDetails'] = static::_refreshAccessToken($queryParams); } $tokenDetails = empty($response['tokenDetails']) ? $queryParams->tokenDetails : $response['tokenDetails']; @@ -121,7 +88,7 @@ public static function getAllTags($queryParams) } $mauticUrl = $queryParams->baseUrl; $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (static::isTokenExpired($queryParams->tokenDetails)) { $response['tokenDetails'] = static::_refreshAccessToken($queryParams); } $tokenDetails = empty($response['tokenDetails']) ? $queryParams->tokenDetails : $response['tokenDetails']; @@ -155,7 +122,7 @@ public static function getAllUsers($queryParams) $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (static::isTokenExpired($queryParams->tokenDetails)) { $response['tokenDetails'] = static::_refreshAccessToken($queryParams); } @@ -202,7 +169,7 @@ public function execute($integrationData, $fieldValues) // translators: %s: Placeholder value return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'mautic')); } - if ((\intval($tokenDetails->generates_on) + (60 * 55)) < time()) { + if (static::isTokenExpired($tokenDetails)) { $requiredParams['clientId'] = $integrationDetails->clientId; $requiredParams['clientSecret'] = $integrationDetails->clientSecret; $requiredParams['baseUrl'] = $integrationDetails->baseUrl; @@ -248,9 +215,33 @@ protected static function _refreshAccessToken($apiData) if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { return false; } - $tokenDetails->generates_on = time(); + $generatedAt = time(); + $tokenDetails->generates_on = $generatedAt; + $tokenDetails->generated_at = $generatedAt; $tokenDetails->access_token = $apiResponse->access_token; + if (!empty($apiResponse->refresh_token)) { + $tokenDetails->refresh_token = $apiResponse->refresh_token; + } + if (isset($apiResponse->expires_in)) { + $tokenDetails->expires_in = $apiResponse->expires_in; + } return $tokenDetails; } + + private static function isTokenExpired($tokenDetails) + { + if (empty($tokenDetails) || !\is_object($tokenDetails)) { + return false; + } + + $generatedAt = empty($tokenDetails->generates_on) ? ($tokenDetails->generated_at ?? 0) : $tokenDetails->generates_on; + $expiresIn = $tokenDetails->expires_in ?? 0; + + if (!empty($generatedAt) && !empty($expiresIn) && (int) $expiresIn > 0) { + return ((int) $generatedAt + (int) $expiresIn - 30) < time(); + } + + return ((int) $generatedAt + (55 * 60)) < time(); + } } diff --git a/backend/Actions/Mautic/Routes.php b/backend/Actions/Mautic/Routes.php index 91cb283ce..8b53da7f7 100644 --- a/backend/Actions/Mautic/Routes.php +++ b/backend/Actions/Mautic/Routes.php @@ -7,8 +7,6 @@ use BitApps\Integrations\Actions\Mautic\MauticController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('mautic_generate_token', [MauticController::class, 'generateTokens']); -// Route::post('mChimp_refresh_audience', [ MailChimpController::class, 'refreshAudience']); Route::post('mautic_get_fields', [MauticController::class, 'getAllFields']); Route::post('mautic_get_tags', [MauticController::class, 'getAllTags']); Route::post('mautic_get_users', [MauticController::class, 'getAllUsers']); diff --git a/backend/Actions/Salesforce/Routes.php b/backend/Actions/Salesforce/Routes.php index 81e9b97ff..59966dbaf 100644 --- a/backend/Actions/Salesforce/Routes.php +++ b/backend/Actions/Salesforce/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Salesforce\SalesforceController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('selesforce_generate_token', [SalesforceController::class, 'generateTokens']); Route::post('selesforce_custom_action', [SalesforceController::class, 'customActions']); Route::post('selesforce_campaign_list', [SalesforceController::class, 'selesforceCampaignList']); Route::post('selesforce_lead_list', [SalesforceController::class, 'selesforceLeadList']); diff --git a/backend/Actions/Salesforce/SalesforceController.php b/backend/Actions/Salesforce/SalesforceController.php index bab7d9381..5cc029784 100644 --- a/backend/Actions/Salesforce/SalesforceController.php +++ b/backend/Actions/Salesforce/SalesforceController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Salesforce; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Core\Util\Hooks; @@ -14,6 +15,16 @@ class SalesforceController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'salesforce', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'instance_url']], + ], + ]; + public static $actions = [ 'contact-create' => 'Contact', 'lead-create' => 'Lead', @@ -27,47 +38,6 @@ class SalesforceController private $_integrationID; - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = 'https://login.salesforce.com/services/oauth2/token?grant_type=authorization_code&client_id=' . $requestsParams->clientId . '&client_secret=' . $requestsParams->clientSecret . '&redirect_uri=' . $requestsParams->redirectURI . '&code=' . $requestsParams->code; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'code' => explode('#', $requestsParams->code)[0], - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'format' => 'json', - ]; - - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - - $apiResponse->generates_on = time(); - - wp_send_json_success($apiResponse, 200); - } - public function customActions($params) { if ( @@ -423,7 +393,7 @@ public function execute($integrationData, $fieldValues) return new WP_Error('REQ_FIELD_EMPTY', __('list are required for zoho desk api', 'bit-integrations')); } - if ((\intval($tokenDetails->generates_on) + (55 * 60)) < time()) { + if (self::isTokenExpired($tokenDetails)) { $newTokenDetails = self::refreshAccessToken((object) [ 'clientId' => $integrationDetails->clientId, 'clientSecret' => $integrationDetails->clientSecret, @@ -456,7 +426,7 @@ public static function refreshTokenDetails($params) { $response = ['tokenDetails' => $params->tokenDetails]; - if ((\intval($params->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (self::isTokenExpired($params->tokenDetails)) { $response['tokenDetails'] = self::refreshAccessToken($params); } @@ -518,8 +488,16 @@ protected static function refreshAccessToken($apiData) return false; } - $tokenDetails->generates_on = time(); + $generatedAt = time(); + $tokenDetails->generates_on = $generatedAt; + $tokenDetails->generated_at = $generatedAt; $tokenDetails->access_token = $apiResponse->access_token; + if (!empty($apiResponse->refresh_token)) { + $tokenDetails->refresh_token = $apiResponse->refresh_token; + } + if (isset($apiResponse->expires_in)) { + $tokenDetails->expires_in = $apiResponse->expires_in; + } return $tokenDetails; } @@ -536,6 +514,22 @@ private static function isRequiredField($key, $action) return \in_array($key, $requiredFields[$action] ?? ['Name']); } + private static function isTokenExpired($tokenDetails) + { + if (empty($tokenDetails) || !\is_object($tokenDetails)) { + return false; + } + + $generatedAt = empty($tokenDetails->generates_on) ? ($tokenDetails->generated_at ?? 0) : $tokenDetails->generates_on; + $expiresIn = $tokenDetails->expires_in ?? 0; + + if (!empty($generatedAt) && !empty($expiresIn) && (int) $expiresIn > 0) { + return ((int) $generatedAt + (int) $expiresIn - 30) < time(); + } + + return ((int) $generatedAt + (55 * 60)) < time(); + } + private static function getCaseMetaData($params, $module) { if ( diff --git a/frontend/src/components/AllIntegrations/Mautic/Mautic.jsx b/frontend/src/components/AllIntegrations/Mautic/Mautic.jsx index e13280eff..009f4065d 100644 --- a/frontend/src/components/AllIntegrations/Mautic/Mautic.jsx +++ b/frontend/src/components/AllIntegrations/Mautic/Mautic.jsx @@ -1,15 +1,15 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { useNavigate, useParams } from 'react-router' +import { useNavigate } from 'react-router' import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' +import { checkMappedFields, handleInput } from './MauticCommonFunc' import MauticAuthorization from './MauticAuthorization' -import { setGrantTokenResponse, checkMappedFields, handleInput } from './MauticCommonFunc' import MauticIntegLayout from './MauticIntegLayout' function Mautic({ formFields, setFlow, flow, allIntegURL }) { @@ -26,10 +26,6 @@ function Mautic({ formFields, setFlow, flow, allIntegURL }) { field_map: [{ formField: '', mauticField: '' }], actions: {} }) - - useEffect(() => { - window.opener && setGrantTokenResponse('mautic') - }, []) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/Mautic/MauticAuthorization.jsx b/frontend/src/components/AllIntegrations/Mautic/MauticAuthorization.jsx index e7ca87081..f6a69882b 100644 --- a/frontend/src/components/AllIntegrations/Mautic/MauticAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mautic/MauticAuthorization.jsx @@ -1,169 +1,77 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleMauticAuthorize, getAllFields } from './MauticCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' +import { getAllFields } from './MauticCommonFunc' export default function MauticAuthorization({ mauticConf, setMauticConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ baseUrl: '', clientId: '', clientSecret: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - getAllFields(mauticConf, setMauticConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...mauticConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMauticConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Mautic API Console', 'bit-integrations')} - - - -
- {__('Mautic Base URL:', 'bit-integrations')} -
- -
{error.baseUrl}
+ const loadFields = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...mauticConf, connection_id: connectionId } : mauticConf + await getAllFields(nextConf, setMauticConf, setIsLoading, setSnackbar) + }, + [mauticConf, setMauticConf, setIsLoading, setSnackbar] + ) - - {__('Example: https://mautic.bit-integration.pro', 'bit-integrations')} - + const handleSetStep = useCallback( + value => { + if (value === 2 && !mauticConf?.default?.fields) { + loadFields() + } -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ setstep(value) + }, + [mauticConf, loadFields, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Mautic OAuth2 Setup', 'bit-integrations')}

+
    +
  1. ${__('Open your Mautic account and create an OAuth2 API credential.', 'bit-integrations')}
  2. +
  3. ${__('Set the callback URL exactly as shown below.', 'bit-integrations')}
  4. +
  5. ${__('Use your Mautic base URL (example: https://mautic.example.com).', 'bit-integrations')}
  6. +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/Mautic/MauticCommonFunc.js b/frontend/src/components/AllIntegrations/Mautic/MauticCommonFunc.js index d0f7df5ff..a7756ae62 100644 --- a/frontend/src/components/AllIntegrations/Mautic/MauticCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mautic/MauticCommonFunc.js @@ -1,6 +1,5 @@ import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { sprintf, __ } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' export const handleInput = (e, sheetConf, setSheetConf) => { const newConf = { ...sheetConf } @@ -8,14 +7,19 @@ export const handleInput = (e, sheetConf, setSheetConf) => { setSheetConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + baseUrl: conf.baseUrl, + tokenDetails: conf.tokenDetails + } + export const getAllFields = (mauticConf, setMauticConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - clientId: mauticConf.clientId, - clientSecret: mauticConf.clientSecret, - baseUrl: mauticConf.baseUrl, - tokenDetails: mauticConf.tokenDetails - } + const requestParams = buildAuthRequestParams(mauticConf) bitsFetch(requestParams, 'mautic_get_fields') .then(result => { if (result && result.success) { @@ -51,12 +55,7 @@ export const getAllFields = (mauticConf, setMauticConf, setIsLoading, setSnackba } export const getAllTags = (mauticConf, setMauticConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - clientId: mauticConf.clientId, - clientSecret: mauticConf.clientSecret, - baseUrl: mauticConf.baseUrl, - tokenDetails: mauticConf.tokenDetails - } + const requestParams = buildAuthRequestParams(mauticConf) bitsFetch(requestParams, 'mautic_get_tags') .then(result => { if (result && result.success) { @@ -92,12 +91,7 @@ export const getAllTags = (mauticConf, setMauticConf, setIsLoading, setSnackbar) export const getAllUsers = (mauticConf, setMauticConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - clientId: mauticConf.clientId, - clientSecret: mauticConf.clientSecret, - baseUrl: mauticConf.baseUrl, - tokenDetails: mauticConf.tokenDetails - } + const requestParams = buildAuthRequestParams(mauticConf) bitsFetch(requestParams, 'mautic_get_users') .then(result => { if (result && result.success) { @@ -131,124 +125,6 @@ export const getAllUsers = (mauticConf, setMauticConf, setIsLoading, setSnackbar .catch(() => setIsLoading(false)) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleMauticAuthorize = ( - integ, - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.clientId || !confTmp.clientSecret || !confTmp.baseUrl) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '', - baseUrl: !confTmp.baseUrl ? __("Base Url can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const apiEndpoint = `${confTmp.baseUrl}/oauth/v2/authorize?client_id=${ - confTmp.clientId - }&redirect_uri=${encodeURIComponent(window.location.href)}&response_type=code` - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsMautic = localStorage.getItem(`__${integ}`) - if (bitsMautic) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsMautic) - localStorage.removeItem(`__${integ}`) - if (grantTokenResponse.code.search('#')) { - const [code] = grantTokenResponse.code.split('#') - grantTokenResponse.code = code - } - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setisAuthorized, setIsLoading, setSnackbar) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setisAuthorized, setIsLoading, setSnackbar) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.baseUrl = confTmp.baseUrl - tokenRequestParams.redirectURI = window.location.href - - bitsFetch(tokenRequestParams, 'mautic_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = mauticConf => { const mappedFleld = mauticConf.field_map ? mauticConf.field_map.filter(mapped => !mapped.formField && !mapped.mauticField) diff --git a/frontend/src/components/AllIntegrations/Salesforce/Salesforce.jsx b/frontend/src/components/AllIntegrations/Salesforce/Salesforce.jsx index 339790e64..157d221aa 100644 --- a/frontend/src/components/AllIntegrations/Salesforce/Salesforce.jsx +++ b/frontend/src/components/AllIntegrations/Salesforce/Salesforce.jsx @@ -1,5 +1,5 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import toast from 'react-hot-toast' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' @@ -7,7 +7,7 @@ import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveActionConf, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import { checkMappedFields, handleInput } from './SalesforceCommonFunc' import SelesforceIntegLayout from './SalesforceIntegLayout' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' @@ -42,9 +42,6 @@ function Salesforce({ formFields, setFlow, flow, allIntegURL }) { action_modules, actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('salesforce') - }, []) const checkedActionFieldsMapType = [ 'contact-create', diff --git a/frontend/src/components/AllIntegrations/Salesforce/SalesforceAuthorization.jsx b/frontend/src/components/AllIntegrations/Salesforce/SalesforceAuthorization.jsx index 5e13bd392..aee8c65dc 100644 --- a/frontend/src/components/AllIntegrations/Salesforce/SalesforceAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Salesforce/SalesforceAuthorization.jsx @@ -1,151 +1,49 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from './SalesforceCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import { $appConfigState } from '../../../GlobalStates' -import { useRecoilValue } from 'recoil' +import Authorization from '../../Connections/Authorization' export default function SalesforceAuthorization({ - formID, salesforceConf, setSalesforceConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const btcbi = useRecoilValue($appConfigState) - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ - dataCenter: '', - clientId: '', - clientSecret: '' - }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...salesforceConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSalesforceConf(newConf) - } + const note = `

${__('Salesforce OAuth2 Setup', 'bit-integrations')}

+
    +
  1. ${__('Create a Connected App in Salesforce.', 'bit-integrations')}
  2. +
  3. ${__('Set the callback URL exactly as shown below.', 'bit-integrations')}
  4. +
  5. ${__('Use Consumer Key as Client ID and Consumer Secret as Client Secret.', 'bit-integrations')}
  6. +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
-
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Salesforce/SalesforceCommonFunc.js b/frontend/src/components/AllIntegrations/Salesforce/SalesforceCommonFunc.js index 423e9bd72..64f3b4d21 100644 --- a/frontend/src/components/AllIntegrations/Salesforce/SalesforceCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Salesforce/SalesforceCommonFunc.js @@ -1,8 +1,17 @@ import toast from 'react-hot-toast' -import { __, sprintf } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import { create } from 'mutative' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const handleInput = ( e, salesforceConf, @@ -68,12 +77,7 @@ export const getAllCampaignList = ( setSnackbar ) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(campaignRequestParams, 'selesforce_campaign_list').then(result => { if (result && result.success) { setSalesforceConf(oldConf => { @@ -105,12 +109,7 @@ export const getAllCampaignList = ( export const getAllOrigin = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(campaignRequestParams, 'selesforce_case_origin').then(result => { if (result && result.success) { setSalesforceConf(prevConf => @@ -133,12 +132,7 @@ export const getAllOrigin = (formID, salesforceConf, setSalesforceConf, setIsLoa export const getAllType = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(campaignRequestParams, 'selesforce_case_type').then(result => { if (result && result.success) { setSalesforceConf(prevConf => @@ -161,12 +155,7 @@ export const getAllType = (formID, salesforceConf, setSalesforceConf, setIsLoadi export const getAllReason = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_case_reason').then(result => { if (result && result.success) { setSalesforceConf(prevConf => @@ -189,12 +178,7 @@ export const getAllReason = (formID, salesforceConf, setSalesforceConf, setIsLoa export const getAllStatus = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_case_status').then(result => { if (result && result.success) { setSalesforceConf(prevConf => @@ -217,12 +201,7 @@ export const getAllStatus = (formID, salesforceConf, setSalesforceConf, setIsLoa export const getAllPriority = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_case_priority').then(result => { if (result && result.success) { setSalesforceConf(prevConf => @@ -251,12 +230,7 @@ export const getAllPotentialLiability = ( setSnackbar ) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_case_potential_liability').then( result => { if (result && result.success) { @@ -287,12 +261,7 @@ export const getAllSLAViolation = ( setSnackbar ) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_case_sla_violation').then( result => { if (result && result.success) { @@ -326,9 +295,7 @@ export const getAllLeadSource = ( const campaignRequestParams = { formID, actionName: salesforceConf.actionName, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails + ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_get_lead_sources').then( result => { @@ -365,9 +332,7 @@ export const getAllLeadStatus = ( const campaignRequestParams = { formID, actionName: salesforceConf.actionName, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails + ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_get_lead_status').then(result => { if (result && result.success) { @@ -402,9 +367,7 @@ export const getAllLeadRatings = ( const campaignRequestParams = { formID, actionName: salesforceConf.actionName, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails + ...buildAuthRequestParams(salesforceConf) } const loadPostReasons = bitsFetch(campaignRequestParams, 'selesforce_get_lead_ratings').then( result => { @@ -471,12 +434,7 @@ export const getAllLeadIndustries = ( export const getAllLeadList = (formID, salesforceConf, setSalesforceConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } bitsFetch(campaignRequestParams, 'selesforce_lead_list') .then(result => { if (result && result.success) { @@ -511,12 +469,7 @@ export const getAllContactList = ( setSnackbar ) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(campaignRequestParams, 'selesforce_contact_list').then(result => { if (result && result.success) { setSalesforceConf(oldConf => { @@ -558,9 +511,7 @@ export const getAllCustomFields = ( const customFieldRequestParams = { formID, actionName, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails + ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(customFieldRequestParams, 'selesforce_custom_field').then(result => { @@ -597,9 +548,7 @@ export const getAllCustomActionModules = ( setSnackbar ) => { const customFieldRequestParams = { - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails + ...buildAuthRequestParams(salesforceConf) } setIsLoading(true) bitsFetch(customFieldRequestParams, 'selesforce_custom_action').then(result => { @@ -666,12 +615,7 @@ export const getAllAccountList = ( setSnackbar ) => { setIsLoading(true) - const campaignRequestParams = { - formID, - clientId: salesforceConf.clientId, - clientSecret: salesforceConf.clientSecret, - tokenDetails: salesforceConf.tokenDetails - } + const campaignRequestParams = { formID, ...buildAuthRequestParams(salesforceConf) } const loadPostTypes = bitsFetch(campaignRequestParams, 'selesforce_account_list').then(result => { if (result && result.success) { setSalesforceConf(oldConf => { @@ -742,116 +686,3 @@ export const generateMappedField = (salesforceConf, actionName) => { return fieldMap } - -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - - setIsLoading(true) - const apiEndpoint = `https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=${ - confTmp.clientId - }&prompt=login%20consent&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(btcbi.api)}/redirect` - - const authWindow = window.open(apiEndpoint, 'salesforce', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitformsZoho = localStorage.getItem('__salesforce') - if (bitformsZoho) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitformsZoho) - localStorage.removeItem('__salesforce') - } - - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'selesforce_generate_token').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} From 4270064af63bd4569e9c6f46a0f908cdc72702e6 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Tue, 12 May 2026 17:56:59 +0600 Subject: [PATCH 37/58] refactor: integration components to use a unified Authorization component - Removed the checkAuthorization route from SureCart. - Simplified SureCartController by removing the checkAuthorization method. - Updated Acumbamail, FluentSupport, NinjaTables, SendFox, SeoPress, and SureCart authorization components to utilize the new Authorization component for handling authentication. - Streamlined the handling of authorization parameters and error messages across integrations. - Removed redundant authorization logic from common functions in integrations. - Enhanced user experience by providing clear instructions for obtaining API keys and tokens in the authorization notes. --- .../Acumbamail/AcumbamailController.php | 35 ++--- backend/Actions/Acumbamail/Routes.php | 1 - .../FluentSupport/FluentSupportController.php | 15 -- backend/Actions/FluentSupport/Routes.php | 1 - .../NinjaTables/NinjaTablesController.php | 11 -- backend/Actions/NinjaTables/Routes.php | 1 - backend/Actions/SendFox/Routes.php | 1 - backend/Actions/SendFox/SendFoxController.php | 38 ++--- backend/Actions/SeoPress/Routes.php | 2 - .../Actions/SeoPress/SeoPressController.php | 11 -- .../Acumbamail/AcumbamailAuthorization.jsx | 148 ++++++------------ .../Acumbamail/AcumbamailCommonFunc.js | 62 +++----- .../FluentSupportAuthorization.jsx | 116 +++++--------- .../FluentSupport/FluentSupportCommonFunc.js | 45 +----- .../NinjaTables/NinjaTablesAuthorization.jsx | 129 +++------------ .../NinjaTables/NinjaTablesCommonFunc.js | 3 +- .../SendFox/SendFoxAuthorization.jsx | 139 +++------------- .../SendFox/SendFoxCommonFunc.js | 42 +---- .../SeoPress/SeoPressAuthorization.jsx | 125 +++------------ 19 files changed, 213 insertions(+), 712 deletions(-) diff --git a/backend/Actions/Acumbamail/AcumbamailController.php b/backend/Actions/Acumbamail/AcumbamailController.php index 3f83d53f5..572d3c053 100644 --- a/backend/Actions/Acumbamail/AcumbamailController.php +++ b/backend/Actions/Acumbamail/AcumbamailController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Acumbamail; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,6 +15,14 @@ */ class AcumbamailController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'acumbamail', + 'fields' => [ + 'auth_token' => 'value', + ], + ]; + private $baseUrl = 'https://acumbamail.com/api/1/'; public function fetchAllLists($requestParams) @@ -46,32 +55,6 @@ public function fetchAllLists($requestParams) } } - public function acumbamailAuthAndFetchSubscriberList($requestParams) - { - if (empty($requestParams->auth_token)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoints = $this->baseUrl . 'getSubscribers/'; - - $requestParams = [ - 'auth_token' => $requestParams->auth_token, - ]; - - $response = HttpHelper::post($apiEndpoints, $requestParams); - - if ($response == 'Unauthorized' || $response == 'This endpoint is not available for non-paying customers' || $response == 'Your auth token has expired check /apidoc/ for the new one') { - wp_send_json_error($response, 400); - } else { - wp_send_json_success($response, 200); - } - } - public function acumbamailRefreshFields($refreshFieldsRequestParams) { if (empty($refreshFieldsRequestParams->auth_token) || empty($refreshFieldsRequestParams->list_id)) { diff --git a/backend/Actions/Acumbamail/Routes.php b/backend/Actions/Acumbamail/Routes.php index 3abc1e6fe..9dd043ab0 100644 --- a/backend/Actions/Acumbamail/Routes.php +++ b/backend/Actions/Acumbamail/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Acumbamail\AcumbamailController; use BitApps\Integrations\Core\Util\Route; -Route::post('acumbamail_authorization_and_fetch_subscriber_list', [AcumbamailController::class, 'acumbamailAuthAndFetchSubscriberList']); Route::post('acumbamail_fetch_all_list', [AcumbamailController::class, 'fetchAllLists']); Route::post('acumbamail_refresh_fields', [AcumbamailController::class, 'acumbamailRefreshFields']); diff --git a/backend/Actions/FluentSupport/FluentSupportController.php b/backend/Actions/FluentSupport/FluentSupportController.php index d47451403..a2cd3c963 100644 --- a/backend/Actions/FluentSupport/FluentSupportController.php +++ b/backend/Actions/FluentSupport/FluentSupportController.php @@ -16,21 +16,6 @@ */ class FluentSupportController { - public function checkAuthorization() - { - if (!is_plugin_active('fluent-support/fluent-support.php')) { - wp_send_json_error( - __( - 'Fluent Support Plugin is not active or not installed', - 'bit-integrations' - ), - 400 - ); - } else { - return true; - } - } - public function getCustomFields() { if (!class_exists(\FluentSupportPro\App\Services\CustomFieldsService::class)) { diff --git a/backend/Actions/FluentSupport/Routes.php b/backend/Actions/FluentSupport/Routes.php index 4b459e543..6a912f39d 100644 --- a/backend/Actions/FluentSupport/Routes.php +++ b/backend/Actions/FluentSupport/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\FluentSupport\FluentSupportController; use BitApps\Integrations\Core\Util\Route; -Route::post('fluentSupport_authorization', [FluentSupportController::class, 'checkAuthorization']); Route::post('fluent_support_get_custom_fields', [FluentSupportController::class, 'getCustomFields']); Route::post('fluent_support_get_all_support_staff', [FluentSupportController::class, 'getAllSupportStaff']); Route::post('fluent_support_get_all_business_inboxes', [FluentSupportController::class, 'getAllBusinessInboxes']); diff --git a/backend/Actions/NinjaTables/NinjaTablesController.php b/backend/Actions/NinjaTables/NinjaTablesController.php index a629b12a0..25b7da05c 100644 --- a/backend/Actions/NinjaTables/NinjaTablesController.php +++ b/backend/Actions/NinjaTables/NinjaTablesController.php @@ -42,17 +42,6 @@ public static function isExists() return true; } - /** - * Authorize Ninja Tables integration - * - * @return void - */ - public static function ninjaTablesAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - /** * Get all published Ninja Tables * diff --git a/backend/Actions/NinjaTables/Routes.php b/backend/Actions/NinjaTables/Routes.php index a1d59d2d8..0a53913dd 100644 --- a/backend/Actions/NinjaTables/Routes.php +++ b/backend/Actions/NinjaTables/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\NinjaTables\NinjaTablesController; use BitApps\Integrations\Core\Util\Route; -Route::post('ninja_tables_authorize', [NinjaTablesController::class, 'ninjaTablesAuthorize']); Route::post('refresh_ninja_tables', [NinjaTablesController::class, 'refreshTables']); Route::post('refresh_ninja_tables_rows', [NinjaTablesController::class, 'refreshRows']); Route::post('refresh_ninja_tables_users', [NinjaTablesController::class, 'refreshUsers']); diff --git a/backend/Actions/SendFox/Routes.php b/backend/Actions/SendFox/Routes.php index dc5aeb632..8da76d7d2 100644 --- a/backend/Actions/SendFox/Routes.php +++ b/backend/Actions/SendFox/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\SendFox\SendFoxController; use BitApps\Integrations\Core\Util\Route; -Route::post('sendFox_authorize', [SendFoxController::class, 'sendFoxAuthorize']); Route::post('sendfox_fetch_all_list', [SendFoxController::class, 'fetchContactLists']); diff --git a/backend/Actions/SendFox/SendFoxController.php b/backend/Actions/SendFox/SendFoxController.php index 4a3ebb175..df5c24c0a 100644 --- a/backend/Actions/SendFox/SendFoxController.php +++ b/backend/Actions/SendFox/SendFoxController.php @@ -6,41 +6,21 @@ namespace BitApps\Integrations\Actions\SendFox; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class SendFoxController { - private $baseUrl = 'https://api.sendfox.com/'; - - public function sendFoxAuthorize($requestParams) - { - if (empty($requestParams->access_token)) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoints = $this->baseUrl . 'me'; - - $requestParams = [ - 'Authorization' => "Bearer {$requestParams->access_token}", - 'Accept' => 'application/json', - ]; + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'sendfox', + 'fields' => [ + 'access_token' => 'token', + ], + ]; - $response = HttpHelper::get($apiEndpoints, null, $requestParams); - if ($response->message !== 'Unauthenticated.') { - wp_send_json_success($response, 200); - } else { - wp_send_json_error( - 'The token is invalid', - 400 - ); - } - } + private $baseUrl = 'https://api.sendfox.com/'; public function fetchContactLists($requestParams) { diff --git a/backend/Actions/SeoPress/Routes.php b/backend/Actions/SeoPress/Routes.php index f67670882..56954c52a 100644 --- a/backend/Actions/SeoPress/Routes.php +++ b/backend/Actions/SeoPress/Routes.php @@ -6,5 +6,3 @@ use BitApps\Integrations\Actions\SeoPress\SeoPressController; use BitApps\Integrations\Core\Util\Route; - -Route::post('seopress_authorize', [SeoPressController::class, 'seoPressAuthorize']); diff --git a/backend/Actions/SeoPress/SeoPressController.php b/backend/Actions/SeoPress/SeoPressController.php index a9ee6d120..8ce6cd63b 100644 --- a/backend/Actions/SeoPress/SeoPressController.php +++ b/backend/Actions/SeoPress/SeoPressController.php @@ -32,17 +32,6 @@ public static function isExists() } } - /** - * Process ajax request for authorization - * - * @return JSON response - */ - public static function seoPressAuthorize() - { - self::isExists(); - wp_send_json_success(true); - } - /** * Execute action * diff --git a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx index 6a8791821..24738ddc9 100644 --- a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx @@ -1,119 +1,65 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { fetchAllList, handleAuthorize } from './AcumbamailCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchAllList } from './AcumbamailCommonFunc' export default function AcumbamailAuthorization({ - formID, acumbamailConf, setAcumbamailConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadLists = useCallback( + connectionId => { + const nextConf = connectionId + ? { ...acumbamailConf, connection_id: connectionId } + : acumbamailConf + fetchAllList(nextConf, setAcumbamailConf, setIsLoading, setSnackbar) + }, + [acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar] + ) - setstep(2) - fetchAllList(acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !acumbamailConf?.default?.allLists) { + loadLists() + } + setstep(value) + }, + [acumbamailConf?.default?.allLists, loadLists, setstep] + ) - const handleInput = e => { - const newConf = { ...acumbamailConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setAcumbamailConf(newConf) - } + const note = ` + + ${__('To get your auth token, please visit', 'bit-integrations')} + + ${__(' Acumbamail API docs', 'bit-integrations')} + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - - - {__('To Get Client Auth token, Please Visit', 'bit-integrations')} -   - - {__('Acumbamail doc', 'bit-integrations')} - - - -
- {__('Auth Token:', 'bit-integrations')} -
- -
{error.auth_token}
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailCommonFunc.js b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailCommonFunc.js index 3fd047efd..c809f6acf 100644 --- a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailCommonFunc.js @@ -1,7 +1,15 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { sprintf, __ } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' + +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + auth_token: conf.auth_token + } + +const hasAuthParams = conf => Boolean(conf?.connection_id || conf?.auth_token) export const handleInput = (e, acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar, formID) => { let newConf = { ...acumbamailConf } @@ -27,11 +35,14 @@ export const handleInput = (e, acumbamailConf, setAcumbamailConf, setIsLoading, export const refreshFields = (formID, acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar) => { const { listId } = acumbamailConf - if (!listId) { + if (!listId || !hasAuthParams(acumbamailConf)) { return } setIsLoading(true) - const refreshFieldsRequestParams = { auth_token: acumbamailConf.auth_token, list_id: listId } + const refreshFieldsRequestParams = { + ...buildAuthRequestParams(acumbamailConf), + list_id: listId + } bitsFetch(refreshFieldsRequestParams, 'acumbamail_refresh_fields') .then(result => { if (result && result.success) { @@ -60,8 +71,16 @@ export const refreshFields = (formID, acumbamailConf, setAcumbamailConf, setIsLo } export const fetchAllList = (acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar) => { + if (!hasAuthParams(acumbamailConf)) { + setSnackbar({ + show: true, + msg: __('Authorization info is missing. please authorize again', 'bit-integrations') + }) + return + } + setIsLoading(true) - const requestParams = { auth_token: acumbamailConf.auth_token } + const requestParams = buildAuthRequestParams(acumbamailConf) bitsFetch(requestParams, 'acumbamail_fetch_all_list') .then(result => { @@ -85,39 +104,6 @@ export const fetchAllList = (acumbamailConf, setAcumbamailConf, setIsLoading, se .catch(() => setIsLoading(false)) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.auth_token) { - setError({ - auth_token: !confTmp.auth_token ? __("Api Key can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setIsLoading(true) - - const requestParams = { auth_token: confTmp.auth_token } - - bitsFetch(requestParams, 'acumbamail_authorization_and_fetch_subscriber_list').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setIsLoading(false) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__(result.data, 'bit-integrations')) - }) -} - export const checkMappedFields = acumbamailConf => { const mappedFields = acumbamailConf?.field_map ? acumbamailConf.field_map.filter( diff --git a/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportAuthorization.jsx b/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportAuthorization.jsx index e97b681fb..5ae042a66 100644 --- a/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportAuthorization.jsx @@ -1,95 +1,55 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getCustomFields, handleAuthorize } from './FluentSupportCommonFunc' +import { getCustomFields } from './FluentSupportCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function FluentSupportAuthorization({ - formID, fluentSupportConf, setFluentSupportConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - + const loadCustomFields = useCallback(() => { getCustomFields(fluentSupportConf, setFluentSupportConf, setIsLoading, setSnackbar) - setstep(2) - } - const handleInput = e => { - const newConf = { ...fluentSupportConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setFluentSupportConf(newConf) - } - - return ( -
- + }, [fluentSupportConf, setFluentSupportConf, setIsLoading, setSnackbar]) -
- {__('Integration Name:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !fluentSupportConf?.fluentSupportFields?.length) { + loadCustomFields() + } + setstep(value) + }, + [fluentSupportConf?.fluentSupportFields?.length, loadCustomFields, setstep] + ) - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportCommonFunc.js b/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportCommonFunc.js index 44bf042fd..b925cac1d 100644 --- a/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportCommonFunc.js +++ b/frontend/src/components/AllIntegrations/FluentSupport/FluentSupportCommonFunc.js @@ -1,6 +1,5 @@ import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' -import { sprintf, __ } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' import { create } from 'mutative' export const handleInput = ( @@ -160,45 +159,3 @@ export const checkMappedFields = fluentSupportConf => { } return true } - -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - setError({}) - setIsLoading(true) - - bitsFetch(null, 'fluentSupport_authorization') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Cunnection failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Cunnection failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesAuthorization.jsx b/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesAuthorization.jsx index 6051107f3..cccb093fd 100644 --- a/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesAuthorization.jsx @@ -1,117 +1,40 @@ -import { useState } from 'react' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function NinjaTablesAuthorization({ - formID, ninjaTablesConf, setNinjaTablesConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) - - const handleAuthorize = () => { - setIsLoading('auth') - const requestParams = { formID } - bitsFetch(requestParams, 'ninja_tables_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with Ninja Tables successfully', 'bit-integrations') - }) - setIsLoading(false) - } else { - setShowAuthMsg(true) - setSnackbar({ - show: true, - msg: __( - result?.data - ? result.data - : 'Connection failed. Please make sure Ninja Tables is installed and activated', - 'bit-integrations' - ) - }) - setIsLoading(false) - } - }) - } + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( - <> - - -
-
- {__('Integration Name:', 'bit-integrations')} -
- { - const newConf = { ...ninjaTablesConf } - newConf.name = e.target.value - setNinjaTablesConf(newConf) - }} - name="name" - value={ninjaTablesConf.name} - type="text" - placeholder={__('Integration Name...', 'bit-integrations')} - /> - - -
- {showAuthMsg && ( -
- - {__('Reminder:', 'bit-integrations')}{' '} - {__( - 'Please make sure Ninja Tables plugin is installed and activated.', - 'bit-integrations' - )} - -
- )} - - {!isInfo && ( -
-
- -
- )} -
- + ) } diff --git a/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesCommonFunc.js b/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesCommonFunc.js index d0246cf42..9612a5a1e 100644 --- a/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesCommonFunc.js +++ b/frontend/src/components/AllIntegrations/NinjaTables/NinjaTablesCommonFunc.js @@ -6,8 +6,7 @@ const API_ENDPOINTS = { REFRESH_TABLES: 'refresh_ninja_tables', REFRESH_ROWS: 'refresh_ninja_tables_rows', REFRESH_USERS: 'refresh_ninja_tables_users', - REFRESH_COLUMNS: 'refresh_ninja_tables_columns', - AUTHORIZE: 'ninja_tables_authorize' + REFRESH_COLUMNS: 'refresh_ninja_tables_columns' } const ACTIONS = { diff --git a/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx b/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx index b8aa62250..bb2c73a72 100644 --- a/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx @@ -1,129 +1,38 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import TutorialLink from '../../Utilities/TutorialLink' -import { handleAuthorize } from './SendFoxCommonFunc' +import Authorization from '../../Connections/Authorization' export default function SendFoxAuthorization({ - formID, sendFoxConf, setSendFoxConf, step, setstep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setstep(2) - // fetchAllList(sendFoxConf, setSendFoxConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...sendFoxConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSendFoxConf(newConf) - } + const note = ` + + ${__('To generate an access token, please visit', 'bit-integrations')} + + ${__(' SendFox OAuth settings', 'bit-integrations')} + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - -
- {__('Access Token:', 'bit-integrations')} -
- -
{error.access_token}
- - - {__('To Get Client Auth token, Please Visit', 'bit-integrations')} -   - - {__('SendFox Access Token', 'bit-integrations')} - - - - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/SendFox/SendFoxCommonFunc.js b/frontend/src/components/AllIntegrations/SendFox/SendFoxCommonFunc.js index 96a8769f2..f4b2f5049 100644 --- a/frontend/src/components/AllIntegrations/SendFox/SendFoxCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SendFox/SendFoxCommonFunc.js @@ -5,6 +5,13 @@ import { contactFields } from './SendFoxFieldMap' import { listFields } from './SendFoxListFieldMap' import { unsubscribeFields } from './SendFoxUnsubscribeFieldMap' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + access_token: conf.access_token + } + export const handleInput = (e, sendFoxConf, setSendFoxConf, setIsLoading, setSnackbar, formID) => { const newConf = { ...sendFoxConf } const { name } = e.target @@ -21,7 +28,7 @@ export const handleInput = (e, sendFoxConf, setSendFoxConf, setIsLoading, setSna export const fetchAllList = (sendFoxConf, setSendFoxConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { access_token: sendFoxConf.access_token } + const requestParams = buildAuthRequestParams(sendFoxConf) bitsFetch(requestParams, 'sendfox_fetch_all_list') .then(result => { @@ -45,39 +52,6 @@ export const fetchAllList = (sendFoxConf, setSendFoxConf, setIsLoading, setSnack .catch(() => setIsLoading(false)) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.access_token) { - setError({ - access_token: !confTmp.access_token ? __("Access Token can't be empty", 'bit-integrations') : '' - }) - return - } - setError({}) - setIsLoading(true) - - const requestParams = { access_token: confTmp.access_token } - - bitsFetch(requestParams, 'sendFox_authorize').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setisAuthorized(true) - setIsLoading(false) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - setIsLoading(false) - toast.error(__('Authorized failed', 'bit-integrations')) - }) -} - export const generateMappedField = sendFoxConf => { const requiredFlds = contactFields.filter(fld => fld.required === true) return requiredFlds.length > 0 diff --git a/frontend/src/components/AllIntegrations/SeoPress/SeoPressAuthorization.jsx b/frontend/src/components/AllIntegrations/SeoPress/SeoPressAuthorization.jsx index faffd0b3a..38f4922a5 100644 --- a/frontend/src/components/AllIntegrations/SeoPress/SeoPressAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SeoPress/SeoPressAuthorization.jsx @@ -1,113 +1,40 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' -import bitsFetch from '../../../Utils/bitsFetch' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import LoaderSm from '../../Loaders/LoaderSm' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function SeoPressAuthorization({ - formID, seoPressConf, setSeoPressConf, step, nextPage, - isLoading, - setIsLoading, - setSnackbar, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [showAuthMsg, setShowAuthMsg] = useState(false) -const authorizeHandler = () => { - setIsLoading('auth') - bitsFetch({}, 'seopress_authorize').then(result => { - if (result?.success) { - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Connected with SEOPress Successfully', 'bit-integrations') - }) - } - setIsLoading(false) - setShowAuthMsg(true) - }) - } - - const handleInput = e => { - const newConf = { ...seoPressConf } - newConf[e.target.name] = e.target.value - setSeoPressConf(newConf) - } + const setStep = useCallback(value => nextPage(value), [nextPage]) return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - - {isLoading === 'auth' && ( -
- - {__('Checking if SEOPress is active!!!', 'bit-integrations')} -
- )} - - {showAuthMsg && !isAuthorized && !isLoading && ( -
-
-
- -
-
- {__('SEOPress is not activated or not installed', 'bit-integrations')} -
-
-
- )} - - {showAuthMsg && isAuthorized && !isLoading && ( -
-
- -
-
{__('SEOPress is activated', 'bit-integrations')}
-
- )} - - -
- -
+ ) } From 30ea100376eeb244702089bc5f0764c92b32eda6 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Wed, 13 May 2026 10:37:31 +0600 Subject: [PATCH 38/58] refactor: update SureCart integration to use bearer token and streamline authorization process --- backend/Actions/SureCart/RecordApiHelper.php | 8 +- backend/Actions/SureCart/Routes.php | 5 - .../Actions/SureCart/SureCartController.php | 68 ++-------- .../AllIntegrations/SureCart/SureCart.jsx | 8 +- .../SureCart/SureCartAuthorization.jsx | 128 ++++-------------- .../SureCart/SureCartCommonFunc.js | 36 ----- 6 files changed, 40 insertions(+), 213 deletions(-) diff --git a/backend/Actions/SureCart/RecordApiHelper.php b/backend/Actions/SureCart/RecordApiHelper.php index a81121ae1..a30556396 100644 --- a/backend/Actions/SureCart/RecordApiHelper.php +++ b/backend/Actions/SureCart/RecordApiHelper.php @@ -31,11 +31,11 @@ public function generateReqDataFromFieldMap($data, $fieldMap) return $dataFinal; } - public function createCustomer($finalData, $api_key) + public function createCustomer($finalData, $token) { $requestData = [ 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, + 'Authorization' => 'Bearer ' . $token, 'User-Agent' => 'bit-integrations', 'Content-Type' => 'application/json', ], @@ -62,7 +62,7 @@ public function createCustomer($finalData, $api_key) } public function execute( - $api_key, + $token, $fieldValues, $fieldMap, $integrationDetails, @@ -71,7 +71,7 @@ public function execute( $finalData = $this->generateReqDataFromFieldMap($fieldValues, $fieldMap); if ($mainAction == '1') { - $apiResponse = $this->createCustomer($finalData, $api_key); + $apiResponse = $this->createCustomer($finalData, $token); if ($apiResponse[1] === 200) { LogHandler::save($this->_integrationID, wp_json_encode(['type' => 'create', 'type_name' => 'create-customer']), 'success', $apiResponse[0]); } else { diff --git a/backend/Actions/SureCart/Routes.php b/backend/Actions/SureCart/Routes.php index 1ba85c02e..5c539fc24 100644 --- a/backend/Actions/SureCart/Routes.php +++ b/backend/Actions/SureCart/Routes.php @@ -3,8 +3,3 @@ if (!defined('ABSPATH')) { exit; } - -use BitApps\Integrations\Actions\SureCart\SureCartController; -use BitApps\Integrations\Core\Util\Route; - -Route::post('sureCart_authorization', [SureCartController::class, 'checkAuthorization']); diff --git a/backend/Actions/SureCart/SureCartController.php b/backend/Actions/SureCart/SureCartController.php index c34aef239..64d3f8777 100644 --- a/backend/Actions/SureCart/SureCartController.php +++ b/backend/Actions/SureCart/SureCartController.php @@ -2,75 +2,29 @@ namespace BitApps\Integrations\Actions\SureCart; +use BitApps\Integrations\Authorization\AuthorizationType; use WP_Error; class SureCartController { - public $api_url = 'https://api.surecart.com/v1/'; - - public function checkAuthorization($tokenRequestParams) - { - if ( - empty($tokenRequestParams->api_key) || empty($tokenRequestParams->auth_url) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiKey = $tokenRequestParams->api_key; - $webhook_url = $tokenRequestParams->auth_url . '/surecart/webhooks'; - - $request_data = [ - 'webhook_endpoint' => [ - 'description' => 'Authorization', - 'enabled' => true, - 'destination' => 'wordpress', - 'url' => $webhook_url, - ], - ]; - - $headers = [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $apiKey, - 'User-Agent' => 'bit-integrations', - 'Content-Type' => 'application/json', - ], - 'timeout' => 60, - 'sslverify' => false, - 'data_format' => 'body', - 'body' => wp_json_encode($request_data), - ]; - - $request = wp_remote_post($this->api_url . 'webhook_endpoints', $headers); - if (is_wp_error($request)) { - wp_send_json_error($request->get_error_message(), 400); - } - $request_body = wp_remote_retrieve_body($request); - $request_data = json_decode($request_body); - if (!$request_data || $request_data->code !== 'unauthorized') { - wp_send_json_success($request_body, 200); - } else { - wp_send_json_error( - $request_data->message, - 400 - ); - } - } + public static array $authConfig = [ + 'authType' => AuthorizationType::BEARER_TOKEN, + 'slug' => 'surecart', + 'fields' => [ + 'token' => 'token', + ], + ]; public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integrationId = $integrationData->id; - $api_key = $integrationDetails->api_key; + $token = $integrationDetails->token ?: ($integrationDetails->api_key ?: ($integrationDetails->value ?? '')); $fieldMap = $integrationDetails->field_map; $mainAction = $integrationDetails->mainAction; if ( - empty($api_key) + empty($token) || empty($integrationDetails) || empty($fieldMap) @@ -81,7 +35,7 @@ public function execute($integrationData, $fieldValues) $recordApiHelper = new RecordApiHelper($integrationId); return $recordApiHelper->execute( - $api_key, + $token, $fieldValues, $fieldMap, $integrationDetails, diff --git a/frontend/src/components/AllIntegrations/SureCart/SureCart.jsx b/frontend/src/components/AllIntegrations/SureCart/SureCart.jsx index c13cddd3b..d3709bfb6 100644 --- a/frontend/src/components/AllIntegrations/SureCart/SureCart.jsx +++ b/frontend/src/components/AllIntegrations/SureCart/SureCart.jsx @@ -1,7 +1,7 @@ /* eslint-disable no-unused-expressions */ import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { useNavigate, useParams } from 'react-router' +import { useNavigate } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' @@ -14,7 +14,6 @@ import SureCartIntegLayout from './SureCartIntegLayout' function SureCart({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() - const { formID } = useParams() const [isLoading, setIsLoading] = useState(false) const [step, setStep] = useState(1) const [snack, setSnackbar] = useState({ show: false }) @@ -40,7 +39,6 @@ function SureCart({ formFields, setFlow, flow, allIntegURL }) { name: 'SureCart', type: 'SureCart', mainAction: '', - api_key: '', field_map: [{ formField: '', SureCartFormField: '' }], customerFields, allActions, @@ -67,14 +65,10 @@ function SureCart({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/SureCart/SureCartAuthorization.jsx b/frontend/src/components/AllIntegrations/SureCart/SureCartAuthorization.jsx index d0b182f25..e650c522a 100644 --- a/frontend/src/components/AllIntegrations/SureCart/SureCartAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SureCart/SureCartAuthorization.jsx @@ -1,118 +1,38 @@ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize } from './SureCartCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function SureCartAuthorization({ - formID, sureCartConf, setSureCartConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setStep(2) - } - const handleInput = e => { - const newConf = { ...sureCartConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSureCartConf(newConf) - } + const note = ` + + ${__('To get bearer token, please visit', 'bit-integrations')} + + ${__(' SureCart developer settings', 'bit-integrations')} + + ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('App api key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get Api key , Please Visit ', 'bit-integrations')} - - {__('SureCart', 'bit-integrations')} - - -
-
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/SureCart/SureCartCommonFunc.js b/frontend/src/components/AllIntegrations/SureCart/SureCartCommonFunc.js index ef31df6b1..bb0670b4d 100644 --- a/frontend/src/components/AllIntegrations/SureCart/SureCartCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SureCart/SureCartCommonFunc.js @@ -1,8 +1,3 @@ -/* eslint-disable no-else-return */ -import toast from 'react-hot-toast' -import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' - export const handleInput = (e, slackConf, setSlackConf) => { const newConf = { ...slackConf } const { name } = e.target @@ -14,37 +9,6 @@ export const handleInput = (e, slackConf, setSlackConf) => { setSlackConf({ ...newConf }) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setIsAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.api_key) { - setError({ api_key: !confTmp.api_key ? __("Api Key can't be empty", 'bit-integrations') : '' }) - return - } - setError({}) - setIsLoading(true) - const auth_url = window.location.origin - const tokenRequestParams = { api_key: confTmp.api_key, auth_url } - - bitsFetch(tokenRequestParams, 'sureCart_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - setConf(newConf) - setIsAuthorized(true) - setIsLoading(false) - toast.success(__('Authorized Successfully', 'bit-integrations')) - return - } - toast.error(__('Authorization Failed', 'bit-integrations')) - setIsLoading(false) - }) -} - export const checkMappedFields = fieldsMapped => { const checkedField = fieldsMapped ? fieldsMapped?.filter(item => !item.formField || !item.SureCartFormField) From 3c38dddac9c0d76d1cfb51ad85708ea6e9b3a4a3 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Wed, 13 May 2026 11:21:58 +0600 Subject: [PATCH 39/58] refactor: integration components to use a unified Authorization component for OAuth flows - Updated Notion integration to simplify loading logic. - Removed unused imports and state management in PCloud and Zoom integrations. - Replaced custom authorization logic with a shared Authorization component in PCloud, Zoom, and ZoomWebinar integrations. - Streamlined the handling of OAuth parameters and requests across integrations. - Improved code readability and maintainability by reducing duplication and enhancing structure. --- .../GoogleCalendarController.php | 130 +++++++++---- backend/Actions/GoogleCalendar/Routes.php | 1 - .../GoogleDrive/GoogleDriveController.php | 141 +++++++++----- backend/Actions/GoogleDrive/Routes.php | 1 - backend/Actions/Notion/NotionController.php | 59 +++--- backend/Actions/Notion/Routes.php | 1 - backend/Actions/PCloud/PCloudController.php | 46 ++--- backend/Actions/PCloud/Routes.php | 1 - backend/Actions/Zoom/Routes.php | 1 - backend/Actions/Zoom/ZoomController.php | 103 +++++++--- backend/Actions/ZoomWebinar/Routes.php | 1 - .../ZoomWebinar/ZoomWebinarController.php | 85 ++++++--- .../GoogleCalendar/GoogleCalendar.jsx | 7 +- .../GoogleCalendarAuthorization.jsx | 180 ++++-------------- .../GoogleCalendarCommonFunc.js | 92 +-------- .../GoogleDrive/GoogleDrive.jsx | 7 +- .../GoogleDrive/GoogleDriveAuthorization.jsx | 180 ++++-------------- .../GoogleDrive/GoogleDriveCommonFunc.js | 92 +-------- .../AllIntegrations/Notion/Notion.jsx | 9 +- .../Notion/NotionAuthorization.jsx | 115 +++-------- .../Notion/NotionCommonFunc.js | 93 +-------- .../Notion/NotionIntegLayout.jsx | 2 +- .../AllIntegrations/PCloud/PCloud.jsx | 15 +- .../PCloud/PCloudAuthorization.jsx | 166 ++++------------ .../PCloud/PCloudCommonFunc.js | 92 +-------- .../PCloud/PCloudIntegLayout.jsx | 2 +- .../components/AllIntegrations/Zoom/Zoom.jsx | 11 +- .../Zoom/ZoomAuthorization.jsx | 166 +++------------- .../AllIntegrations/Zoom/ZoomCommonFunc.js | 148 ++------------ .../ZoomWebinar/ZoomCommonFunc.js | 139 +------------- .../ZoomWebinar/ZoomWebinar.jsx | 11 +- .../ZoomWebinar/ZoomWebinarAuthorization.jsx | 166 +++------------- 32 files changed, 609 insertions(+), 1654 deletions(-) diff --git a/backend/Actions/GoogleCalendar/GoogleCalendarController.php b/backend/Actions/GoogleCalendar/GoogleCalendarController.php index ec2453a61..0e20e4601 100644 --- a/backend/Actions/GoogleCalendar/GoogleCalendarController.php +++ b/backend/Actions/GoogleCalendar/GoogleCalendarController.php @@ -3,6 +3,7 @@ namespace BitApps\Integrations\Actions\GoogleCalendar; use BitApps\Integrations\Actions\GoogleCalendar\RecordApiHelper as GoogleCalendarRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -10,6 +11,16 @@ class GoogleCalendarController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'googlecalendar', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -17,43 +28,41 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) + public static function getAllCalendarLists($queryParams) { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + $flowID = $queryParams->flowID ?? null; + $isConnectionAuth = !empty($queryParams->connection_id); + + if (empty($tokenDetails->access_token) && !empty($queryParams->accessToken)) { + $tokenDetails->access_token = $queryParams->accessToken; + $tokenDetails->refresh_token = $queryParams->refreshToken ?? ''; } - $body = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestParams->clientId, - 'client_secret' => $requestParams->clientSecret, - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'code' => urldecode($requestParams->code) - ]; + if (empty($tokenDetails->access_token)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + } - $apiEndpoint = 'https://oauth2.googleapis.com/token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; + $oldToken = $tokenDetails->access_token; - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); + } - public static function getAllCalendarLists($queryParams) - { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + if (!empty($flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($flowID, $tokenDetails); + } } - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); - $lists = self::getGoogleCalendarList($token->access_token); + $lists = self::getGoogleCalendarList($tokenDetails->access_token); $data = []; - if (\is_array($lists->items)) { + if (!empty($lists) && !empty($lists->items) && \is_array($lists->items)) { foreach ($lists->items as $list) { $data[] = (object) [ 'id' => $list->id, @@ -64,28 +73,47 @@ public static function getAllCalendarLists($queryParams) } $response['googleCalendarLists'] = $data; - $response['tokenDetails'] = $token; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response, 200); } public function execute($integrationData, $fieldValues) { - if (empty($integrationData->flow_details->tokenDetails->access_token)) { - // translators: %s: Service name - LogHandler::save($this->integrationID, wp_json_encode(['type' => 'record', 'type_name' => 'insert']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleCalendar')); - - return false; - } - $integrationDetails = $integrationData->flow_details; $actions = $integrationDetails->actions; $timeZone = $integrationDetails->timeZone; $fieldMap = $integrationDetails->field_map; $calendarId = $integrationDetails->calendarId; $reminderFieldMap = $integrationDetails->reminder_field_map; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $integrationDetails->tokenDetails->access_token) { - $this->saveRefreshedToken($this->integrationID, $tokenDetails); + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + + if (empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'record', 'type_name' => 'insert']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleCalendar')); + + return false; + } + + $oldToken = $tokenDetails->access_token; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck( + $tokenDetails, + $integrationDetails->clientId ?? '', + $integrationDetails->clientSecret ?? '' + ); + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'record', 'type_name' => 'insert']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleCalendar')); + + return false; + } + + if ($tokenDetails->access_token !== $oldToken) { + $this->saveRefreshedToken($this->integrationID, $tokenDetails); + } } if (empty($fieldMap)) { @@ -125,15 +153,21 @@ protected static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; } - $token->access_token = $refreshToken->access_token; - $token->expires_in = $refreshToken->expires_in; - $token->generates_on = $refreshToken->generates_on; + if (isset($refreshToken->access_token)) { + $token->access_token = $refreshToken->access_token; + $token->expires_in = $refreshToken->expires_in; + $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; + $token->refresh_token = $refreshToken->refresh_token; + } } return $token; @@ -155,6 +189,20 @@ protected static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + protected static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/backend/Actions/GoogleCalendar/Routes.php b/backend/Actions/GoogleCalendar/Routes.php index 93a635f05..670b36d4f 100644 --- a/backend/Actions/GoogleCalendar/Routes.php +++ b/backend/Actions/GoogleCalendar/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\GoogleCalendar\GoogleCalendarController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('googleCalendar_authorization', [GoogleCalendarController::class, 'authorization']); Route::post('googleCalendar_get_all_lists', [GoogleCalendarController::class, 'getAllCalendarLists']); diff --git a/backend/Actions/GoogleDrive/GoogleDriveController.php b/backend/Actions/GoogleDrive/GoogleDriveController.php index 044a7e051..69ac7fc0d 100644 --- a/backend/Actions/GoogleDrive/GoogleDriveController.php +++ b/backend/Actions/GoogleDrive/GoogleDriveController.php @@ -3,6 +3,7 @@ namespace BitApps\Integrations\Actions\GoogleDrive; use BitApps\Integrations\Actions\GoogleDrive\RecordApiHelper as GoogleDriveRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -10,6 +11,16 @@ class GoogleDriveController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'googledrive', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -17,57 +28,54 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) + public static function getAllFolders($queryParams) { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + $flowID = $queryParams->flowID ?? null; + $isConnectionAuth = !empty($queryParams->connection_id); - $body = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestParams->clientId, - 'client_secret' => $requestParams->clientSecret, - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'code' => urldecode($requestParams->code) - ]; - - $apiEndpoint = 'https://oauth2.googleapis.com/token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); + if (empty($tokenDetails->access_token) && !empty($queryParams->accessToken)) { + $tokenDetails->access_token = $queryParams->accessToken; + $tokenDetails->refresh_token = $queryParams->refreshToken ?? ''; } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllFolders($queryParams) - { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { + if (empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); - if ($token->access_token !== $queryParams->tokenDetails->access_token) { - self::saveRefreshedToken($queryParams->flowID, $token); + $oldToken = $tokenDetails->access_token; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); + } + + if (!empty($flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($flowID, $tokenDetails); + } } - $folders = self::getGoogleDriveFoldersList($token->access_token); - $folders = self::getPathFromParentId($folders->files); + $folders = self::getGoogleDriveFoldersList($tokenDetails->access_token); $data = []; - if (\is_array($folders)) { - foreach ($folders as $folder) { - $data[] = (object) [ - 'id' => $folder->id, - 'name' => $folder->name, - ]; + if (!empty($folders) && !empty($folders->files)) { + $folders = self::getPathFromParentId($folders->files); + if (\is_array($folders)) { + foreach ($folders as $folder) { + $data[] = (object) [ + 'id' => $folder->id, + 'name' => $folder->name, + ]; + } } } $response['googleDriveFoldersList'] = $data; - $response['tokenDetails'] = $token; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response, 200); } @@ -90,19 +98,38 @@ public static function getGoogleDriveFoldersList($token) public function execute($integrationData, $fieldValues) { - if (empty($integrationData->flow_details->tokenDetails->access_token)) { + $integrationDetails = $integrationData->flow_details; + $actions = $integrationDetails->actions; + $fieldMap = $integrationDetails->field_map; + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + + if (empty($tokenDetails->access_token)) { // translators: %s: Service name LogHandler::save($this->integrationID, wp_json_encode(['type' => 'googleDrive', 'type_name' => 'file_upload']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleDrive')); return false; } - $integrationDetails = $integrationData->flow_details; - $actions = $integrationDetails->actions; - $fieldMap = $integrationDetails->field_map; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $integrationDetails->tokenDetails->access_token) { - self::saveRefreshedToken($this->integrationID, $tokenDetails); + $oldToken = $tokenDetails->access_token; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck( + $tokenDetails, + $integrationDetails->clientId ?? '', + $integrationDetails->clientSecret ?? '' + ); + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'googleDrive', 'type_name' => 'file_upload']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleDrive')); + + return false; + } + + if ($tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($this->integrationID, $tokenDetails); + } } if (empty($fieldMap)) { @@ -155,15 +182,21 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; } - $token->access_token = $refreshToken->access_token; - $token->expires_in = $refreshToken->expires_in; - $token->generates_on = $refreshToken->generates_on; + if (isset($refreshToken->access_token)) { + $token->access_token = $refreshToken->access_token; + $token->expires_in = $refreshToken->expires_in; + $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; + $token->refresh_token = $refreshToken->refresh_token; + } } return $token; @@ -185,6 +218,20 @@ private static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/backend/Actions/GoogleDrive/Routes.php b/backend/Actions/GoogleDrive/Routes.php index 751a1f328..d0e29db27 100644 --- a/backend/Actions/GoogleDrive/Routes.php +++ b/backend/Actions/GoogleDrive/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\GoogleDrive\GoogleDriveController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('googleDrive_authorization', [GoogleDriveController::class, 'authorization']); Route::post('googleDrive_get_all_folders', [GoogleDriveController::class, 'getAllFolders']); diff --git a/backend/Actions/Notion/NotionController.php b/backend/Actions/Notion/NotionController.php index b3a8c60b7..a5aaa8ae5 100644 --- a/backend/Actions/Notion/NotionController.php +++ b/backend/Actions/Notion/NotionController.php @@ -2,44 +2,30 @@ namespace BitApps\Integrations\Actions\Notion; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class NotionController { - private $baseurl = 'https://api.notion.com/v1/'; - - public function authorization($requestParams) - { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $body = [ - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'grant_type' => 'authorization_code', - 'code' => $requestParams->code - ]; - $apiEndpoint = "{$this->baseurl}oauth/token"; - - $clientId = $requestParams->clientId; - $clientSecret = $requestParams->clientSecret; + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'notion', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; - $header['Content-Type'] = 'application/json'; - $header['Authorization'] = 'Basic ' . base64_encode("{$clientId}:{$clientSecret}"); - - $apiResponse = HttpHelper::post($apiEndpoint, wp_json_encode($body), $header); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); - } - $apiResponse->generates_on = time(); - - wp_send_json_success($apiResponse, 200); - } + private $baseurl = 'https://api.notion.com/v1/'; public function getAllDatabaseLists($requestParams) { - if (empty($requestParams->accessToken)) { + $accessToken = $requestParams->accessToken + ?? ($requestParams->tokenDetails->access_token ?? ($requestParams->access_token ?? '')); + + if (empty($accessToken)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -50,7 +36,7 @@ public function getAllDatabaseLists($requestParams) } $apiEndpoints = "{$this->baseurl}search"; $headers = [ - 'Authorization' => 'Bearer ' . $requestParams->accessToken, + 'Authorization' => 'Bearer ' . $accessToken, 'Notion-Version' => '2021-08-16' ]; $response = HttpHelper::post($apiEndpoints, null, $headers); @@ -68,7 +54,10 @@ public function getAllDatabaseLists($requestParams) public function getFieldsProperties($requestParams) { - if (empty($requestParams->accessToken)) { + $accessToken = $requestParams->accessToken + ?? ($requestParams->tokenDetails->access_token ?? ($requestParams->access_token ?? '')); + + if (empty($accessToken)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -79,7 +68,7 @@ public function getFieldsProperties($requestParams) } $apiEndpoints = "{$this->baseurl}databases/{$requestParams->databaseId}"; $headers = [ - 'Authorization' => 'Bearer ' . $requestParams->accessToken, + 'Authorization' => 'Bearer ' . $accessToken, 'Notion-Version' => '2021-08-16' ]; $response = HttpHelper::get($apiEndpoints, null, $headers); @@ -101,9 +90,9 @@ public function execute($integrationData, $fieldValues) $integId = $integrationData->id; $databaseId = $integrationDetails->databaseId; $notionFields = $integrationDetails->notionFields; - $tokenDetails = $integrationDetails->tokenDetails; - $accessToken = $tokenDetails->access_token; - $tokenType = $tokenDetails->token_type; + $tokenDetails = $integrationDetails->tokenDetails ?? null; + $accessToken = $tokenDetails->access_token ?? ($integrationDetails->access_token ?? ''); + $tokenType = $tokenDetails->token_type ?? ($integrationDetails->token_type ?? 'Bearer'); $field_map = $integrationDetails->field_map; if ( diff --git a/backend/Actions/Notion/Routes.php b/backend/Actions/Notion/Routes.php index 0300eb863..b7f119083 100644 --- a/backend/Actions/Notion/Routes.php +++ b/backend/Actions/Notion/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Notion\NotionController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('notion_authorization', [NotionController::class, 'authorization']); Route::post('notion_database_lists', [NotionController::class, 'getAllDatabaseLists']); Route::post('notion_database_properties', [NotionController::class, 'getFieldsProperties']); diff --git a/backend/Actions/PCloud/PCloudController.php b/backend/Actions/PCloud/PCloudController.php index 2ca7321f7..7377cf5fe 100644 --- a/backend/Actions/PCloud/PCloudController.php +++ b/backend/Actions/PCloud/PCloudController.php @@ -3,12 +3,23 @@ namespace BitApps\Integrations\Actions\PCloud; use BitApps\Integrations\Actions\PCloud\RecordApiHelper as PCloudRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Log\LogHandler; use WP_Error; class PCloudController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'pcloud', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -16,37 +27,16 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) - { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $body = [ - 'client_id' => $requestParams->clientId, - 'client_secret' => $requestParams->clientSecret, - 'code' => $requestParams->code - ]; - - $apiEndpoint = 'https://api.pcloud.com/oauth2_token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, 400); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllFolders($queryParams) { - if (empty($queryParams->tokenDetails)) { + $accessToken = $queryParams->tokenDetails->access_token ?? ($queryParams->access_token ?? ''); + + if (empty($accessToken)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } $apiEndpoint = 'https://api.pcloud.com/listfolder?folderid=0'; - $header['Authorization'] = 'Bearer ' . $queryParams->tokenDetails->access_token; + $header['Authorization'] = 'Bearer ' . $accessToken; $apiResponse = HttpHelper::get($apiEndpoint, null, $header); @@ -67,7 +57,10 @@ public static function getAllFolders($queryParams) public function execute($integrationData, $fieldValues) { - if (empty($integrationData->flow_details->tokenDetails->access_token)) { + $accessToken = $integrationData->flow_details->tokenDetails->access_token + ?? ($integrationData->flow_details->access_token ?? ''); + + if (empty($accessToken)) { // translators: %s: Service name LogHandler::save($this->integrationID, wp_json_encode(['type' => 'pCloud', 'type_name' => 'file_upload']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'PCloud')); @@ -77,7 +70,6 @@ public function execute($integrationData, $fieldValues) $integrationDetails = $integrationData->flow_details; $actions = $integrationDetails->actions; $fieldMap = $integrationDetails->field_map; - $accessToken = $integrationDetails->tokenDetails->access_token; if (empty($fieldMap)) { $error = new WP_Error('REQ_FIELD_EMPTY', __('Required fields not mapped', 'bit-integrations')); diff --git a/backend/Actions/PCloud/Routes.php b/backend/Actions/PCloud/Routes.php index ffd20e1dd..822dbbaf0 100644 --- a/backend/Actions/PCloud/Routes.php +++ b/backend/Actions/PCloud/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\PCloud\PCloudController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('pCloud_authorization', [PCloudController::class, 'authorization']); Route::post('pCloud_get_all_folders', [PCloudController::class, 'getAllFolders']); diff --git a/backend/Actions/Zoom/Routes.php b/backend/Actions/Zoom/Routes.php index 66caeb5dc..2208e06e2 100644 --- a/backend/Actions/Zoom/Routes.php +++ b/backend/Actions/Zoom/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Zoom\ZoomController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zoom_generate_token', [ZoomController::class, 'authorization']); Route::post('zoom_fetch_all_meetings', [ZoomController::class, 'zoomFetchAllMeetings']); Route::post('zoom_fetch_all_fields', [ZoomController::class, 'zoomFetchAllCustomFields']); diff --git a/backend/Actions/Zoom/ZoomController.php b/backend/Actions/Zoom/ZoomController.php index 0bc1d400c..7d2b2d171 100644 --- a/backend/Actions/Zoom/ZoomController.php +++ b/backend/Actions/Zoom/ZoomController.php @@ -2,12 +2,24 @@ namespace BitApps\Integrations\Actions\Zoom; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; class ZoomController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zoom', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + 'accessToken' => 'access_token', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -15,36 +27,28 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) + public static function zoomFetchAllMeetings($requestParams) { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $body = [ - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'grant_type' => 'authorization_code', - 'code' => urldecode($requestParams->code) - ]; - - $apiEndpoint = 'https://zoom.us/oauth/token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $header['Authorization'] = 'Basic ' . base64_encode("{$requestParams->clientId}:{$requestParams->clientSecret}"); - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); + $tokenDetails = $requestParams->tokenDetails ?? null; + $clientId = $requestParams->clientId ?? ''; + $clientSecret = $requestParams->clientSecret ?? ''; + + if (empty($tokenDetails) && !empty($requestParams->accessToken)) { + $tokenDetails = (object) [ + 'access_token' => $requestParams->accessToken, + 'refresh_token' => $requestParams->refreshToken ?? '', + ]; } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function zoomFetchAllMeetings($requestParams) - { - if (empty($requestParams->tokenDetails) || empty($requestParams->clientId) || empty($requestParams->clientSecret)) { + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $tokenDetails = self::tokenExpiryCheck($requestParams->tokenDetails, $requestParams->clientId, $requestParams->clientSecret); + if (!empty($requestParams->connection_id)) { + $tokenDetails = self::normalizeConnectionToken($tokenDetails); + } else { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); + } $header = [ 'Authorization' => 'Bearer ' . $tokenDetails->access_token, 'Content-Type' => 'application/json' @@ -63,11 +67,26 @@ public static function zoomFetchAllMeetings($requestParams) public static function zoomFetchAllCustomFields($requestParams) { - if (empty($requestParams->tokenDetails) || empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->meetingId)) { + $tokenDetails = $requestParams->tokenDetails ?? null; + $clientId = $requestParams->clientId ?? ''; + $clientSecret = $requestParams->clientSecret ?? ''; + + if (empty($tokenDetails) && !empty($requestParams->accessToken)) { + $tokenDetails = (object) [ + 'access_token' => $requestParams->accessToken, + 'refresh_token' => $requestParams->refreshToken ?? '', + ]; + } + + if (empty($tokenDetails) || empty($tokenDetails->access_token) || empty($requestParams->meetingId)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $tokenDetails = self::tokenExpiryCheck($requestParams->tokenDetails, $requestParams->clientId, $requestParams->clientSecret); + if (!empty($requestParams->connection_id)) { + $tokenDetails = self::normalizeConnectionToken($tokenDetails); + } else { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); + } $header = [ 'Authorization' => 'Bearer ' . $tokenDetails->access_token, 'Content-Type' => 'application/json' @@ -118,9 +137,16 @@ public function execute($integrationData, $fieldValues) $actions = $integrationDetails->actions; $defaultDataConf = $integrationDetails->default; $selectedAction = $integrationDetails->selectedActions; - $oldToken = $integrationDetails->tokenDetails->access_token; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $oldToken) { + + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails); + $oldToken = $tokenDetails->access_token ?? ''; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); + } + + if (!$isConnectionAuth && $tokenDetails->access_token !== $oldToken) { self::saveRefreshedToken($this->integrationID, $tokenDetails); } if ( @@ -155,7 +181,9 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; @@ -165,6 +193,7 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) $token->access_token = $refreshToken->access_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; $token->refresh_token = $refreshToken->refresh_token; } } @@ -191,6 +220,20 @@ private static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/backend/Actions/ZoomWebinar/Routes.php b/backend/Actions/ZoomWebinar/Routes.php index 4465fc553..7863ff6fa 100644 --- a/backend/Actions/ZoomWebinar/Routes.php +++ b/backend/Actions/ZoomWebinar/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\ZoomWebinar\ZoomWebinarController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zoom_webinar_generate_token', [ZoomWebinarController::class, 'authorization']); Route::post('zoom_webinar_fetch_all_webinar', [ZoomWebinarController::class, 'zoomFetchAllWebinar']); diff --git a/backend/Actions/ZoomWebinar/ZoomWebinarController.php b/backend/Actions/ZoomWebinar/ZoomWebinarController.php index dbaa1116c..aba4c7839 100644 --- a/backend/Actions/ZoomWebinar/ZoomWebinarController.php +++ b/backend/Actions/ZoomWebinar/ZoomWebinarController.php @@ -2,12 +2,24 @@ namespace BitApps\Integrations\Actions\ZoomWebinar; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; class ZoomWebinarController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zoomwebinar', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + 'accessToken' => 'access_token', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -15,36 +27,31 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) + public static function zoomFetchAllWebinar($requestParams) { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + $tokenDetails = $requestParams->tokenDetails ?? null; + $clientId = $requestParams->clientId ?? ''; + $clientSecret = $requestParams->clientSecret ?? ''; + + if (empty($tokenDetails) && !empty($requestParams->accessToken)) { + $tokenDetails = (object) [ + 'access_token' => $requestParams->accessToken, + 'refresh_token' => $requestParams->refreshToken ?? '', + ]; } - $body = [ - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'grant_type' => 'authorization_code', - 'code' => urldecode($requestParams->code) - ]; - - $apiEndpoint = 'https://zoom.us/oauth/token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $header['Authorization'] = 'Basic ' . base64_encode("{$requestParams->clientId}:{$requestParams->clientSecret}"); - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function zoomFetchAllWebinar($requestParams) - { - if (empty($requestParams->accessToken)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + if (!empty($requestParams->connection_id)) { + $tokenDetails = self::normalizeConnectionToken($tokenDetails); + } else { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); } + $header = [ - 'Authorization' => 'Bearer ' . $requestParams->accessToken, + 'Authorization' => 'Bearer ' . $tokenDetails->access_token, 'Content-Type' => 'application/json' ]; @@ -68,9 +75,16 @@ public function execute($integrationData, $fieldValues) $actions = $integrationDetails->actions; $defaultDataConf = $integrationDetails->default; $selectedAction = $integrationDetails->selectedActions; - $oldToken = $integrationDetails->tokenDetails->access_token; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $oldToken) { + + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails); + $oldToken = $tokenDetails->access_token ?? ''; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); + } + + if (!$isConnectionAuth && $tokenDetails->access_token !== $oldToken) { self::saveRefreshedToken($this->integrationID, $tokenDetails); } if ( @@ -105,7 +119,9 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; @@ -115,6 +131,7 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) $token->access_token = $refreshToken->access_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; $token->refresh_token = $refreshToken->refresh_token; } } @@ -141,6 +158,20 @@ private static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendar.jsx b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendar.jsx index 090e08b72..18a3185bc 100644 --- a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendar.jsx +++ b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendar.jsx @@ -1,11 +1,10 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from '../IntegrationHelpers/GoogleIntegrationHelpers' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import GoogleCalendarAuthorization from './GoogleCalendarAuthorization' @@ -39,10 +38,6 @@ function GoogleCalendar({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('googleCalendar') - }, []) - const saveConfig = () => { saveActionConf({ flow, diff --git a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarAuthorization.jsx b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarAuthorization.jsx index 4bd950bf8..19292d1b2 100644 --- a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarAuthorization.jsx @@ -1,156 +1,52 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { getAllGoogleCalendarLists, handleAuthorize } from './GoogleCalendarCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function GoogleCalendarAuthorization({ - flowID, googleCalendarConf, setGoogleCalendarConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - getAllGoogleCalendarLists(flowID, googleCalendarConf, setGoogleCalendarConf, setIsLoading) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...googleCalendarConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setGoogleCalendarConf(newConf) - } + const note = ` +

${__('Google Calendar OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create OAuth client in Google API Console.', 'bit-integrations')}
  • +
  • ${__('Set homepage and redirect URI exactly from integration settings.', 'bit-integrations')}
  • +
  • ${__('Enable Google Calendar API and authorize with required scope.', 'bit-integrations')}
  • +
+ ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('Google API Console', 'bit-integrations')} - - - -
- {__('GoogleCalendar Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('GoogleCalendar Client Secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarCommonFunc.js b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarCommonFunc.js index 38b7cce6d..35d3be316 100644 --- a/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarCommonFunc.js +++ b/frontend/src/components/AllIntegrations/GoogleCalendar/GoogleCalendarCommonFunc.js @@ -22,12 +22,19 @@ export const checkMappedFields = fieldsMapped => { return true } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const getAllGoogleCalendarLists = (flowID, googleCalendarConf, setGoogleCalendarConf) => { const queryParams = { flowID: flowID ?? null, - clientId: googleCalendarConf.clientId, - clientSecret: googleCalendarConf.clientSecret, - tokenDetails: googleCalendarConf.tokenDetails + ...buildAuthRequestParams(googleCalendarConf) } const loadPostTypes = bitsFetch(queryParams, 'googleCalendar_get_all_lists').then(result => { if (result && result.success) { @@ -49,82 +56,3 @@ export const getAllGoogleCalendarLists = (flowID, googleCalendarConf, setGoogleC loading: __('Loading Google Calendar List...', 'bit-integrations') }) } - -export const handleAuthorize = (confTmp, setConf, setIsAuthorized, setIsLoading, setError, btcbi) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = 'https://www.googleapis.com/auth/calendar' - // eslint-disable-next-line no-undef - const apiEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?scope=${scopes}&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}&client_id=${confTmp.clientId}` - const authWindow = window.open(apiEndpoint, 'googleCalendar', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const bitsGoogleCalendar = localStorage.getItem('__googleCalendar') - if (bitsGoogleCalendar) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleCalendar) - localStorage.removeItem('__googleCalendar') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'googleCalendar_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDrive.jsx b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDrive.jsx index 698e4882b..6f1523073 100644 --- a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDrive.jsx +++ b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDrive.jsx @@ -1,11 +1,10 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from '../IntegrationHelpers/GoogleIntegrationHelpers' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import GoogleDriveAuthorization from './GoogleDriveAuthorization' @@ -28,10 +27,6 @@ function GoogleDrive({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('googleDrive') - }, []) - const saveConfig = () => { saveActionConf({ flow, diff --git a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveAuthorization.jsx b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveAuthorization.jsx index 54be78e86..01065aaf3 100644 --- a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveAuthorization.jsx @@ -1,156 +1,52 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { getAllGoogleDriveFolders, handleAuthorize } from './GoogleDriveCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' export default function GoogleDriveAuthorization({ - flowID, googleDriveConf, setGoogleDriveConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - getAllGoogleDriveFolders(flowID, googleDriveConf, setGoogleDriveConf, setIsLoading) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...googleDriveConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setGoogleDriveConf(newConf) - } + const note = ` +

${__('Google Drive OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create OAuth client in Google API Console.', 'bit-integrations')}
  • +
  • ${__('Set homepage and redirect URI exactly from integration settings.', 'bit-integrations')}
  • +
  • ${__('Enable Google Drive API and authorize with required scope.', 'bit-integrations')}
  • +
+ ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('Google API Console', 'bit-integrations')} - - - -
- {__('GoogleDrive Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('GoogleDrive Client Secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveCommonFunc.js b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveCommonFunc.js index 317dffbd0..a2c144c6c 100644 --- a/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveCommonFunc.js +++ b/frontend/src/components/AllIntegrations/GoogleDrive/GoogleDriveCommonFunc.js @@ -14,12 +14,19 @@ export const handleInput = (e, googleDriveConf, setGoogleDriveConf) => { setGoogleDriveConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const getAllGoogleDriveFolders = (flowID, googleDriveConf, setGoogleDriveConf) => { const queryParams = { flowID: flowID ?? null, - clientId: googleDriveConf.clientId, - clientSecret: googleDriveConf.clientSecret, - tokenDetails: googleDriveConf.tokenDetails + ...buildAuthRequestParams(googleDriveConf) } const loadPostTypes = bitsFetch(queryParams, 'googleDrive_get_all_folders').then(result => { if (result && result.success) { @@ -41,82 +48,3 @@ export const getAllGoogleDriveFolders = (flowID, googleDriveConf, setGoogleDrive loading: __('Loading GoogleDrive Folders List...', 'bit-integrations') }) } - -export const handleAuthorize = (confTmp, setConf, setIsAuthorized, setIsLoading, setError, btcbi) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = 'https://www.googleapis.com/auth/drive' - // eslint-disable-next-line no-undef - const apiEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?scope=${scopes}&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}&client_id=${confTmp.clientId}` - const authWindow = window.open(apiEndpoint, 'googleDrive', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const bitsGoogleDrive = localStorage.getItem('__googleDrive') - if (bitsGoogleDrive) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleDrive) - localStorage.removeItem('__googleDrive') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'googleDrive_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/Notion/Notion.jsx b/frontend/src/components/AllIntegrations/Notion/Notion.jsx index f51578d61..0c5b5c326 100644 --- a/frontend/src/components/AllIntegrations/Notion/Notion.jsx +++ b/frontend/src/components/AllIntegrations/Notion/Notion.jsx @@ -1,12 +1,11 @@ /* eslint-disable import/no-named-as-default */ /* eslint-disable import/no-named-as-default-member */ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import { useNavigate } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import StepPage from '../../Utilities/StepPage' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from '../IntegrationHelpers/GoogleIntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import NotionAuthorization from './NotionAuthorization' import { nextPage, saveConfig } from './NotionCommonFunc' @@ -14,13 +13,9 @@ import NotionIntegLayout from './NotionIntegLayout' function Notion({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() - useEffect(() => { - window.opener && setGrantTokenResponse('notion') - }, []) const [step, setStep] = useState(1) const [loading, setLoading] = useState({ - auth: false, list: false, page: false, field: false @@ -48,8 +43,6 @@ function Notion({ formFields, setFlow, flow, allIntegURL }) { setStep={setStep} notionConf={notionConf} setNotionConf={setNotionConf} - loading={loading} - setLoading={setLoading} /> {/* --- STEP 2 --- */} diff --git a/frontend/src/components/AllIntegrations/Notion/NotionAuthorization.jsx b/frontend/src/components/AllIntegrations/Notion/NotionAuthorization.jsx index e6220e188..8af55401e 100644 --- a/frontend/src/components/AllIntegrations/Notion/NotionAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Notion/NotionAuthorization.jsx @@ -1,34 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import AuthorizeButton from '../../Utilities/AuthorizeButton' -import ErrorField from '../../Utilities/ErrorField' -import GetInfo from '../../Utilities/GetInfo' -import Input from '../../Utilities/Input' -import Note from '../../Utilities/Note' -import StepPage from '../../Utilities/StepPage' -import { getAllDatabaseLists, handleAuthorize, handleInput } from './NotionCommonFunc' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' import { __ } from '../../../Utils/i18nwrap' +import Authorization from '../../Connections/Authorization' -function NotionAuthorization({ notionConf, setNotionConf, step, setStep, isInfo, loading, setLoading }) { - const btcbi = useRecoilValue($appConfigState) - const [authorized, setAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) -const nextPage = async () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setStep(2) - setLoading({ ...loading, page: true }) - const data = await getAllDatabaseLists(notionConf, setNotionConf) - if (data) { - setLoading({ ...loading, page: false }) - } - } - +function NotionAuthorization({ notionConf, setNotionConf, step, setStep, isInfo }) { const note = `

${__('Step of get Client Id & Client Secret', 'bit-integrations')}

    @@ -50,64 +25,32 @@ const nextPage = async () => {
` return ( - - - -
- {/* Notion Authorization */} - - handleInput(e, notionConf, setNotionConf, error, setError)} - /> - - - handleInput(e, notionConf, setNotionConf, error, setError)} - /> - - handleInput(e, notionConf, setNotionConf, error, setError)} - /> - - - {!isInfo && ( - - handleAuthorize( - notionConf, - setNotionConf, - error, - setError, - setAuthorized, - loading, - setLoading, - btcbi - ) - } - nextPage={nextPage} - auth={authorized} - loading={loading.auth} - /> - )} -
- - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/Notion/NotionCommonFunc.js b/frontend/src/components/AllIntegrations/Notion/NotionCommonFunc.js index 3686a8cf6..97722304b 100644 --- a/frontend/src/components/AllIntegrations/Notion/NotionCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Notion/NotionCommonFunc.js @@ -20,95 +20,14 @@ export const handleInput = (e, conf, setConf, error, setError) => { setConf(newConf) } -export const handleAuthorize = ( - conf, - setConf, - error, - setError, - setAuthorized, - loading, - setLoading, - btcbi -) => { - if (!conf.clientId || !conf.clientSecret) { - setError({ - clientId: !conf.clientId ? __("Client Id can't be empty") : '', - clientSecret: !conf.clientSecret ? __("Client Secret can't be empty") : '' - }) - return - } - setError({}) - setLoading({ ...loading, auth: true }) - const apiEndpoint = `https://api.notion.com/v1/oauth/authorize?client_id=${ - conf.clientId - }&response_type=code&owner=user&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}`)}/redirect` - const authWindow = window.open(apiEndpoint, 'Notion', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const notionStoreValue = localStorage.getItem('__notion') - if (notionStoreValue) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(notionStoreValue) - localStorage.removeItem('__notion') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setLoading({ ...loading, auth: false }) - } else { - tokenHelper(grantTokenResponse, conf, setConf, setAuthorized, loading, setLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, conf, setConf, setAuthorized, loading, setLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = conf.clientId - tokenRequestParams.clientSecret = conf.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - bitsFetch(tokenRequestParams, 'notion_authorization').then(result => { - if (result && result.success) { - const newConf = { ...conf } - newConf.tokenDetails = result.data - setConf(newConf) - setAuthorized(true) - toast.success(__('Authorized Successfully')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:')}${result.data.data || result.data}. ${__( - 'please try again' - )}` - ) - } else { - toast.error(__('Authorization failed. please try again')) - } - setLoading({ ...loading, auth: false }) - }) -} +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { accessToken: conf?.tokenDetails?.access_token } export const getAllDatabaseLists = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, list: true }) - const requestParams = { accessToken: conf.tokenDetails.access_token } + const requestParams = buildAuthRequestParams(conf) const result = await bitsFetch(requestParams, 'notion_database_lists') if (result.success && result.data.results) { const data = result?.data.results @@ -139,7 +58,7 @@ export const getAllDatabaseLists = async (conf, setConf, loading, setLoading) => export const getFieldsProperties = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, field: true }) const requestParams = { - accessToken: conf.tokenDetails.access_token, + ...buildAuthRequestParams(conf), databaseId: conf.databaseId } const result = await bitsFetch(requestParams, 'notion_database_properties') diff --git a/frontend/src/components/AllIntegrations/Notion/NotionIntegLayout.jsx b/frontend/src/components/AllIntegrations/Notion/NotionIntegLayout.jsx index c2864fdf2..cdc38861a 100644 --- a/frontend/src/components/AllIntegrations/Notion/NotionIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Notion/NotionIntegLayout.jsx @@ -45,7 +45,7 @@ function NotionIntegLayout({ notionConf, setNotionConf, formFields, loading, set return (
- {!loading.page && notionConf?.default?.databaseLists && ( + {!loading.page && (
{__('Database List:', 'bit-integrations')} - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('pCloud API apps', 'bit-integrations')} - - - -
- {__('PCloud Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('PCloud Client Secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - {!isInfo && ( - <> - -
- - - )} -
+ ) } diff --git a/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js b/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js index 7777ba590..0372feb99 100644 --- a/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js +++ b/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js @@ -25,8 +25,13 @@ export const checkMappedFields = pCloudConf => { return true } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { tokenDetails: conf.tokenDetails } + export const getAllPCloudFolders = (pCloudConf, setPCloudConf, type) => { - const queryParams = { tokenDetails: pCloudConf.tokenDetails } + const queryParams = buildAuthRequestParams(pCloudConf) const loadPostTypes = bitsFetch(queryParams, 'pCloud_get_all_folders').then(result => { if (result && result.success) { const newConf = { ...pCloudConf } @@ -48,88 +53,3 @@ export const getAllPCloudFolders = (pCloudConf, setPCloudConf, type) => { loading: __('Loading PCloud Folders List...', 'bit-integrations') }) } - -export const handleAuthorization = ( - confTmp, - setConf, - setIsAuthorized, - setIsLoading, - setError, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - // eslint-disable-next-line no-undef - const apiEndpoint = `https://my.pcloud.com/oauth2/authorize?client_id=${ - confTmp.clientId - }&response_type=code&redirect_uri=${btcbi.api}/redirect&state=${encodeURIComponent( - window.location.href - )}/redirect` - const authWindow = window.open(apiEndpoint, 'pCloud', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const bitsPCloud = localStorage.getItem('__pCloud') - if (bitsPCloud) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsPCloud) - localStorage.removeItem('__pCloud') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'pCloud_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ((result && result.data) || (!result.success && typeof result.data === 'string')) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${result.data}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/PCloud/PCloudIntegLayout.jsx b/frontend/src/components/AllIntegrations/PCloud/PCloudIntegLayout.jsx index c32e6aefb..889c98e41 100644 --- a/frontend/src/components/AllIntegrations/PCloud/PCloudIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/PCloud/PCloudIntegLayout.jsx @@ -5,7 +5,7 @@ import PCloudFieldMap from './PCloudFieldMap' import PCloudActions from './PCloudActions' import { getAllPCloudFolders } from './PCloudCommonFunc' -export default function PCloudIntegLayout({ flowID, formFields, pCloudConf, setPCloudConf }) { +export default function PCloudIntegLayout({ formFields, pCloudConf, setPCloudConf }) { return ( <>
diff --git a/frontend/src/components/AllIntegrations/Zoom/Zoom.jsx b/frontend/src/components/AllIntegrations/Zoom/Zoom.jsx index 3f0465b7f..4aa6170ef 100644 --- a/frontend/src/components/AllIntegrations/Zoom/Zoom.jsx +++ b/frontend/src/components/AllIntegrations/Zoom/Zoom.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' @@ -8,7 +8,6 @@ import Steps from '../../Utilities/Steps' import ZoomAuthorization from './ZoomAuthorization' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import { handleInput, checkMappedFields } from './ZoomCommonFunc' -import { setGrantTokenResponse } from './ZoomCommonFunc' import ZoomIntegLayout from './ZoomIntegLayout' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' @@ -36,10 +35,6 @@ function Zoom({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('zoom') - }, []) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -62,14 +57,10 @@ function Zoom({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} {/* STEP 2 */}
{ - const newConf = { ...zoomConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setZoomConf(newConf) - } - const nextPage = () => { - zoomAllMeeting(formID, zoomConf, setZoomConf, setIsLoading, setSnackbar) - setStep(2) - } - - const ZoomInstructions = `

${__('Pro or higher plan only .', 'bit-integrations')}

+ const note = `

${__('Pro or higher plan only .', 'bit-integrations')}

${__('Client Id and Client Secret generate with OAuth .', 'bit-integrations')}

${__('Scope:', 'bit-integrations')}

    @@ -57,114 +29,28 @@ const handleInput = e => { ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Get Zoom client id and secret', 'bit-integrations')} - - - -
    - {__('Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('Client secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - {!isInfo && ( - <> - -
    - - - )} - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Zoom/ZoomCommonFunc.js b/frontend/src/components/AllIntegrations/Zoom/ZoomCommonFunc.js index 3ab747f7d..387b86e72 100644 --- a/frontend/src/components/AllIntegrations/Zoom/ZoomCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Zoom/ZoomCommonFunc.js @@ -38,32 +38,22 @@ export const handleInput = ( setZoomConf({ ...newConf }) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails, + accessToken: conf?.tokenDetails?.access_token, + refreshToken: conf?.tokenDetails?.refresh_token } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} export const zoomAllMeeting = (formID, zoomConf, setZoomConf, setIsLoading, setSnackbar) => { setIsLoading(true) const fetchMeetingModulesRequestParams = { formID, - clientId: zoomConf.clientId, - accessToken: zoomConf.tokenDetails.access_token, - clientSecret: zoomConf.clientSecret, - refreshToken: zoomConf.tokenDetails.refresh_token, - tokenDetails: zoomConf.tokenDetails + ...buildAuthRequestParams(zoomConf) } bitsFetch(fetchMeetingModulesRequestParams, 'zoom_fetch_all_meetings') .then(result => { @@ -107,11 +97,7 @@ export const refreshFields = (zoomConf, setZoomConf, setIsLoading, setSnackbar) setIsLoading(true) const fetchMeetingModulesRequestParams = { meetingId: zoomConf.id, - clientId: zoomConf.clientId, - accessToken: zoomConf.tokenDetails.access_token, - clientSecret: zoomConf.clientSecret, - refreshToken: zoomConf.tokenDetails.refresh_token, - tokenDetails: zoomConf.tokenDetails + ...buildAuthRequestParams(zoomConf) } bitsFetch(fetchMeetingModulesRequestParams, 'zoom_fetch_all_fields') .then(result => { @@ -136,118 +122,6 @@ export const refreshFields = (zoomConf, setZoomConf, setIsLoading, setSnackbar) .catch(() => setIsLoading(false)) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const apiEndpoint = `https://zoom.us/oauth/authorize?response_type=code&client_id=${ - confTmp.clientId - }&state=${encodeURIComponent(window.location.href)}/redirect&redirect_uri=${encodeURIComponent( - `${btcbi.api}/redirect` - )}` - const authWindow = window.open(apiEndpoint, 'zoom', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsGoogleSheet = localStorage.getItem('__zoom') - if (bitsGoogleSheet) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleSheet) - localStorage.removeItem('__zoom') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - bitsFetch(tokenRequestParams, 'zoom_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = zoomConf => { const mappedFleld = zoomConf.field_map ? zoomConf.field_map.filter(mapped => !mapped.formField && !mapped.zoomConf) diff --git a/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomCommonFunc.js b/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomCommonFunc.js index 30804a974..61dfdac1c 100644 --- a/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomCommonFunc.js @@ -2,22 +2,16 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import { deepCopy } from '../../../Utils/Helpers' -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails, + accessToken: conf?.tokenDetails?.access_token, + refreshToken: conf?.tokenDetails?.refresh_token } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} export const handleInput = ( e, @@ -49,11 +43,7 @@ export const zoomAllWebinar = ( setIsLoading(true) const fetchWebinarModulesRequestParams = { formID, - clientId: zoomWebinarConf.clientId, - accessToken: zoomWebinarConf.tokenDetails.access_token, - clientSecret: zoomWebinarConf.clientSecret, - refreshToken: zoomWebinarConf.tokenDetails.refresh_token, - tokenDetails: zoomWebinarConf.tokenDetails + ...buildAuthRequestParams(zoomWebinarConf) } bitsFetch(fetchWebinarModulesRequestParams, 'zoom_webinar_fetch_all_webinar') .then(result => { @@ -92,115 +82,6 @@ export const zoomAllWebinar = ( .catch(() => setIsLoading(false)) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const apiEndpoint = `https://zoom.us/oauth/authorize?response_type=code&client_id=${ - confTmp.clientId - }&state=${encodeURIComponent(window.location.href)}/redirect&redirect_uri=${encodeURIComponent( - `${btcbi.api}/redirect` - )}` - const authWindow = window.open(apiEndpoint, 'zoom', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsGoogleSheet = localStorage.getItem('__zoom') - if (bitsGoogleSheet) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleSheet) - localStorage.removeItem('__zoom') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - bitsFetch(tokenRequestParams, 'zoom_webinar_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = zoomWebinarConf => { const mappedFleld = zoomWebinarConf.field_map ? zoomWebinarConf.field_map.filter(mapped => !mapped.formField && !mapped.zoomWebinarConf) diff --git a/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomWebinar.jsx b/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomWebinar.jsx index b0ba98e95..eb667b431 100644 --- a/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomWebinar.jsx +++ b/frontend/src/components/AllIntegrations/ZoomWebinar/ZoomWebinar.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' @@ -8,7 +8,6 @@ import Steps from '../../Utilities/Steps' import ZoomWebinarAuthorization from './ZoomWebinarAuthorization' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import { handleInput, checkMappedFields } from './ZoomCommonFunc' -import { setGrantTokenResponse } from './ZoomCommonFunc' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import ZoomWebinarIntegLayout from './ZoomWebinarIntegLayout' @@ -46,10 +45,6 @@ function ZoomWebinar({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('zoom') - }, []) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -72,14 +67,10 @@ function ZoomWebinar({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} {/* STEP 2 */}
    { - const newConf = { ...zoomWebinarConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setZoomWebinarConf(newConf) - } - const nextPage = () => { - zoomAllWebinar(formID, zoomWebinarConf, setZoomWebinarConf, setIsLoading, setSnackbar) - setStep(2) - } - - const ZoomInstructions = `

    ${__('Pro or higher plan only .', 'bit-integrations')}

    + const note = `

    ${__('Pro or higher plan only .', 'bit-integrations')}

    ${__('Client Id and Client Secret generate with OAuth .', 'bit-integrations')}

    ${__('Scope:', 'bit-integrations')}

      @@ -56,114 +28,28 @@ const handleInput = e => {
    ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Get Zoom client id and secret', 'bit-integrations')} - - - -
    - {__('Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('Client secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - {!isInfo && ( - <> - -
    - - - )} - -
    + ) } From 98989a357b3f6eaa373089a1ba64cb3f27fb6198 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Wed, 13 May 2026 11:42:10 +0600 Subject: [PATCH 40/58] refactor: integration components to use a unified Authorization component for Dropbox, Google Contacts, Google Sheets, and OneDrive; streamline authentication handling and improve code maintainability. --- backend/Actions/Dropbox/DropboxController.php | 91 ++++--- backend/Actions/Dropbox/Routes.php | 1 - .../GoogleContactsController.php | 85 ++++++- backend/Actions/GoogleContacts/Routes.php | 5 - .../GoogleSheet/GoogleSheetController.php | 74 ++++-- backend/Actions/GoogleSheet/Routes.php | 1 - .../Actions/OneDrive/OneDriveController.php | 117 ++++++--- backend/Actions/OneDrive/Routes.php | 1 - .../AllIntegrations/Dropbox/Dropbox.jsx | 4 - .../Dropbox/DropboxAuthorization.jsx | 186 +++----------- .../Dropbox/DropboxCommonFunc.js | 55 +--- .../GoogleContacts/GoogleContacts.jsx | 7 +- .../GoogleContactsAuthorization.jsx | 179 +++---------- .../GoogleContactsCommonFunc.js | 82 ------ .../GoogleSheet/GoogleSheet.jsx | 2 +- .../GoogleSheet/GoogleSheetAuthorization.jsx | 240 +++--------------- .../GoogleSheet/GoogleSheetCommonFunc.js | 127 +-------- .../AllIntegrations/OneDrive/OneDrive.jsx | 12 +- .../OneDrive/OneDriveAuthorization.jsx | 177 +++---------- .../OneDrive/OneDriveCommonFunc.js | 129 +--------- 20 files changed, 460 insertions(+), 1115 deletions(-) diff --git a/backend/Actions/Dropbox/DropboxController.php b/backend/Actions/Dropbox/DropboxController.php index 197b33f56..c953d7d27 100644 --- a/backend/Actions/Dropbox/DropboxController.php +++ b/backend/Actions/Dropbox/DropboxController.php @@ -3,6 +3,7 @@ namespace BitApps\Integrations\Actions\Dropbox; use BitApps\Integrations\Actions\Dropbox\RecordApiHelper as DropboxRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -10,6 +11,16 @@ class DropboxController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'dropbox', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + protected static $apiBaseUri = 'https://api.dropboxapi.com'; private $integrationID; @@ -19,41 +30,31 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function checkAuthorization($tokenRequestParams) + public static function getAllFolders($queryParams) { - if (empty($tokenRequestParams->accessCode) || empty($tokenRequestParams->clientId) || empty($tokenRequestParams->clientSecret)) { + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + + if (empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $body = [ - 'code' => $tokenRequestParams->accessCode, - 'grant_type' => 'authorization_code', - 'client_id' => $tokenRequestParams->clientId, - 'client_secret' => $tokenRequestParams->clientSecret, - ]; - - $apiEndpoint = self::$apiBaseUri . '/oauth2/token'; - $apiResponse = HttpHelper::post($apiEndpoint, $body); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); + $oldToken = $tokenDetails->access_token; + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllFolders($queryParams) - { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); } - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); - if ($token->access_token !== $queryParams->tokenDetails->access_token) { - self::saveRefreshedToken($queryParams->flowID, $token); + if (!$isConnectionAuth && !empty($queryParams->flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($queryParams->flowID, $tokenDetails); } - $folders = self::getDropboxFoldersList($token->access_token); + $folders = self::getDropboxFoldersList($tokenDetails->access_token); $data = []; if ($folders->entries) { foreach ($folders->entries as $folder) { @@ -68,7 +69,7 @@ public static function getAllFolders($queryParams) } $response['dropboxFoldersList'] = $data; - $response['tokenDetails'] = $token; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response, 200); } @@ -108,8 +109,22 @@ public function execute($integrationData, $fieldValues) $integrationDetails = $integrationData->flow_details; $actions = $integrationDetails->actions; $fieldMap = $integrationDetails->field_map; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $integrationDetails->tokenDetails->access_token) { + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + $oldToken = $tokenDetails->access_token ?? ''; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $integrationDetails->clientId ?? '', $integrationDetails->clientSecret ?? ''); + } + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'dropbox', 'type_name' => 'file_upload']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'Dropbox')); + + return false; + } + + if (!$isConnectionAuth && $tokenDetails->access_token !== $oldToken) { self::saveRefreshedToken($this->integrationID, $tokenDetails); } @@ -129,7 +144,9 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if (($token->generates_on + $token->expires_in - 30) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if (($generatedOn + $token->expires_in - 30) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; @@ -138,6 +155,8 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) $token->access_token = $refreshToken->access_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; + $token->refresh_token = $refreshToken->refresh_token; } return $token; @@ -159,6 +178,20 @@ private static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/backend/Actions/Dropbox/Routes.php b/backend/Actions/Dropbox/Routes.php index ce087f493..cacfd10ec 100644 --- a/backend/Actions/Dropbox/Routes.php +++ b/backend/Actions/Dropbox/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\Dropbox\DropboxController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('dropbox_authorization', [DropboxController::class, 'checkAuthorization']); Route::post('dropbox_get_all_folders', [DropboxController::class, 'getAllFolders']); diff --git a/backend/Actions/GoogleContacts/GoogleContactsController.php b/backend/Actions/GoogleContacts/GoogleContactsController.php index 081ad4eb0..077485cf8 100644 --- a/backend/Actions/GoogleContacts/GoogleContactsController.php +++ b/backend/Actions/GoogleContacts/GoogleContactsController.php @@ -3,6 +3,7 @@ namespace BitApps\Integrations\Actions\GoogleContacts; use BitApps\Integrations\Actions\GoogleContacts\RecordApiHelper as GoogleContactsRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -10,6 +11,16 @@ class GoogleContactsController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'googlecontacts', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -44,11 +55,31 @@ public static function authorization($requestParams) public static function getAllCalendarLists($queryParams) { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + $flowID = $queryParams->flowID ?? null; + $isConnectionAuth = !empty($queryParams->connection_id); + + if (empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); + $oldToken = $tokenDetails->access_token; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); + } + + if (!empty($flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($flowID, $tokenDetails); + } + } + + $token = $tokenDetails; $lists = self::getGoogleCalendarList($token->access_token); $data = []; @@ -80,9 +111,31 @@ public function execute($integrationData, $fieldValues) $actions = $integrationDetails->actions; $fieldMap = $integrationDetails->field_map; $mainAction = $integrationDetails->mainAction; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); - if ($tokenDetails->access_token !== $integrationDetails->tokenDetails->access_token) { - $this->saveRefreshedToken($this->integrationID, $tokenDetails); + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + + if (empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'record', 'type_name' => 'insert']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleContact')); + + return false; + } + + $oldToken = $tokenDetails->access_token; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'record', 'type_name' => 'insert']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'GoogleContact')); + + return false; + } + + if ($tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($this->integrationID, $tokenDetails); + } } if (empty($fieldMap)) { @@ -122,7 +175,9 @@ protected static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; @@ -131,6 +186,8 @@ protected static function tokenExpiryCheck($token, $clientId, $clientSecret) $token->access_token = $refreshToken->access_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; + $token->refresh_token = $refreshToken->refresh_token; } return $token; @@ -152,11 +209,25 @@ protected static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + protected static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } - protected function saveRefreshedToken($integrationID, $tokenDetails) + protected static function saveRefreshedToken($integrationID, $tokenDetails) { if (empty($integrationID)) { return; diff --git a/backend/Actions/GoogleContacts/Routes.php b/backend/Actions/GoogleContacts/Routes.php index f18e6089b..5c539fc24 100644 --- a/backend/Actions/GoogleContacts/Routes.php +++ b/backend/Actions/GoogleContacts/Routes.php @@ -3,8 +3,3 @@ if (!defined('ABSPATH')) { exit; } - -use BitApps\Integrations\Actions\GoogleContacts\GoogleContactsController; -use BitApps\Integrations\Core\Util\Route; - -Route::no_sanitize()->post('googleContacts_authorization', [GoogleContactsController::class, 'authorization']); diff --git a/backend/Actions/GoogleSheet/GoogleSheetController.php b/backend/Actions/GoogleSheet/GoogleSheetController.php index cd20c51f5..81332ca68 100644 --- a/backend/Actions/GoogleSheet/GoogleSheetController.php +++ b/backend/Actions/GoogleSheet/GoogleSheetController.php @@ -9,6 +9,7 @@ if (! defined('ABSPATH')) { exit; } +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; @@ -18,6 +19,16 @@ */ class GoogleSheetController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'googlesheet', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -32,7 +43,6 @@ public function __construct($integrationID) */ public static function registerAjax() { - add_action('wp_ajax_gsheet_generate_token', [__CLASS__, 'generateTokens']); add_action('wp_ajax_gsheet_refresh_spreadsheets', [__CLASS__, 'refreshSpreadsheetsAjaxHelper']); add_action('wp_ajax_gsheet_refresh_worksheets', [__CLASS__, 'refreshWorksheetsAjaxHelper']); add_action('wp_ajax_gsheet_refresh_worksheet_headers', [__CLASS__, 'refreshWorksheetHeadersAjaxHelper']); @@ -93,8 +103,10 @@ public static function generateTokens($requestsParams) */ public static function refreshSpreadsheetsAjaxHelper($queryParams) { - if (empty($queryParams->tokenDetails) - ) { + $queryParams->tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + + if (empty($queryParams->tokenDetails->access_token)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -105,10 +117,13 @@ public static function refreshSpreadsheetsAjaxHelper($queryParams) } $spreadSheets = "https://www.googleapis.com/drive/v3/files?q=mimeType%20%3D%20'application%2Fvnd.google-apps.spreadsheet'"; $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); - $authorizationHeader['Authorization'] = 'Bearer ' . $response['tokenDetails']->access_token; - } else { + if (!empty($response['tokenDetails']->access_token)) { + $authorizationHeader['Authorization'] = 'Bearer ' . $response['tokenDetails']->access_token; + } + } + if (empty($authorizationHeader['Authorization'])) { $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; } @@ -130,8 +145,8 @@ public static function refreshSpreadsheetsAjaxHelper($queryParams) 400 ); } - if (!empty($response['tokenDetails']) && !empty($queryParams->id)) { - GoogleSheetController::saveRefreshedToken($queryParams->id, $response['tokenDetails'], $response['$spreadsheets']); + if (!$isConnectionAuth && !empty($response['tokenDetails']) && !empty($queryParams->id)) { + GoogleSheetController::saveRefreshedToken($queryParams->id, $response['tokenDetails'], $response); } wp_send_json_success($response, 200); } @@ -145,7 +160,10 @@ public static function refreshSpreadsheetsAjaxHelper($queryParams) */ public static function refreshWorksheetsAjaxHelper($queryParams) { - if (empty($queryParams->tokenDetails) + $queryParams->tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + + if (empty($queryParams->tokenDetails->access_token) || empty($queryParams->spreadsheetId) ) { wp_send_json_error( @@ -157,8 +175,11 @@ public static function refreshWorksheetsAjaxHelper($queryParams) ); } $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); + if (!empty($response['tokenDetails']->access_token)) { + $queryParams->tokenDetails = $response['tokenDetails']; + } } $worksheetsMetaApiEndpoint = "https://sheets.googleapis.com/v4/spreadsheets/{$queryParams->spreadsheetId}?&fields=sheets.properties"; @@ -176,7 +197,7 @@ public static function refreshWorksheetsAjaxHelper($queryParams) 400 ); } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->id)) { + if (!$isConnectionAuth && !empty($response['tokenDetails']) && !empty($queryParams->id)) { $response['queryWorkbook'] = $queryParams->workbook; GoogleSheetController::saveRefreshedToken($queryParams->id, $response['tokenDetails'], $response); } @@ -192,8 +213,11 @@ public static function refreshWorksheetsAjaxHelper($queryParams) */ public static function refreshWorksheetHeadersAjaxHelper($queryParams) { + $queryParams->tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + if (empty($queryParams->worksheetName) - || empty($queryParams->tokenDetails) + || empty($queryParams->tokenDetails->access_token) || empty($queryParams->header) || empty($queryParams->headerRow) ) { @@ -206,8 +230,11 @@ public static function refreshWorksheetHeadersAjaxHelper($queryParams) ); } $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { + if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); + if (!empty($response['tokenDetails']->access_token)) { + $queryParams->tokenDetails = $response['tokenDetails']; + } } $headerRow = $queryParams->headerRow; if ($queryParams->header === 'ROWS') { @@ -237,7 +264,7 @@ public static function refreshWorksheetHeadersAjaxHelper($queryParams) $response['worksheet_headers'][] = "{$header}_{$key}"; } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->id)) { + if (!$isConnectionAuth && !empty($response['tokenDetails']) && !empty($queryParams->id)) { $response['queryModule'] = $queryParams->module; GoogleSheetController::saveRefreshedToken($queryParams->id, $response['tokenDetails'], $response); } @@ -251,7 +278,8 @@ public function execute($integrationData, $fieldValues) // wp_send_json_success($integrationDetails); - $tokenDetails = $integrationDetails->tokenDetails; + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + $isConnectionAuth = !empty($integrationDetails->connection_id); $spreadsheetId = $integrationDetails->spreadsheetId; $worksheetName = $integrationDetails->worksheetName; $headerRow = $integrationDetails->headerRow; @@ -269,7 +297,7 @@ public function execute($integrationData, $fieldValues) return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Google sheet')); } - if ((\intval($tokenDetails->generates_on) + (55 * 60)) < time()) { + if (!$isConnectionAuth && (\intval($tokenDetails->generates_on) + (55 * 60)) < time()) { $requiredParams['clientId'] = $integrationDetails->clientId; $requiredParams['clientSecret'] = $integrationDetails->clientSecret; $requiredParams['tokenDetails'] = $tokenDetails; @@ -331,6 +359,7 @@ protected static function refreshAccessToken($apiData) return false; } $tokenDetails->generates_on = time(); + $tokenDetails->generated_at = $tokenDetails->generates_on; $tokenDetails->access_token = $apiResponse->access_token; return $tokenDetails; @@ -372,4 +401,17 @@ protected static function saveRefreshedToken($integrationID, $tokenDetails, $oth $flow->update($integrationID, ['flow_details' => wp_json_encode($newDetails)]); } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } + + return $token; + } } diff --git a/backend/Actions/GoogleSheet/Routes.php b/backend/Actions/GoogleSheet/Routes.php index df662a004..ce9ddb943 100644 --- a/backend/Actions/GoogleSheet/Routes.php +++ b/backend/Actions/GoogleSheet/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\GoogleSheet\GoogleSheetController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('gsheet_generate_token', [GoogleSheetController::class, 'generateTokens']); Route::post('gsheet_refresh_spreadsheets', [GoogleSheetController::class, 'refreshSpreadsheetsAjaxHelper']); Route::post('gsheet_refresh_worksheets', [GoogleSheetController::class, 'refreshWorksheetsAjaxHelper']); Route::post('gsheet_refresh_worksheet_headers', [GoogleSheetController::class, 'refreshWorksheetHeadersAjaxHelper']); diff --git a/backend/Actions/OneDrive/OneDriveController.php b/backend/Actions/OneDrive/OneDriveController.php index 813a21648..ec1d8a4a7 100644 --- a/backend/Actions/OneDrive/OneDriveController.php +++ b/backend/Actions/OneDrive/OneDriveController.php @@ -3,12 +3,23 @@ namespace BitApps\Integrations\Actions\OneDrive; use BitApps\Integrations\Actions\OneDrive\RecordApiHelper as OneDriveRecordApiHelper; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; class OneDriveController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'onedrive', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -16,42 +27,31 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) + public static function getAllFolders($queryParams) { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code) || empty($requestParams->redirectURI)) { + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + + if (empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $body = [ - 'client_id' => $requestParams->clientId, - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'client_secret' => $requestParams->clientSecret, - 'grant_type' => 'authorization_code', - 'code' => urldecode($requestParams->code) - ]; - - $apiEndpoint = 'https://login.live.com/oauth20_token.srf'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - if (is_wp_error($apiResponse) || !empty($apiResponse->error) || HttpHelper::$responseCode !== 200) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); + $oldToken = $tokenDetails->access_token; + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllFolders($queryParams) - { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); } - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); - if ($token->access_token !== $queryParams->tokenDetails->access_token) { - self::saveRefreshedToken($queryParams->flowID, $token); + if (!$isConnectionAuth && !empty($queryParams->flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($queryParams->flowID, $tokenDetails); } - $folders = self::getOneDriveFoldersList($token->access_token); + $folders = self::getOneDriveFoldersList($tokenDetails->access_token); $foldersOnly = $folders->value; $data = []; @@ -63,7 +63,7 @@ public static function getAllFolders($queryParams) } } $response['oneDriveFoldersList'] = $data; - $response['tokenDetails'] = $token; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response, 200); } @@ -85,20 +85,33 @@ public static function getOneDriveFoldersList($token) public static function singleOneDriveFolderList($queryParams) { - if (empty($queryParams->tokenDetails) || empty($queryParams->clientId) || empty($queryParams->clientSecret)) { + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $clientId = $queryParams->clientId ?? ''; + $clientSecret = $queryParams->clientSecret ?? ''; + + if (empty($tokenDetails->access_token)) { wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } $ids = explode('!', $queryParams->folder); - $token = self::tokenExpiryCheck($queryParams->tokenDetails, $queryParams->clientId, $queryParams->clientSecret); - if ($token->access_token !== $queryParams->tokenDetails->access_token) { - self::saveRefreshedToken($queryParams->flowID, $token); + $oldToken = $tokenDetails->access_token; + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $clientId, $clientSecret); + } + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization failed', 'bit-integrations'), 400); + } + + if (!$isConnectionAuth && !empty($queryParams->flowID) && $tokenDetails->access_token !== $oldToken) { + self::saveRefreshedToken($queryParams->flowID, $tokenDetails); } $headers = [ 'Accept' => 'application/json', 'Content-Type' => 'application/json;', - 'Authorization' => 'bearer ' . $queryParams->tokenDetails->access_token, + 'Authorization' => 'bearer ' . $tokenDetails->access_token, ]; $apiEndpoint = 'https://api.onedrive.com/v1.0/drives/' . $ids[0] . '/items/' . $queryParams->folder . '/children'; $apiResponse = HttpHelper::get($apiEndpoint, [], $headers); @@ -112,7 +125,7 @@ public static function singleOneDriveFolderList($queryParams) } } $response['folders'] = $data; - $response['tokenDetails'] = $token; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response, 200); } @@ -129,11 +142,25 @@ public function execute($integrationData, $fieldValues) $actions = $integrationDetails->actions; $folderId = $integrationDetails->folder; // $fieldMap = $integrationDetails->field_map; - $tokenDetails = self::tokenExpiryCheck($integrationDetails->tokenDetails, $integrationDetails->clientId, $integrationDetails->clientSecret); + $isConnectionAuth = !empty($integrationDetails->connection_id); + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + $oldToken = $tokenDetails->access_token ?? ''; + + if (!$isConnectionAuth) { + $tokenDetails = self::tokenExpiryCheck($tokenDetails, $integrationDetails->clientId ?? '', $integrationDetails->clientSecret ?? ''); + } + + if (empty($tokenDetails) || empty($tokenDetails->access_token)) { + // translators: %s: Service name + LogHandler::save($this->integrationID, wp_json_encode(['type' => 'oneDrive', 'type_name' => 'file_upload']), 'error', wp_sprintf(__('Not Authorization By %s', 'bit-integrations'), 'OneDrive')); + + return false; + } // folderMap need check $parentId = $integrationData->flow_details->folderMap[1]; $fieldMap = null; - if ($tokenDetails->access_token !== $integrationDetails->tokenDetails->access_token) { + + if (!$isConnectionAuth && $tokenDetails->access_token !== $oldToken) { self::saveRefreshedToken($this->integrationID, $tokenDetails); } @@ -148,7 +175,9 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) ? (int) $token->generates_on : (int) ($token->generated_at ?? 0); + + if ($generatedOn > 0 && ($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; @@ -156,6 +185,8 @@ private static function tokenExpiryCheck($token, $clientId, $clientSecret) $token->access_token = $refreshToken->access_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; + $token->refresh_token = $refreshToken->refresh_token; } return $token; @@ -177,6 +208,20 @@ private static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; + + return $token; + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } return $token; } diff --git a/backend/Actions/OneDrive/Routes.php b/backend/Actions/OneDrive/Routes.php index 030066433..046e17d31 100644 --- a/backend/Actions/OneDrive/Routes.php +++ b/backend/Actions/OneDrive/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\OneDrive\OneDriveController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('oneDrive_authorization', [OneDriveController::class, 'authorization']); Route::post('oneDrive_get_all_folders', [OneDriveController::class, 'getAllFolders']); Route::post('oneDrive_get_single_folder', [OneDriveController::class, 'singleOneDriveFolderList']); diff --git a/frontend/src/components/AllIntegrations/Dropbox/Dropbox.jsx b/frontend/src/components/AllIntegrations/Dropbox/Dropbox.jsx index 93bb9cb31..0496ddda4 100644 --- a/frontend/src/components/AllIntegrations/Dropbox/Dropbox.jsx +++ b/frontend/src/components/AllIntegrations/Dropbox/Dropbox.jsx @@ -22,7 +22,6 @@ function Dropbox({ formFields, setFlow, flow, allIntegURL }) { type: 'Dropbox', clientId: '', clientSecret: '', - accessCode: '', field_map: [{ formField: '', dropboxFormField: '' }], foldersList: [], actions: {} @@ -49,13 +48,10 @@ function Dropbox({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/Dropbox/DropboxAuthorization.jsx b/frontend/src/components/AllIntegrations/Dropbox/DropboxAuthorization.jsx index 4744709a8..e8e1988fa 100644 --- a/frontend/src/components/AllIntegrations/Dropbox/DropboxAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Dropbox/DropboxAuthorization.jsx @@ -1,165 +1,49 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { getAllDropboxFolders, handleAuthorize } from './DropboxCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function DropboxAuthorization({ - flowID, dropboxConf, setDropboxConf, step, setStep, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - getAllDropboxFolders(flowID, dropboxConf, setDropboxConf, setIsLoading) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...dropboxConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setDropboxConf(newConf) - } - - const getAccessCode = () => { - if (!dropboxConf.clientId || !dropboxConf.clientSecret) { - setError({ - clientId: !dropboxConf.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !dropboxConf.clientSecret - ? __("Client Secret can't be empty", 'bit-integrations') - : '' - }) - return - } - window.open( - `https://www.dropbox.com/oauth2/authorize?client_id=${dropboxConf.clientId}&token_access_type=offline&response_type=code`, - '_blank' - ) - } + const note = ` +

    ${__('Dropbox OAuth setup', 'bit-integrations')}

    +
      +
    • ${__('Create app in Dropbox API Console.', 'bit-integrations')}
    • +
    • ${__('Add redirect URI from integration settings and keep offline token access enabled.', 'bit-integrations')}
    • +
    + ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('Dropbox API Console', 'bit-integrations')} - - - -
    - {__('Dropbox Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('Dropbox Client Secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - - - {__('To Get Access Code, Please Visit', 'bit-integrations')} -   - - - -
    - {__('Dropbox Access Code:', 'bit-integrations')} -
    - -
    {error.accessCode}
    - - {!isInfo && ( - <> - -
    - - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/Dropbox/DropboxCommonFunc.js b/frontend/src/components/AllIntegrations/Dropbox/DropboxCommonFunc.js index c3a6a9117..bbb85e66b 100644 --- a/frontend/src/components/AllIntegrations/Dropbox/DropboxCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Dropbox/DropboxCommonFunc.js @@ -14,12 +14,19 @@ export const handleInput = (e, dropboxConf, setDropboxConf) => { setDropboxConf({ ...newConf }) } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const getAllDropboxFolders = (flowID, dropboxConf, setDropboxConf) => { const queryParams = { flowID: flowID ?? null, - clientId: dropboxConf.clientId, - clientSecret: dropboxConf.clientSecret, - tokenDetails: dropboxConf.tokenDetails + ...buildAuthRequestParams(dropboxConf) } const loadPostTypes = bitsFetch(queryParams, 'dropbox_get_all_folders').then(result => { if (result && result.success) { @@ -41,45 +48,3 @@ export const getAllDropboxFolders = (flowID, dropboxConf, setDropboxConf) => { loading: __('Loading Dropbox Folders List...', 'bit-integrations') }) } - -export const handleAuthorize = (confTmp, setConf, setIsAuthorized, setIsLoading, setError) => { - if (!confTmp.accessCode || !confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '', - accessCode: !confTmp.accessCode ? __("Access Code can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const tokenRequestParams = { - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - accessCode: confTmp.accessCode - } - - bitsFetch(tokenRequestParams, 'dropbox_authorization') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')} ${ - result.data.data || result.data - } ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} diff --git a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContacts.jsx b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContacts.jsx index c9eaf6278..40778ab8f 100644 --- a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContacts.jsx +++ b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContacts.jsx @@ -1,11 +1,10 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from '../IntegrationHelpers/GoogleIntegrationHelpers' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import GoogleContactsAuthorization from './GoogleContactsAuthorization' @@ -50,10 +49,6 @@ function GoogleContacts({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('googleContacts') - }, []) - const saveConfig = () => { saveActionConf({ flow, diff --git a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsAuthorization.jsx b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsAuthorization.jsx index 7ca2f6bde..ed4818e4a 100644 --- a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsAuthorization.jsx @@ -1,155 +1,52 @@ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from './GoogleContactsCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function GoogleContactsAuthorization({ - flowID, googleContactsConf, setGoogleContactsConf, step, setStep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - // getAllGoogleCalendarLists(flowID, googleContactsConf, setGoogleContactsConf, setIsLoading) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...googleContactsConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setGoogleContactsConf(newConf) - } + const note = ` +

    ${__('Google Contacts OAuth setup', 'bit-integrations')}

    +
      +
    • ${__('Create OAuth client in Google API Console.', 'bit-integrations')}
    • +
    • ${__('Set homepage and redirect URI exactly from integration settings.', 'bit-integrations')}
    • +
    • ${__('Enable Google People API and authorize with required scope.', 'bit-integrations')}
    • +
    + ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('Google API Console', 'bit-integrations')} - - - -
    - {__('GoogleContacts Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('GoogleContacts Client Secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - - {!isInfo && ( - <> - -
    - - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsCommonFunc.js b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsCommonFunc.js index 589042f3a..4d6e66ea0 100644 --- a/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/GoogleContacts/GoogleContactsCommonFunc.js @@ -1,7 +1,4 @@ /* eslint-disable no-else-return */ -import toast from 'react-hot-toast' -import { __ } from '../../../Utils/i18nwrap' -import bitsFetch from '../../../Utils/bitsFetch' export const handleInput = (e, googleContactsConf, setGoogleContactsConf) => { const newConf = { ...googleContactsConf } @@ -22,85 +19,6 @@ export const checkMappedFields = fieldsMapped => { return true } -export const handleAuthorize = (confTmp, setConf, setIsAuthorized, setIsLoading, setError, btcbi) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = 'https://www.googleapis.com/auth/contacts' - // eslint-disable-next-line no-undef - const apiEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?scope=${scopes}&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}&client_id=${confTmp.clientId}` - const authWindow = window.open(apiEndpoint, 'googleContacts', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const bitsGoogleCalendar = localStorage.getItem('__googleContacts') - if (bitsGoogleCalendar) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleCalendar) - localStorage.removeItem('__googleContacts') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'googleContacts_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} - export const generateMappedField = googleContactsConf => { const requiredFlds = googleContactsConf?.default.filter(fld => fld.required === true) return requiredFlds.length > 0 diff --git a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheet.jsx b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheet.jsx index 65cfee903..02c8005fc 100644 --- a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheet.jsx +++ b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheet.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' diff --git a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx index 8559ef4db..ca815da0d 100644 --- a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetAuthorization.jsx @@ -1,216 +1,52 @@ -import { useEffect, useState } from 'react' -import { useRecoilState, useRecoilValue } from 'recoil' -import { $appConfigState, authInfoAtom } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize, refreshSpreadsheets, tokenHelper } from './GoogleSheetCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import SelectAuthorizationType from '../../OneClickRadioComponents/SelectAuthorizationType' -import AuthorizationAccountList from '../../OneClickRadioComponents/AuthorizationAccountList' -import bitsFetch from '../../../Utils/bitsFetch' -import Loader from '../../Loaders/Loader' +import Authorization from '../../Connections/Authorization' export default function GoogleSheetAuthorization({ - formID, sheetConf, setSheetConf, step, setstep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, - isEdit, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const [authData, setAuthData] = useState([]) - const [authInfo, setAuthInfo] = useRecoilState(authInfoAtom) - const [selectedAuthType, setSelectedAuthType] = useState('Custom Authorization') + const note = ` +

    ${__('Google Sheets OAuth setup', 'bit-integrations')}

    +
      +
    • ${__('Create OAuth client in Google API Console.', 'bit-integrations')}
    • +
    • ${__('Set homepage and redirect URI exactly from integration settings.', 'bit-integrations')}
    • +
    • ${__('Enable Google Drive API and authorize with required scope.', 'bit-integrations')}
    • +
    + ` - const processAuth = option => { - handleAuthorize(sheetConf, option, setError, setIsLoading, btcbi) - } - - const getAuthData = () => { - setIsLoading(true) - const queryParams = { - actionName: sheetConf.type - } - - bitsFetch(null, 'auth/get', queryParams, 'GET').then(res => { - if (res.success && res.data.data.length > 0) { - setAuthData(res.data.data) - } - setIsLoading(false) - }) - } - - const handleVerificationCode = async authInfo => { - await tokenHelper( - authInfo, - sheetConf, - setSheetConf, - setIsAuthorized, - selectedAuthType, - authData, - setAuthData, - setIsLoading, - setSnackbar, - btcbi - ) - setAuthInfo(undefined) - } - - useEffect(() => { - if (!authInfo || Object.keys(authInfo).length === 0) return - - handleVerificationCode(authInfo) - }, [authInfo]) - - const handleInput = e => { - const newConf = { ...sheetConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setSheetConf(newConf) - } - - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setstep(2) - refreshSpreadsheets(formID, sheetConf, setSheetConf, setIsLoading, setSnackbar) - } return ( -
    - - - {selectedAuthType === 'Custom Authorization' && ( -
    -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Google API Console', 'bit-integrations')} - - - -
    - {__('Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('Client secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - - -
    -
    - )} - {isLoading && selectedAuthType !== 'Custom Authorization' && ( - - )} - {isAuthorized && selectedAuthType === 'One Click Authorization' && ( - - )} -
    - -
    + ) } diff --git a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js index 59514367e..0e89903b4 100644 --- a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js +++ b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetCommonFunc.js @@ -1,8 +1,6 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import { deepCopy } from '../../../Utils/Helpers' -import { handleAuthData } from '../GlobalIntegrationHelper' -import { create } from 'mutative' export const handleInput = ( e, @@ -70,10 +68,8 @@ export const refreshSpreadsheets = (formID, sheetConf, setSheetConf, setIsLoadin const refreshModulesRequestParams = { formID, id: sheetConf.id, - clientId: sheetConf.clientId, - clientSecret: sheetConf.clientSecret, - tokenDetails: sheetConf.tokenDetails, - ownerEmail: sheetConf.ownerEmail + ownerEmail: sheetConf.ownerEmail, + ...buildAuthRequestParams(sheetConf) } setIsLoading(true) @@ -123,7 +119,7 @@ export const refreshWorksheets = (formID, sheetConf, setSheetConf, setIsLoading, const refreshSpreadsheetsRequestParams = { formID, spreadsheetId, - tokenDetails: sheetConf.tokenDetails + ...buildAuthRequestParams(sheetConf) } bitsFetch(refreshSpreadsheetsRequestParams, 'gsheet_refresh_worksheets') .then(result => { @@ -165,9 +161,7 @@ export const refreshWorksheetHeaders = (formID, sheetConf, setSheetConf, setIsLo worksheetName, header, headerRow, - clientId: sheetConf.clientId, - clientSecret: sheetConf.clientSecret, - tokenDetails: sheetConf.tokenDetails + ...buildAuthRequestParams(sheetConf) } bitsFetch(refreshWorksheetHeadersRequestParams, 'gsheet_refresh_worksheet_headers') @@ -215,98 +209,6 @@ export const refreshWorksheetHeaders = (formID, sheetConf, setSheetConf, setIsLo .catch(() => setIsLoading(false)) } -export const handleAuthorize = (confTmp, selectedAuthType, setError, setIsLoading, btcbi) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - - const clientId = confTmp.clientId - - setIsLoading(true) - - const scopes = 'https://www.googleapis.com/auth/drive' - // eslint-disable-next-line no-undef - const finalRedirectUri = `${btcbi.api}/redirect` - - const { href, hash } = window.location - const stateUrl = hash ? href.replace(hash, '#/auth-response/') : `${href}#/auth-response/` - - const apiEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?scope=${scopes}&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - stateUrl - )}&redirect_uri=${encodeURIComponent(finalRedirectUri)}&client_id=${clientId}` - const authWindow = window.open(apiEndpoint, 'googleSheet', 'width=400,height=609,toolbar=off') - if (selectedAuthType === 'Custom Authorization') { - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - setIsLoading(false) - } - }, 500) - } -} - -export const tokenHelper = async ( - authInfo, - confTmp, - setConf, - setIsAuthorized, - selectedAuthType, - authData, - setAuthData, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!selectedAuthType) { - return - } - const tokenRequestParams = {} - tokenRequestParams.code = authInfo.code || '' - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - setIsLoading(true) - await bitsFetch(tokenRequestParams, 'gsheet_generate_token') - .then(result => result) - .then(async result => { - if (result && result.success) { - setConf(prevConf => - create(prevConf, draftConf => { - draftConf.tokenDetails = result.data - }) - ) - - setIsAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = sheetconf => { const mappedFleld = sheetconf.field_map ? sheetconf.field_map.filter(mapped => !mapped.formField && !mapped.googleSheetField) @@ -317,18 +219,11 @@ export const checkMappedFields = sheetconf => { return true } -async function fetchUserInfo(tokenResponse) { - const accessToken = tokenResponse.access_token - - try { - const userInfoResponse = await fetch('https://www.googleapis.com/drive/v3/about?fields=user', { - headers: { - Authorization: `Bearer ${accessToken}` +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails } - }) - const userInfo = await userInfoResponse.json() - return userInfo - } catch (error) { - console.error('Error fetching user info:', error) - } -} diff --git a/frontend/src/components/AllIntegrations/OneDrive/OneDrive.jsx b/frontend/src/components/AllIntegrations/OneDrive/OneDrive.jsx index 64bf53cb1..ab0a5ff01 100644 --- a/frontend/src/components/AllIntegrations/OneDrive/OneDrive.jsx +++ b/frontend/src/components/AllIntegrations/OneDrive/OneDrive.jsx @@ -1,11 +1,9 @@ -/* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from '../IntegrationHelpers/GoogleIntegrationHelpers' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import OneDriveAuthorization from './OneDriveAuthorization' @@ -31,10 +29,6 @@ function OneDrive({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('oneDrive') - }, []) - const saveConfig = () => { saveActionConf({ flow, @@ -56,14 +50,10 @@ function OneDrive({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */}
    { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - getAllOneDriveFolders(flowID, oneDriveConf, setOneDriveConf, setIsLoading) - setStep(2) - } - - const handleInput = e => { - const newConf = { ...oneDriveConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setOneDriveConf(newConf) - } + const note = ` +

    ${__('OneDrive OAuth setup', 'bit-integrations')}

    +
      +
    • ${__('Create app in Azure Portal and add redirect URI from integration settings.', 'bit-integrations')}
    • +
    • ${__('Use delegated permissions for OneDrive read/write with offline access.', 'bit-integrations')}
    • +
    + ` return ( -
    - - -
    - {__('Integration Name:', 'bit-integrations')} -
    - - -
    - {__('Homepage URL:', 'bit-integrations')} -
    - - -
    - {__('Authorized Redirect URIs:', 'bit-integrations')} -
    - - - - {__('To Get Client Id & Secret, Please Visit', 'bit-integrations')} -   - - {__('Azure Portal', 'bit-integrations')} - - - -
    - {__('OneDrive Client id:', 'bit-integrations')} -
    - -
    {error.clientId}
    - -
    - {__('OneDrive Client Secret:', 'bit-integrations')} -
    - -
    {error.clientSecret}
    - - {!isInfo && ( - <> - -
    - - - )} -
    + ) } diff --git a/frontend/src/components/AllIntegrations/OneDrive/OneDriveCommonFunc.js b/frontend/src/components/AllIntegrations/OneDrive/OneDriveCommonFunc.js index 4e8af869c..276f3eb24 100644 --- a/frontend/src/components/AllIntegrations/OneDrive/OneDriveCommonFunc.js +++ b/frontend/src/components/AllIntegrations/OneDrive/OneDriveCommonFunc.js @@ -50,6 +50,15 @@ const folderChange = (oneDriveConf, formID, setOneDriveConf, setIsLoading, setSn return newConf } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const getAllOneDriveFolders = ( flowID, oneDriveConf, @@ -60,9 +69,7 @@ export const getAllOneDriveFolders = ( setIsLoading(true) const queryParams = { flowID: flowID ?? null, - clientId: oneDriveConf.clientId, - clientSecret: oneDriveConf.clientSecret, - tokenDetails: oneDriveConf.tokenDetails + ...buildAuthRequestParams(oneDriveConf) } const loadPostTypes = bitsFetch(queryParams, 'oneDrive_get_all_folders') .then(result => { @@ -103,9 +110,7 @@ export const getSingleOneDriveFolders = ( const refreshSubFoldersRequestParams = { formID, dataCenter: oneDriveConf.dataCenter, - clientId: oneDriveConf.clientId, - clientSecret: oneDriveConf.clientSecret, - tokenDetails: oneDriveConf.tokenDetails, + ...buildAuthRequestParams(oneDriveConf), team: oneDriveConf.team, folder, teamType: 'teamType' in oneDriveConf ? 'private' : 'team' @@ -141,115 +146,3 @@ export const getSingleOneDriveFolders = ( }) .catch(() => setIsLoading(false)) } - -// export const getSingleOneDriveFolders = (flowID, oneDriveConf, setOneDriveConf) => { -// // const folder = ind ? oneDriveConf.folderMap[ind] : oneDriveConf.folder -// const queryParams = { -// flowID: flowID ?? null, -// clientId: oneDriveConf.clientId, -// clientSecret: oneDriveConf.clientSecret, -// tokenDetails: oneDriveConf.tokenDetails, -// subFolderId: oneDriveConf.id, -// } -// const loadPostTypes = bitsFetch(queryParams, 'oneDrive_get_single_folder') -// .then(result => { -// if (result && result.success) { -// const newConf = { ...oneDriveConf } -// if (result.data.oneDriveFoldersList) { -// newConf.foldersList = result.data.oneDriveFoldersList -// newConf.tokenDetails = result.data.tokenDetails -// } - -// setOneDriveConf(newConf) -// return 'OneDrive single Folders List refreshed successfully' -// } else { -// return 'OneDrive single Folders List refresh failed. please try again' -// } -// }) -// toast.promise(loadPostTypes, { -// success: data => data, -// error: __('Error Occurred', 'bit-integrations'), -// loading: __('Loading OneDrive Folders List...', 'bit-integrations'), -// }) -// } - -export const handleAuthorize = (confTmp, setConf, setIsAuthorized, setIsLoading, setError, btcbi) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = 'onedrive.readwrite offline_access Files.ReadWrite.All' - // eslint-disable-next-line no-undef - const apiEndpoint = `https://login.live.com/oauth20_authorize.srf?client_id=${ - confTmp.clientId - }&scope=${scopes}&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}` - const authWindow = window.open(apiEndpoint, 'oneDrive', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isAuthRedirectLocation = false - const bitsOneDrive = localStorage.getItem('__oneDrive') - if (bitsOneDrive) { - isAuthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsOneDrive) - localStorage.removeItem('__oneDrive') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isAuthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - ) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, 'oneDrive_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} From 544f68605e3954b67ffc3a298d2179c0ce4bc5a7 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Wed, 13 May 2026 12:12:51 +0600 Subject: [PATCH 41/58] refactor: Constant Contact and LionDesk integration components - Removed unused imports and code related to token handling in Constant Contact and LionDesk components. - Simplified the authorization process by integrating a common Authorization component for both Constant Contact and LionDesk. - Updated the handling of API requests to use a unified method for building authentication parameters. - Improved user instructions and error handling in the authorization steps for both integrations. - Cleaned up the codebase by removing deprecated functions and unnecessary state management. --- .../ConstantContactController.php | 324 +++++++++--------- backend/Actions/ConstantContact/Routes.php | 2 - .../Actions/LionDesk/LionDeskController.php | 301 ++++++++++------ backend/Actions/LionDesk/Routes.php | 1 - .../ConstantContact/ConstantContact.jsx | 10 +- .../ConstantContactAuthorization.jsx | 203 ++--------- .../ConstantContactCommonFunc.js | 181 ++-------- .../AllIntegrations/LionDesk/LionDesk.jsx | 11 +- .../LionDesk/LionDeskAuthorization.jsx | 187 ++-------- .../LionDesk/LionDeskCommonFunc.js | 166 ++------- .../LionDesk/LionDeskIntegLayout.jsx | 7 +- 11 files changed, 478 insertions(+), 915 deletions(-) diff --git a/backend/Actions/ConstantContact/ConstantContactController.php b/backend/Actions/ConstantContact/ConstantContactController.php index 2f6856751..e8d280c1d 100644 --- a/backend/Actions/ConstantContact/ConstantContactController.php +++ b/backend/Actions/ConstantContact/ConstantContactController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ConstantContact; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -16,81 +17,44 @@ */ class ConstantContactController { - protected $_defaultHeader; - - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $auth = $requestsParams->clientId . ':' . $requestsParams->clientSecret; - // Base64 encode it - $credentials = base64_encode($auth); - - $authorizationHeader['Accept'] = 'application/json'; - $authorizationHeader['Content-Type'] = 'application/x-www-form-urlencoded'; - $authorizationHeader['Authorization'] = 'Basic ' . $credentials; - - $requestParams = [ - 'code' => $requestsParams->code, - 'redirect_uri' => "{$requestsParams->redirectURI}", - 'grant_type' => 'authorization_code' - ]; - - $apiResponse = HttpHelper::post('https://authz.constantcontact.com/oauth2/default/v1/token', $requestParams, $authorizationHeader); + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'constantcontact', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at']], + ], + ]; - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } + protected $_defaultHeader; public static function refreshList($queryParams) { - if (empty($queryParams->tokenDetails) - || empty($queryParams->clientId) - || empty($queryParams->clientSecret) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; + + if (empty($oldToken)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (1435 * 60)) < time()) { - $refreshedToken = ConstantContactController::_refreshAccessToken($queryParams); + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $refreshedToken = self::_refreshAccessToken((object) [ + 'clientId' => $queryParams->clientId ?? '', + 'clientSecret' => $queryParams->clientSecret ?? '', + 'tokenDetails' => $tokenDetails, + ]); - if ($refreshedToken) { - $response['tokenDetails'] = $refreshedToken; - } else { - wp_send_json_error( - __('Failed to refresh access token', 'bit-integrations'), - 400 - ); + if (!$refreshedToken) { + wp_send_json_error(__('Failed to refresh access token', 'bit-integrations'), 400); } + + $tokenDetails = $refreshedToken; } - $apiEndpoint = 'https://api.cc.email/v3/contact_lists'; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + $apiEndpoint = 'https://api.cc.email/v3/contact_lists'; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); $allList = []; @@ -103,51 +67,51 @@ public static function refreshList($queryParams) ]; } uksort($allList, 'strnatcasecmp'); - - $response['contactList'] = $allList; } else { - wp_send_json_error( - $apiResponse->response->error->message, - 400 - ); + wp_send_json_error($apiResponse->response->error->message ?? __('List fetch failed', 'bit-integrations'), 400); } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->integId)) { - static::_saveRefreshedToken($queryParams->integId, $response['tokenDetails'], $response); + + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '') && !empty($queryParams->integId)) { + self::_saveRefreshedToken($queryParams->integId, $tokenDetails); } - wp_send_json_success($response, 200); + + wp_send_json_success( + [ + 'contactList' => $allList, + 'tokenDetails' => $tokenDetails + ], + 200 + ); } public static function refreshTags($queryParams) { - if (empty($queryParams->tokenDetails) - || empty($queryParams->clientId) - || empty($queryParams->clientSecret) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; + + if (empty($oldToken)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (1435 * 60)) < time()) { - $refreshedToken = ConstantContactController::_refreshAccessToken($queryParams); - if ($refreshedToken) { - $response['tokenDetails'] = $refreshedToken; - } else { - wp_send_json_error( - __('Failed to refresh access token', 'bit-integrations'), - 400 - ); + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $refreshedToken = self::_refreshAccessToken((object) [ + 'clientId' => $queryParams->clientId ?? '', + 'clientSecret' => $queryParams->clientSecret ?? '', + 'tokenDetails' => $tokenDetails, + ]); + + if (!$refreshedToken) { + wp_send_json_error(__('Failed to refresh access token', 'bit-integrations'), 400); } + + $tokenDetails = $refreshedToken; } - $apiEndpoint = 'https://api.cc.email/v3/contact_tags'; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + $apiEndpoint = 'https://api.cc.email/v3/contact_tags'; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); + $allTag = []; if (!is_wp_error($apiResponse) && empty($apiResponse->response->error)) { $contactTags = $apiResponse->tags; @@ -158,51 +122,49 @@ public static function refreshTags($queryParams) ]; } uksort($allTag, 'strnatcasecmp'); - - $response['contactTag'] = $allTag; } else { - wp_send_json_error( - $apiResponse->response->error->message, - 400 - ); + wp_send_json_error($apiResponse->response->error->message ?? __('Tag fetch failed', 'bit-integrations'), 400); } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->integId)) { - static::_saveRefreshedToken($queryParams->integId, $response['tokenDetails'], $response); + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '') && !empty($queryParams->integId)) { + self::_saveRefreshedToken($queryParams->integId, $tokenDetails); } - wp_send_json_success($response, 200); + + wp_send_json_success( + [ + 'contactTag' => $allTag, + 'tokenDetails' => $tokenDetails + ], + 200 + ); } public static function getCustomFields($queryParams) { - if (empty($queryParams->tokenDetails) - || empty($queryParams->clientId) - || empty($queryParams->clientSecret) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); + $tokenDetails = self::normalizeConnectionToken($queryParams->tokenDetails ?? null); + $isConnectionAuth = !empty($queryParams->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; + + if (empty($oldToken)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $response = []; - if ((\intval($queryParams->tokenDetails->generates_on) + (1435 * 60)) < time()) { - $refreshedToken = ConstantContactController::_refreshAccessToken($queryParams); - if ($refreshedToken) { - $response['tokenDetails'] = $refreshedToken; - } else { - wp_send_json_error( - __('Failed to refresh access token', 'bit-integrations'), - 400 - ); + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $refreshedToken = self::_refreshAccessToken((object) [ + 'clientId' => $queryParams->clientId ?? '', + 'clientSecret' => $queryParams->clientSecret ?? '', + 'tokenDetails' => $tokenDetails, + ]); + + if (!$refreshedToken) { + wp_send_json_error(__('Failed to refresh access token', 'bit-integrations'), 400); } + + $tokenDetails = $refreshedToken; } - $apiEndpoint = 'https://api.cc.email/v3/contact_custom_fields'; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + $apiEndpoint = 'https://api.cc.email/v3/contact_custom_fields'; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $apiResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); $allCFields = []; if (!is_wp_error($apiResponse) && empty($apiResponse->response->error)) { @@ -215,26 +177,30 @@ public static function getCustomFields($queryParams) ]; } uksort($allCFields, 'strnatcasecmp'); - - $response['customFields'] = $allCFields; } else { - wp_send_json_error( - $apiResponse->response->error->message, - 400 - ); + wp_send_json_error($apiResponse->response->error->message ?? __('Custom fields fetch failed', 'bit-integrations'), 400); } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->integId)) { - static::_saveRefreshedToken($queryParams->integId, $response['tokenDetails'], $response); + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '') && !empty($queryParams->integId)) { + self::_saveRefreshedToken($queryParams->integId, $tokenDetails); } - wp_send_json_success($response, 200); + + wp_send_json_success( + [ + 'customFields' => $allCFields, + 'tokenDetails' => $tokenDetails + ], + 200 + ); } public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $auth_token = $integrationDetails->tokenDetails->access_token; + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); + $isConnectionAuth = !empty($integrationDetails->connection_id); + $authToken = $tokenDetails->access_token ?? ''; $listIds = $integrationDetails->list_ids; $tagIds = $integrationDetails->tag_ids; $fieldMap = $integrationDetails->field_map; @@ -245,24 +211,22 @@ public function execute($integrationData, $fieldValues) $phoneType = $integrationDetails->phone_type; $update = $integrationDetails->actions->update ?? false; - if ( - empty($fieldMap) - || empty($auth_token) - ) { + if (empty($fieldMap) || empty($authToken)) { // translators: %s: Placeholder value return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Constant Contact')); } - if ((\intval($integrationDetails->tokenDetails->generates_on) + (1435 * 60)) < time()) { - $requiredParams['clientId'] = $integrationDetails->clientId; - $requiredParams['clientSecret'] = $integrationDetails->clientSecret; - $requiredParams['tokenDetails'] = $integrationDetails->tokenDetails; - - $newTokenDetails = ConstantContactController::_refreshAccessToken((object) $requiredParams); + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $newTokenDetails = self::_refreshAccessToken((object) [ + 'clientId' => $integrationDetails->clientId ?? '', + 'clientSecret' => $integrationDetails->clientSecret ?? '', + 'tokenDetails' => $tokenDetails + ]); if ($newTokenDetails) { - ConstantContactController::_saveRefreshedToken($integId, $newTokenDetails); - $integrationDetails->tokenDetails->access_token = $newTokenDetails->access_token; + self::_saveRefreshedToken($integId, $newTokenDetails); + $tokenDetails = $newTokenDetails; + $integrationDetails->tokenDetails = $newTokenDetails; } else { LogHandler::save($integId, 'token', 'error', __('Failed to refresh access token', 'bit-integrations')); @@ -294,21 +258,24 @@ public function execute($integrationData, $fieldValues) protected static function _refreshAccessToken($apiData) { - if ( - empty($apiData->tokenDetails) - ) { + if (empty($apiData->tokenDetails) || empty($apiData->tokenDetails->refresh_token)) { + return false; + } + + $clientId = $apiData->clientId ?? ($apiData->tokenDetails->client_id ?? ''); + $clientSecret = $apiData->clientSecret ?? ($apiData->tokenDetails->client_secret ?? ''); + if (empty($clientId) || empty($clientSecret)) { return false; } - $tokenDetails = $apiData->tokenDetails; + $tokenDetails = $apiData->tokenDetails; $apiEndpoint = 'https://authz.constantcontact.com/oauth2/default/v1/token'; $requestParams = [ 'grant_type' => 'refresh_token', 'refresh_token' => $tokenDetails->refresh_token, ]; - $auth = $apiData->clientId . ':' . $apiData->clientSecret; - // Base64 encode it + $auth = $clientId . ':' . $clientSecret; $credentials = base64_encode($auth); $authorizationHeader['Authorization'] = 'Basic ' . $credentials; @@ -318,11 +285,52 @@ protected static function _refreshAccessToken($apiData) return false; } $tokenDetails->generates_on = time(); - $tokenDetails->access_token = $apiResponse->access_token; + $tokenDetails->generated_at = $tokenDetails->generates_on; + $tokenDetails->access_token = $apiResponse->access_token ?? $tokenDetails->access_token; + $tokenDetails->refresh_token = $apiResponse->refresh_token ?? $tokenDetails->refresh_token; + $tokenDetails->token_type = $apiResponse->token_type ?? ($tokenDetails->token_type ?? 'Bearer'); + $tokenDetails->expires_in = $apiResponse->expires_in ?? ($tokenDetails->expires_in ?? 0); return $tokenDetails; } + private static function isTokenExpired($tokenDetails) + { + if (empty($tokenDetails)) { + return true; + } + + $generatedOn = (int) ($tokenDetails->generates_on ?? $tokenDetails->generated_at ?? 0); + $expiresIn = (int) ($tokenDetails->expires_in ?? 0); + + if ($generatedOn <= 0) { + return true; + } + + if ($expiresIn > 0) { + return ($generatedOn + \max($expiresIn - 300, 0)) < time(); + } + + return ($generatedOn + (1435 * 60)) < time(); + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } + + if (empty($token->generated_at) && !empty($token->generates_on)) { + $token->generated_at = (int) $token->generates_on; + } + + return $token; + } + private static function _saveRefreshedToken($integrationID, $tokenDetails) { if (empty($integrationID)) { diff --git a/backend/Actions/ConstantContact/Routes.php b/backend/Actions/ConstantContact/Routes.php index 0b4bd4ddf..f992e793c 100644 --- a/backend/Actions/ConstantContact/Routes.php +++ b/backend/Actions/ConstantContact/Routes.php @@ -7,8 +7,6 @@ use BitApps\Integrations\Actions\ConstantContact\ConstantContactController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('cContact_generate_token', [ConstantContactController::class, 'generateTokens']); Route::post('cContact_refresh_list', [ConstantContactController::class, 'refreshList']); -Route::post('cContact_refresh_fields', [ConstantContactController::class, 'refreshListFields']); Route::post('cContact_refresh_tags', [ConstantContactController::class, 'refreshTags']); Route::post('cContact_custom_fields', [ConstantContactController::class, 'getCustomFields']); diff --git a/backend/Actions/LionDesk/LionDeskController.php b/backend/Actions/LionDesk/LionDeskController.php index 3a43eddad..1e27f3767 100644 --- a/backend/Actions/LionDesk/LionDeskController.php +++ b/backend/Actions/LionDesk/LionDeskController.php @@ -6,7 +6,9 @@ namespace BitApps\Integrations\Actions\LionDesk; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; +use BitApps\Integrations\Flow\FlowController; use WP_Error; /** @@ -14,7 +16,15 @@ */ class LionDeskController { - protected $_defaultHeader; + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'liondesk', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires', 'expires_in', 'generated_at']], + ], + ]; protected $apiEndpoint; @@ -23,125 +33,145 @@ public function __construct() $this->apiEndpoint = 'https://api-v2.liondesk.com/'; } - /** - * Process ajax request for generate_token - * - * @param $requestsParams Mandatory params for generate tokens - * - * @return JSON LionDesk api response and status - */ - public function generateTokens($requestsParams) + public function getCustomFields($fieldsRequestParams) { - if ( - empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoint = $this->apiEndpoint . '/oauth2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); + $tokenDetails = self::normalizeConnectionToken($fieldsRequestParams->tokenDetails ?? $fieldsRequestParams->token_details ?? null); + $isConnectionAuth = !empty($fieldsRequestParams->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; + + if (empty($oldToken)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public function getCustomFields($fieldsRequestParams) - { - $response = []; - if (strtotime($fieldsRequestParams->token_details->expires) < time()) { - $response['tokenDetails'] = $this->_refreshAccessToken($fieldsRequestParams); - $fieldsRequestParams->token_details = $response['tokenDetails']; + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $refreshedToken = self::_refreshAccessToken((object) [ + 'clientId' => $fieldsRequestParams->clientId ?? $fieldsRequestParams->client_id ?? '', + 'clientSecret' => $fieldsRequestParams->clientSecret ?? $fieldsRequestParams->client_secret ?? '', + 'redirectURI' => $fieldsRequestParams->redirectURI ?? $fieldsRequestParams->redirect_uri ?? '', + 'tokenDetails' => $tokenDetails, + ]); + + if (!$refreshedToken) { + wp_send_json_error(__('Failed to refresh access token', 'bit-integrations'), 400); + } + $tokenDetails = $refreshedToken; } - $this->checkValidation($fieldsRequestParams); - $access_token = $fieldsRequestParams->token_details->access_token; $apiEndpoint = $this->apiEndpoint . '/custom-fields'; - $headers = $this->setHeaders($access_token); - $response = HttpHelper::get($apiEndpoint, null, $headers); - if (isset($response)) { - if (isset($response->data)) { - foreach ($response->data as $customField) { - $customFields[] = [ - 'key' => $customField->id, - 'label' => $customField->name, - ]; - } - wp_send_json_success($customFields, 200); - } else { - wp_send_json_error($response->message, 400); + $headers = $this->setHeaders($tokenDetails->access_token); + $apiResponse = HttpHelper::get($apiEndpoint, null, $headers); + + if (!isset($apiResponse) || isset($apiResponse->message) && !isset($apiResponse->data)) { + wp_send_json_error($apiResponse->message ?? __('Custom field fetching failed', 'bit-integrations'), 400); + } + + $customFields = []; + if (!empty($apiResponse->data)) { + foreach ($apiResponse->data as $customField) { + $customFields[] = [ + 'key' => $customField->id, + 'label' => $customField->name, + ]; } - } else { - wp_send_json_error(__('Custom field fetching failed', 'bit-integrations'), 400); } + + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '') && !empty($fieldsRequestParams->id)) { + self::saveRefreshedToken($fieldsRequestParams->id, $tokenDetails); + } + + wp_send_json_success( + [ + 'customFields' => $customFields, + 'tokenDetails' => $tokenDetails, + ], + 200 + ); } public function getAllTags($fieldsRequestParams) { - $response = []; - if (strtotime($fieldsRequestParams->token_details->expires) < time()) { - $response['tokenDetails'] = $this->_refreshAccessToken($fieldsRequestParams); - $fieldsRequestParams->token_details = $response['tokenDetails']; + $tokenDetails = self::normalizeConnectionToken($fieldsRequestParams->tokenDetails ?? $fieldsRequestParams->token_details ?? null); + $isConnectionAuth = !empty($fieldsRequestParams->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; + + if (empty($oldToken)) { + wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + } + + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $refreshedToken = self::_refreshAccessToken((object) [ + 'clientId' => $fieldsRequestParams->clientId ?? $fieldsRequestParams->client_id ?? '', + 'clientSecret' => $fieldsRequestParams->clientSecret ?? $fieldsRequestParams->client_secret ?? '', + 'redirectURI' => $fieldsRequestParams->redirectURI ?? $fieldsRequestParams->redirect_uri ?? '', + 'tokenDetails' => $tokenDetails, + ]); + + if (!$refreshedToken) { + wp_send_json_error(__('Failed to refresh access token', 'bit-integrations'), 400); + } + $tokenDetails = $refreshedToken; } - $this->checkValidation($fieldsRequestParams); - $access_token = $fieldsRequestParams->token_details->access_token; $apiEndpoint = $this->apiEndpoint . '/tags'; - $headers = $this->setHeaders($access_token); - $response = HttpHelper::get($apiEndpoint, null, $headers); - - if (isset($response)) { - if (isset($response->data)) { - foreach ($response->data as $tag) { - $tags[] = [ - 'tag' => $tag->content - ]; - } - wp_send_json_success($tags, 200); - } else { - wp_send_json_error($response->message, 400); + $headers = $this->setHeaders($tokenDetails->access_token); + $apiResponse = HttpHelper::get($apiEndpoint, null, $headers); + + if (!isset($apiResponse) || isset($apiResponse->message) && !isset($apiResponse->data)) { + wp_send_json_error($apiResponse->message ?? __('Tags fetching failed', 'bit-integrations'), 400); + } + + $tags = []; + if (!empty($apiResponse->data)) { + foreach ($apiResponse->data as $tag) { + $tags[] = [ + 'tag' => $tag->content + ]; } - } else { - wp_send_json_error(__('Tags fetching failed', 'bit-integrations'), 400); } + + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '') && !empty($fieldsRequestParams->id)) { + self::saveRefreshedToken($fieldsRequestParams->id, $tokenDetails); + } + + wp_send_json_success( + [ + 'tags' => $tags, + 'tokenDetails' => $tokenDetails, + ], + 200 + ); } public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; $integId = $integrationData->id; - $tokenDetails = $integrationDetails->tokenDetails; + $tokenDetails = self::normalizeConnectionToken($integrationDetails->tokenDetails ?? null); $fieldMap = $integrationDetails->field_map; $actionName = $integrationDetails->actionName; + $isConnectionAuth = !empty($integrationDetails->connection_id); + $oldToken = $tokenDetails->access_token ?? ''; - if (empty($fieldMap) || empty($tokenDetails) || empty($actionName)) { + if (empty($fieldMap) || empty($oldToken) || empty($actionName)) { // translators: %s: Placeholder value return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'LionDesk')); } - $response = []; - if (strtotime($tokenDetails->expires) < time()) { - $response['tokenDetails'] = $this->_refreshAccessToken($tokenDetails); - $tokenDetails = $response['tokenDetails']; + if (!$isConnectionAuth && self::isTokenExpired($tokenDetails)) { + $tokenDetails = self::_refreshAccessToken((object) [ + 'clientId' => $integrationDetails->clientId ?? '', + 'clientSecret' => $integrationDetails->clientSecret ?? '', + 'redirectURI' => $integrationDetails->redirectURI ?? '', + 'tokenDetails' => $tokenDetails, + ]); + + if (!$tokenDetails || empty($tokenDetails->access_token)) { + return new WP_Error('AUTH_FAILED', __('Failed to refresh access token', 'bit-integrations')); + } + } + + if (!$isConnectionAuth && $oldToken !== ($tokenDetails->access_token ?? '')) { + self::saveRefreshedToken($integId, $tokenDetails); } $recordApiHelper = new RecordApiHelper($integrationDetails, $integId, $tokenDetails); @@ -159,46 +189,95 @@ public function execute($integrationData, $fieldValues) * * @param object $apiData Contains required data for refresh access token * - * @return JSON $tokenDetails API token details + * @return object|false $tokenDetails API token details */ - protected function _refreshAccessToken($apiData) + protected static function _refreshAccessToken($apiData) { - if ( - empty($apiData->client_id) - || empty($apiData->client_secret) - || empty($apiData->token_details) - || empty($apiData->redirect_uri) + if (empty($apiData->clientId) + || empty($apiData->clientSecret) + || empty($apiData->tokenDetails) + || empty($apiData->tokenDetails->refresh_token) ) { return false; } - $apiEndpoint = $this->apiEndpoint . '/oauth2/token'; + $apiEndpoint = 'https://api-v2.liondesk.com/oauth2/token'; $requestParams = [ - 'refresh_token' => $apiData->token_details->refresh_token, - 'client_id' => $apiData->client_id, - 'client_secret' => $apiData->client_secret, - 'redirect_uri' => $apiData->redirect_uri, + 'refresh_token' => $apiData->tokenDetails->refresh_token, + 'client_id' => $apiData->clientId, + 'client_secret' => $apiData->clientSecret, 'grant_type' => 'refresh_token', ]; + if (!empty($apiData->redirectURI)) { + $requestParams['redirect_uri'] = $apiData->redirectURI; + } + $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); + return false; } - $apiResponse->generates_on = time(); + $tokenDetails = $apiData->tokenDetails; + $tokenDetails->access_token = $apiResponse->access_token ?? $tokenDetails->access_token; + $tokenDetails->token_type = $apiResponse->token_type ?? ($tokenDetails->token_type ?? 'Bearer'); + $tokenDetails->expires_in = $apiResponse->expires_in ?? ($tokenDetails->expires_in ?? 0); + $tokenDetails->expires = $apiResponse->expires ?? ($tokenDetails->expires ?? ''); + $tokenDetails->refresh_token = $apiResponse->refresh_token ?? $tokenDetails->refresh_token; + $tokenDetails->generates_on = time(); + $tokenDetails->generated_at = $tokenDetails->generates_on; - return $apiResponse; + return $tokenDetails; } - private function checkValidation($fieldsRequestParams, $customParam = '**') + private static function isTokenExpired($tokenDetails) { - if (empty($fieldsRequestParams->token_details->access_token) || empty($customParam)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); + if (empty($tokenDetails)) { + return true; + } + + if (!empty($tokenDetails->expires)) { + $expiresOn = strtotime($tokenDetails->expires); + if ($expiresOn && $expiresOn < time()) { + return true; + } } + + $generatedOn = (int) ($tokenDetails->generates_on ?? $tokenDetails->generated_at ?? 0); + $expiresIn = (int) ($tokenDetails->expires_in ?? 0); + + return $generatedOn > 0 && $expiresIn > 0 && ($generatedOn + ($expiresIn - 300)) < time(); + } + + private static function normalizeConnectionToken($token) + { + if (!\is_object($token)) { + $token = (object) []; + } + + if (empty($token->generates_on) && !empty($token->generated_at)) { + $token->generates_on = (int) $token->generated_at; + } + + return $token; + } + + private static function saveRefreshedToken($integrationID, $tokenDetails) + { + if (empty($integrationID)) { + return; + } + + $flow = new FlowController(); + $lionDeskDetails = $flow->get(['id' => $integrationID]); + + if (is_wp_error($lionDeskDetails)) { + return; + } + + $newDetails = json_decode($lionDeskDetails[0]->flow_details); + $newDetails->tokenDetails = $tokenDetails; + $flow->update($integrationID, ['flow_details' => wp_json_encode($newDetails)]); } private function setHeaders($access_token) diff --git a/backend/Actions/LionDesk/Routes.php b/backend/Actions/LionDesk/Routes.php index 6244d9829..6d974534b 100644 --- a/backend/Actions/LionDesk/Routes.php +++ b/backend/Actions/LionDesk/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\LionDesk\LionDeskController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('lionDesk_generate_token', [LionDeskController::class, 'generateTokens']); Route::post('lionDesk_fetch_custom_fields', [LionDeskController::class, 'getCustomFields']); Route::post('lionDesk_fetch_all_tags', [LionDeskController::class, 'getAllTags']); diff --git a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContact.jsx b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContact.jsx index 258b4a095..2c45caab3 100644 --- a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContact.jsx +++ b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContact.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' @@ -11,7 +11,6 @@ import ConstantContactAuthorization from './ConstantContactAuthorization' import { checkAddressFieldMapRequired, handleInput, - setGrantTokenResponse, checkMappedFields, generateMappedField } from './ConstantContactCommonFunc' @@ -95,10 +94,6 @@ function ConstantContact({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('constantContact') - }, []) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -130,9 +125,6 @@ function ConstantContact({ formFields, setFlow, flow, allIntegURL }) { setConstantContactConf={setConstantContactConf} step={step} setstep={setstep} - isLoading={isLoading} - setIsLoading={setIsLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactAuthorization.jsx b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactAuthorization.jsx index 7983f8314..2a6cca244 100644 --- a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactAuthorization.jsx @@ -1,195 +1,60 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import Note from '../../Utilities/Note' -import CopyText from '../../Utilities/CopyText' -import { handleConstantContactAuthorize } from './ConstantContactCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' -import { $appConfigState } from '../../../GlobalStates' -import { useRecoilValue } from 'recoil' +import Authorization from '../../Connections/Authorization' export default function ConstantContactAuthorization({ constantContactConf, setConstantContactConf, step, setstep, - isLoading, - setIsLoading, - setSnackbar, - redirectLocation, isInfo }) { - const btcbi = useRecoilValue($appConfigState) - const [isAuthorized, setisAuthorized] = useState(false) -const [error, setError] = useState({ - dataCenter: '', - clientId: '', - clientSecret: '' - }) - const scopes = 'account_read account_update contact_data offline_access campaign_data' - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - } - - const handleInput = e => { - const newConf = { ...constantContactConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setConstantContactConf(newConf) - } - const note = ` -

    ${__('Step of get API Key(Client Id) And Client Secret:', 'bit-integrations')}

    +

    ${__('Steps to get Client ID and Client Secret', 'bit-integrations')}

    • ${__( - 'Goto', - 'bit-integrations' - )} ${__( - 'Constant Contact Application', - 'bit-integrations' - )}
    • -
    • ${__('Then create a new application.', 'bit-integrations')}
    • -
    • ${__( - 'Select (Authorization Code Flow and Implicit Flow) and (Rotating Refresh Tokens or Long Lived Refresh Tokens).', + 'Go to Constant Contact developer portal and create app.', 'bit-integrations' )}
    • ${__( - 'Copy the Authorized Redirect URIs from here and paste it into the Constant Contact application form.', - 'bit-integrations' - )}
    • -
    • ${__( - 'Then generate Client Secret from the Constant Contact application', + 'Enable Authorization Code flow and refresh token support.', 'bit-integrations' )}
    • ${__( - 'Copy the Client Id and Client Secret from Constant Contact application and paste into this authorization form.', + 'Copy redirect URI from this form and add it to app configuration.', 'bit-integrations' )}
    • -
    • ${__('Finally, click Authorize button.', 'bit-integrations')}
    • -
    +
  • ${__('Copy client ID and client secret, then click Authorize.', 'bit-integrations')}
  • +
` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Constant Contact Application', 'bit-integrations')} - - - -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
-
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactCommonFunc.js b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactCommonFunc.js index c5faea722..ac7da5dc0 100644 --- a/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ConstantContact/ConstantContactCommonFunc.js @@ -1,7 +1,7 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { deepCopy } from '../../../Utils/Helpers' -import { sprintf, __ } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' export const handleInput = ( e, @@ -43,6 +43,15 @@ export const checkAddressFieldMapRequired = constantContactConf => { return true } +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + const listChange = (constantContactConf, setConstantContactConf) => { const newConf = deepCopy(constantContactConf) newConf.field_map = [{ formField: '', constantContactFormField: '' }] @@ -55,9 +64,7 @@ export const getAllContactLists = (id, confTmp, setConf, isLoading, setIsLoading const requestParams = { integId: id, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - tokenDetails: confTmp.tokenDetails + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'cContact_refresh_list').then(result => { @@ -65,6 +72,9 @@ export const getAllContactLists = (id, confTmp, setConf, isLoading, setIsLoading const newConf = { ...confTmp } if (result.data) { newConf.lists = result.data.contactList + if (result.data.tokenDetails) { + newConf.tokenDetails = result.data.tokenDetails + } } setConf(newConf) setIsLoading({ ...isLoading, list: false }) @@ -79,18 +89,19 @@ export const getAllContactLists = (id, confTmp, setConf, isLoading, setIsLoading const getAllCustomFields = (confTmp, setConf) => { const requestParams = { - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - tokenDetails: confTmp.tokenDetails + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'cContact_custom_fields').then(result => { if (result && result.success) { const newConf = { ...confTmp } - if (result.data) { + if (result.data?.customFields) { const mergedFields = newConf.default.constantContactFields.concat(result.data.customFields) newConf.default.constantContactFields = mergedFields } + if (result.data?.tokenDetails) { + newConf.tokenDetails = result.data.tokenDetails + } setConf(newConf) } }) @@ -101,9 +112,7 @@ export const getContactTags = (id, confTmp, setConf, isLoading, setIsLoading) => const requestParams = { integId: id, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - tokenDetails: confTmp.tokenDetails + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'cContact_refresh_tags').then(result => { @@ -111,6 +120,9 @@ export const getContactTags = (id, confTmp, setConf, isLoading, setIsLoading) => const newConf = { ...confTmp } if (result.data) { newConf.tags = result.data.contactTag + if (result.data.tokenDetails) { + newConf.tokenDetails = result.data.tokenDetails + } } setConf(newConf) setIsLoading({ ...isLoading, tag: false }) @@ -123,147 +135,6 @@ export const getContactTags = (id, confTmp, setConf, isLoading, setIsLoading) => }) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleConstantContactAuthorize = ( - integ, - ajaxInteg, - scopes, - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const apiEndpoint = `https://authz.constantcontact.com/oauth2/default/v1/authorize?scope=${scopes}&response_type=code&client_id=${ - confTmp.clientId - }&state=${encodeURIComponent(window.location.href)}/redirect&redirect_uri=${encodeURIComponent( - `${btcbi.api}` - )}/redirect` - - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsConstantContact = localStorage.getItem(`__${integ}`) - if (bitsConstantContact) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsConstantContact) - localStorage.removeItem(`__${integ}`) - if (grantTokenResponse.code.search('#')) { - const [code] = grantTokenResponse.code.split('#') - grantTokenResponse.code = code - } - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - ajaxInteg, - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - ajaxInteg, - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, `${ajaxInteg}_generate_token`) - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = sheetconf => { const mappedFleld = sheetconf.field_map ? sheetconf.field_map.filter(mapped => !mapped.formField && !mapped.constantContactFormField) @@ -275,10 +146,8 @@ export const checkMappedFields = sheetconf => { } export const generateMappedField = constantContactFields => { - const requiredFlds = constantContactFields?.filter( - fld => fld.required === true - ) - + const requiredFlds = constantContactFields?.filter(fld => fld.required === true) + return requiredFlds.length > 0 ? requiredFlds.map(field => ({ formField: '', diff --git a/frontend/src/components/AllIntegrations/LionDesk/LionDesk.jsx b/frontend/src/components/AllIntegrations/LionDesk/LionDesk.jsx index 6a237cbfe..7a03f35f7 100644 --- a/frontend/src/components/AllIntegrations/LionDesk/LionDesk.jsx +++ b/frontend/src/components/AllIntegrations/LionDesk/LionDesk.jsx @@ -1,6 +1,6 @@ /* eslint-disable no-console */ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import toast from 'react-hot-toast' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate } from 'react-router' @@ -10,7 +10,7 @@ import Steps from '../../Utilities/Steps' import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import LionDeskAuthorization from './LionDeskAuthorization' -import { checkMappedFields, handleInput, setGrantTokenResponse } from './LionDeskCommonFunc' +import { checkMappedFields, handleInput } from './LionDeskCommonFunc' import LionDeskIntegLayout from './LionDeskIntegLayout' function LionDesk({ formFields, setFlow, flow, allIntegURL }) { @@ -58,10 +58,6 @@ function LionDesk({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('lionDesk') - }, []) - const saveConfig = () => { setIsLoading(true) const resp = saveIntegConfig( @@ -110,9 +106,6 @@ function LionDesk({ formFields, setFlow, flow, allIntegURL }) { setLionDeskConf={setLionDeskConf} step={step} setStep={setStep} - setSnackbar={setSnackbar} - isLoading={isLoading} - setIsLoading={setIsLoading} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/LionDesk/LionDeskAuthorization.jsx b/frontend/src/components/AllIntegrations/LionDesk/LionDeskAuthorization.jsx index 508ddc95e..0159981ae 100644 --- a/frontend/src/components/AllIntegrations/LionDesk/LionDeskAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LionDesk/LionDeskAuthorization.jsx @@ -1,164 +1,51 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import Note from '../../Utilities/Note' -import { handleAuthorize } from './LionDeskCommonFunc' -import TutorialLink from '../../Utilities/TutorialLink' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' +import Authorization from '../../Connections/Authorization' export default function LionDeskAuthorization({ lionDeskConf, setLionDeskConf, step, setStep, - setSnackbar, - isLoading, - setIsLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ session_token: '' }) - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - !lionDeskConf?.default - setStep(2) - } - - const handleInput = e => { - const newConf = { ...lionDeskConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setLionDeskConf(newConf) - } - - const ActiveInstructions = ` -

${__('Get the Redirect URI, Client Id and Client Secret', 'bit-integrations')}

-
    -
  • ${__('First go to your Lion Desk Developer Center Apps.', 'bit-integrations')}
  • -
  • ${__('Then Click "New App+" from Right in the middle', 'bit-integrations')}
  • -
  • ${__('Then input the "Name and Redirect URI" then save', 'bit-integrations')}
  • -
  • ${__( - 'Then click "REVEAL CLIENT ID" and "REVEAL CLIENT SECRET", Then Copied', - 'bit-integrations' - )}
  • -
` + const note = ` +

${__('Get Redirect URI, Client ID and Client Secret', 'bit-integrations')}

+
    +
  • ${__('Go to LionDesk Developer Center Apps.', 'bit-integrations')}
  • +
  • ${__('Create a new app and set redirect URI from this form.', 'bit-integrations')}
  • +
  • ${__('Copy client ID and client secret from LionDesk app.', 'bit-integrations')}
  • +
  • ${__('Authorize to complete connection.', 'bit-integrations')}
  • +
+ ` return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
- -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')} -   - - {__('Lion Desk Apps', 'bit-integrations')} - - -
-
- - {!isInfo && ( -
- -
- -
- )} - -
+ ) } diff --git a/frontend/src/components/AllIntegrations/LionDesk/LionDeskCommonFunc.js b/frontend/src/components/AllIntegrations/LionDesk/LionDeskCommonFunc.js index 977b6bb65..b695c31e1 100644 --- a/frontend/src/components/AllIntegrations/LionDesk/LionDeskCommonFunc.js +++ b/frontend/src/components/AllIntegrations/LionDesk/LionDeskCommonFunc.js @@ -47,156 +47,32 @@ export const checkMappedFields = lionDeskConf => { return true } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + tokenDetails: conf.tokenDetails, + clientId: conf.clientId, + clientSecret: conf.clientSecret, + redirectURI: conf.redirectURI } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleAuthorize = ( - integ, - ajaxInteg, - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - - setIsLoading(true) - const apiEndpoint = `https://api-v2.liondesk.com/oauth2/authorize?response_type=code&client_id=${ - confTmp.clientId - }&state=${encodeURIComponent(window.location.href)}/redirect&redirect_uri=${encodeURIComponent( - `${btcbi.api}` - )}/redirect&scope=['write','read']` - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitintegrationLionDesk = localStorage.getItem(`__${integ}`) - - if (bitintegrationLionDesk) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitintegrationLionDesk) - localStorage.removeItem(`__${integ}`) - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - ajaxInteg, - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - ajaxInteg, - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - - bitsFetch(tokenRequestParams, `${ajaxInteg}_generate_token`) - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} -export const getCustomFields = (confTmp, setConf, setIsLoading, btcbi) => { +export const getCustomFields = (confTmp, setConf, setIsLoading) => { setIsLoading(true) const requestParams = { - token_details: confTmp.tokenDetails, - client_id: confTmp.clientId, - client_secret: confTmp.clientSecret, - redirect_uri: `${btcbi.api}/redirect` + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'lionDesk_fetch_custom_fields').then(result => { if (result && result.success) { setIsLoading(false) - if (result.data) { + if (result.data?.customFields) { setConf(prevConf => { const newConf = { ...prevConf } - newConf.customFields = result.data + newConf.customFields = result.data.customFields + if (result.data.tokenDetails) { + newConf.tokenDetails = result.data.tokenDetails + } return newConf }) toast.success(__('Custom fields also fetched successfully', 'bit-integrations')) @@ -213,19 +89,19 @@ export const getCustomFields = (confTmp, setConf, setIsLoading, btcbi) => { export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tags: true }) const requestParams = { - token_details: confTmp.tokenDetails, - client_id: confTmp.clientId, - client_secret: confTmp.clientSecret, - redirect_uri: confTmp.redirectURI + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'lionDesk_fetch_all_tags').then(result => { if (result && result.success) { setLoading({ ...setLoading, tags: false }) - if (result.data) { + if (result.data?.tags) { setConf(prevConf => { const newConf = { ...prevConf } - newConf.tags = result.data + newConf.tags = result.data.tags + if (result.data.tokenDetails) { + newConf.tokenDetails = result.data.tokenDetails + } return newConf }) toast.success(__('Tags fetched successfully', 'bit-integrations')) diff --git a/frontend/src/components/AllIntegrations/LionDesk/LionDeskIntegLayout.jsx b/frontend/src/components/AllIntegrations/LionDesk/LionDeskIntegLayout.jsx index 4805008fd..947fad04b 100644 --- a/frontend/src/components/AllIntegrations/LionDesk/LionDeskIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/LionDesk/LionDeskIntegLayout.jsx @@ -1,7 +1,5 @@ /* 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' @@ -20,7 +18,6 @@ export default function LionDeskIntegLayout({ setIsLoading, setSnackbar }) { - const btcbi = useRecoilValue($appConfigState) const handleActionInput = e => { const newConf = { ...lionDeskConf } const { name } = e.target @@ -29,7 +26,7 @@ export default function LionDeskIntegLayout({ if (e.target.value !== '') { newConf[name] = e.target.value if (e.target.value === 'contact') { - getCustomFields(newConf, setLionDeskConf, setIsLoading, btcbi) + getCustomFields(newConf, setLionDeskConf, setIsLoading) } } else { delete newConf[name] @@ -85,7 +82,7 @@ export default function LionDeskIntegLayout({ {__('Field Map', 'bit-integrations')} {lionDeskConf.actionName === 'contact' && (
))} From c4b602c68afa1e1a76f8efe2a2eec36b04f7bce6 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Wed, 13 May 2026 16:25:30 +0600 Subject: [PATCH 43/58] refactor: Zoho integrations to streamline authorization process and remove unused functions - Removed setGrantTokenResponse function from ZohoMarketingHub, ZohoRecruit, and ZohoSheet components. - Simplified authorization handling in ZohoMarketingHubAuthorization, ZohoRecruitAuthorization, and ZohoSheetAuthorization components by integrating a common Authorization component. - Updated refreshLists and refreshModules functions to utilize a buildAuthRequestParams helper for cleaner parameter handling. - Removed unnecessary state management and error handling related to client ID and secret in authorization components. - Enhanced user experience by ensuring proper loading states and error messages during authorization. --- backend/Actions/Keap/KeapController.php | 58 +--- backend/Actions/Keap/Routes.php | 1 - backend/Actions/ZohoAnalytics/Routes.php | 2 - .../ZohoAnalytics/ZohoAnalyticsController.php | 65 +---- backend/Actions/ZohoBigin/Routes.php | 2 - .../Actions/ZohoBigin/ZohoBiginController.php | 67 +---- backend/Actions/ZohoCRM/Routes.php | 1 - backend/Actions/ZohoCRM/ZohoCRMController.php | 63 +---- backend/Actions/ZohoCampaigns/Routes.php | 1 - .../ZohoCampaigns/ZohoCampaignsController.php | 65 +---- backend/Actions/ZohoCreator/Routes.php | 6 - .../ZohoCreator/ZohoCreatorController.php | 58 +--- backend/Actions/ZohoDesk/Routes.php | 1 - .../Actions/ZohoDesk/ZohoDeskController.php | 57 +--- backend/Actions/ZohoMarketingHub/Routes.php | 1 - .../ZohoMarketingHubController.php | 65 +---- backend/Actions/ZohoRecruit/Routes.php | 1 - .../ZohoRecruit/ZohoRecruitController.php | 64 +---- backend/Actions/ZohoSheet/Routes.php | 1 - .../Actions/ZohoSheet/ZohoSheetController.php | 41 +-- backend/Core/Util/CredentialInjector.php | 10 + .../components/AllIntegrations/Keap/Keap.jsx | 10 +- .../Keap/KeapAuthorization.jsx | 191 ++++--------- .../AllIntegrations/Keap/KeapCommonFunc.js | 129 +-------- .../Keap/KeapIntegrationHelpers.js | 19 -- .../ZohoAnalytics/ZohoAnalytics.jsx | 6 +- .../ZohoAnalyticsAuthorization.jsx | 257 ++++++------------ .../ZohoAnalytics/ZohoAnalyticsCommonFunc.js | 135 +-------- .../AllIntegrations/ZohoBigin/ZohoBigin.jsx | 3 +- .../ZohoBigin/ZohoBiginAuthorization.jsx | 231 +++++----------- .../ZohoBigin/ZohoBiginCommonFunc.js | 34 ++- .../AllIntegrations/ZohoCRM/ZohoCRM.jsx | 8 +- .../ZohoCRM/ZohoCRMAuthorization.jsx | 231 +++++----------- .../ZohoCRM/ZohoCRMCommonFunc.js | 34 ++- .../ZohoCampaigns/ZohoCampaigns.jsx | 6 +- .../ZohoCampaignsAuthorization.jsx | 233 +++++----------- .../ZohoCampaigns/ZohoCampaignsCommonFunc.js | 18 +- .../ZohoCreator/ZohoCreator.jsx | 6 +- .../ZohoCreator/ZohoCreatorAuthorization.jsx | 251 ++++++----------- .../ZohoCreator/ZohoCreatorCommonFunc.js | 131 +-------- .../AllIntegrations/ZohoDesk/ZohoDesk.jsx | 6 +- .../ZohoDesk/ZohoDeskAuthorization.jsx | 231 +++++----------- .../ZohoDesk/ZohoDeskCommonFunc.js | 30 +- .../ZohoMarketingHub/ZohoMarketingHub.jsx | 7 +- .../ZohoMarketingHubAuthorization.jsx | 233 +++++----------- .../ZohoMarketingHubCommonFunc.js | 37 +-- .../ZohoRecruit/ZohoRecruit.jsx | 6 +- .../ZohoRecruit/ZohoRecruitAuthorization.jsx | 231 +++++----------- .../ZohoRecruit/ZohoRecruitCommonFunc.js | 26 +- .../AllIntegrations/ZohoSheet/ZohoSheet.jsx | 8 +- .../ZohoSheet/ZohoSheetAuthorization.jsx | 236 ++++++---------- .../ZohoSheet/ZohoSheetCommonFunc.js | 137 +--------- 52 files changed, 1020 insertions(+), 2731 deletions(-) diff --git a/backend/Actions/Keap/KeapController.php b/backend/Actions/Keap/KeapController.php index 4ad8876a4..afc3c4b1a 100644 --- a/backend/Actions/Keap/KeapController.php +++ b/backend/Actions/Keap/KeapController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Keap; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; @@ -15,6 +16,16 @@ */ class KeapController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'keap', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -24,8 +35,6 @@ public function __construct($integrationID) public static function refreshTagListAjaxHelper($queryParams) { - // var_dump($queryParams->tokenDetails); - // die; if ( empty($queryParams->clientId) || empty($queryParams->clientSecret) @@ -114,51 +123,6 @@ public static function refreshCustomFieldAjaxHelper($queryParams) wp_send_json_success($customFields, 200); } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params for generate token - * - * @return JSON Keap api response and status - */ - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = 'https://api.infusionsoft.com/token'; - $authorizationHeader['Content-Type'] = 'application/x-www-form-urlencoded'; - $requestParams = [ - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'code' => $requestsParams->code, - 'grant_type' => 'authorization_code', - 'redirect_uri' => $requestsParams->redirectURI - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams, $authorizationHeader); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function refreshAccessToken($requestsParams) { if ( diff --git a/backend/Actions/Keap/Routes.php b/backend/Actions/Keap/Routes.php index 4a8d39072..5cebd820a 100644 --- a/backend/Actions/Keap/Routes.php +++ b/backend/Actions/Keap/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\Keap\KeapController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('keap_generate_token', [KeapController::class, 'generateTokens']); Route::post('keap_fetch_all_tags', [KeapController::class, 'refreshTagListAjaxHelper']); Route::post('keap_fetch_all_custom_fields', [KeapController::class, 'refreshCustomFieldAjaxHelper']); diff --git a/backend/Actions/ZohoAnalytics/Routes.php b/backend/Actions/ZohoAnalytics/Routes.php index d0367e118..ef3737384 100644 --- a/backend/Actions/ZohoAnalytics/Routes.php +++ b/backend/Actions/ZohoAnalytics/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ZohoAnalytics\ZohoAnalyticsController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zanalytics_generate_token', [ZohoAnalyticsController::class, 'generateTokens']); Route::post('zanalytics_refresh_workspaces', [ZohoAnalyticsController::class, 'refreshWorkspacesAjaxHelper']); Route::post('zanalytics_refresh_users', [ZohoAnalyticsController::class, 'refreshUsersAjaxHelper']); Route::post('zanalytics_refresh_tables', [ZohoAnalyticsController::class, 'refreshTablesAjaxHelper']); @@ -15,7 +14,6 @@ // public static function registerAjax() // { -// add_action('wp_ajax_zanalytics_generate_token', array(__CLASS__, 'generateTokens')); // add_action('wp_ajax_zanalytics_refresh_workspaces', array(__CLASS__, 'refreshWorkspacesAjaxHelper')); // add_action('wp_ajax_zanalytics_refresh_users', array(__CLASS__, 'refreshUsersAjaxHelper')); // add_action('wp_ajax_zanalytics_refresh_tables', array(__CLASS__, 'refreshTablesAjaxHelper')); diff --git a/backend/Actions/ZohoAnalytics/ZohoAnalyticsController.php b/backend/Actions/ZohoAnalytics/ZohoAnalyticsController.php index 5da2283f2..789aed3b7 100644 --- a/backend/Actions/ZohoAnalytics/ZohoAnalyticsController.php +++ b/backend/Actions/ZohoAnalytics/ZohoAnalyticsController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoAnalytics; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\ApiResponse as UtilApiResponse; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Core\Util\IpTool; @@ -17,6 +18,18 @@ */ class ZohoAnalyticsController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohoanalytics', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'ownerEmail' => 'ownerEmail', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -25,58 +38,6 @@ public function __construct($integrationID) $this->_logResponse = new UtilApiResponse(); } - /** - * Process ajax request for generate_token - * - * @param mixed $requestsParams - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh crm modules - * - * @param mixed $queryParams - * - * @return JSON crm module data - */ public static function refreshWorkspacesAjaxHelper($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoBigin/Routes.php b/backend/Actions/ZohoBigin/Routes.php index 82cbd4d5e..b3450a9f1 100644 --- a/backend/Actions/ZohoBigin/Routes.php +++ b/backend/Actions/ZohoBigin/Routes.php @@ -7,10 +7,8 @@ use BitApps\Integrations\Actions\ZohoBigin\ZohoBiginController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zbigin_generate_token', [ZohoBiginController::class, 'generateTokens']); Route::post('zbigin_refresh_modules', [ZohoBiginController::class, 'refreshModules']); Route::post('zbigin_refresh_playouts', [ZohoBiginController::class, 'refreshPLayouts']); -Route::post('zbigin_refresh_notetypes', [ZohoBiginController::class, 'refreshNoteTypes']); Route::post('zbigin_refresh_related_lists', [ZohoBiginController::class, 'refreshRelatedModules']); Route::post('zbigin_refresh_fields', [ZohoBiginController::class, 'getFields']); Route::post('zbigin_refresh_tags', [ZohoBiginController::class, 'getTagList']); diff --git a/backend/Actions/ZohoBigin/ZohoBiginController.php b/backend/Actions/ZohoBigin/ZohoBiginController.php index 88033501c..f88d23885 100644 --- a/backend/Actions/ZohoBigin/ZohoBiginController.php +++ b/backend/Actions/ZohoBigin/ZohoBiginController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoBigin; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\Hooks; use BitApps\Integrations\Core\Util\HttpHelper; @@ -18,6 +19,17 @@ */ class ZohoBiginController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohobigin', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on', 'api_domain']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -25,59 +37,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to generate token - * - * @return JSON zoho bigin api response and status - */ - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh bigin modules - * - * @param object $queryParams Params to refresh modules - * - * @return JSON bigin module data - */ public static function refreshModules($queryParams) { if ( @@ -372,7 +331,7 @@ public static function getTagList($queryParams) wp_send_json_success($response, 200); } - public function getUsers($queryParams) + public static function getUsers($queryParams) { if ( empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoCRM/Routes.php b/backend/Actions/ZohoCRM/Routes.php index 6ef00f0e7..f772b8af3 100644 --- a/backend/Actions/ZohoCRM/Routes.php +++ b/backend/Actions/ZohoCRM/Routes.php @@ -11,6 +11,5 @@ Route::post('zcrm_get_tags', [ZohoCRMController::class, 'refreshTagListAjaxHelper']); Route::post('zcrm_get_assignment_rules', [ZohoCRMController::class, 'getAssignmentRulesAjaxHelper']); Route::post('zcrm_get_related_lists', [ZohoCRMController::class, 'getRelatedListsAjaxHelper']); -Route::no_sanitize()->post('zcrm_generate_token', [ZohoCRMController::class, 'generateTokens']); Route::post('zcrm_refresh_modules', [ZohoCRMController::class, 'refreshModulesAjaxHelper']); Route::post('zcrm_refresh_layouts', [ZohoCRMController::class, 'refreshLayoutsAjaxHelper']); diff --git a/backend/Actions/ZohoCRM/ZohoCRMController.php b/backend/Actions/ZohoCRM/ZohoCRMController.php index ae4359eba..61d55ce73 100644 --- a/backend/Actions/ZohoCRM/ZohoCRMController.php +++ b/backend/Actions/ZohoCRM/ZohoCRMController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoCRM; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -17,6 +18,17 @@ */ final class ZohoCRMController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohocrm', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on', 'api_domain']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -24,57 +36,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param $requestsParams Mandatory params for generate tokens - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh crm modules - * - * @param $queryParams Mandatory params to get modules - * - * @return JSON crm module data - */ public static function refreshModulesAjaxHelper($queryParams) { if ( diff --git a/backend/Actions/ZohoCampaigns/Routes.php b/backend/Actions/ZohoCampaigns/Routes.php index 70cd74e34..4e3b42899 100644 --- a/backend/Actions/ZohoCampaigns/Routes.php +++ b/backend/Actions/ZohoCampaigns/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\ZohoCampaigns\ZohoCampaignsController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zcampaigns_generate_token', [ZohoCampaignsController::class, 'generateTokens']); Route::post('zcampaigns_refresh_lists', [ZohoCampaignsController::class, 'refreshLists']); Route::post('zcampaigns_refresh_contact_fields', [ZohoCampaignsController::class, 'refreshContactFields']); diff --git a/backend/Actions/ZohoCampaigns/ZohoCampaignsController.php b/backend/Actions/ZohoCampaigns/ZohoCampaignsController.php index e90746f52..ff1df3dec 100644 --- a/backend/Actions/ZohoCampaigns/ZohoCampaignsController.php +++ b/backend/Actions/ZohoCampaigns/ZohoCampaignsController.php @@ -6,8 +6,8 @@ namespace BitApps\Integrations\Actions\ZohoCampaigns; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\ApiResponse as UtilApiResponse; - use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use BitApps\Integrations\Log\LogHandler; @@ -18,6 +18,17 @@ */ class ZohoCampaignsController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohocampaigns', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -26,58 +37,6 @@ public function __construct($integrationID) // $this->_logResponse = new UtilApiResponse(); } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to generate token - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh crm modules - * - * @param object $queryParams Params to fetch campaign list - * - * @return JSON crm module data - */ public static function refreshLists($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoCreator/Routes.php b/backend/Actions/ZohoCreator/Routes.php index 563f7253c..71081eab8 100644 --- a/backend/Actions/ZohoCreator/Routes.php +++ b/backend/Actions/ZohoCreator/Routes.php @@ -7,19 +7,13 @@ use BitApps\Integrations\Actions\ZohoCreator\ZohoCreatorController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zcreator_generate_token', [ZohoCreatorController::class, 'generateTokens']); Route::post('zcreator_refresh_applications', [ZohoCreatorController::class, 'refreshApplicationsAjaxHelper']); Route::post('zcreator_refresh_forms', [ZohoCreatorController::class, 'refreshFormsAjaxHelper']); Route::post('zcreator_refresh_fields', [ZohoCreatorController::class, 'refreshFieldsAjaxHelper']); -Route::post('zcreator_refresh_owners', [ZohoCreatorController::class, 'refreshTicketOwnersAjaxHelper']); -Route::post('zcreator_refresh_products', [ZohoCreatorController::class, 'refreshProductsAjaxHelper']); // public static function registerAjax() // { -// add_action('wp_ajax_zcreator_generate_token', array(__CLASS__, 'generateTokens')); // add_action('wp_ajax_zcreator_refresh_applications', array(__CLASS__, 'refreshApplicationsAjaxHelper')); // add_action('wp_ajax_zcreator_refresh_forms', array(__CLASS__, 'refreshFormsAjaxHelper')); // add_action('wp_ajax_zcreator_refresh_fields', array(__CLASS__, 'refreshFieldsAjaxHelper')); -// add_action('wp_ajax_zcreator_refresh_owners', array(__CLASS__, 'refreshTicketOwnersAjaxHelper')); -// add_action('wp_ajax_zcreator_refresh_products', array(__CLASS__, 'refreshProductsAjaxHelper')); // } diff --git a/backend/Actions/ZohoCreator/ZohoCreatorController.php b/backend/Actions/ZohoCreator/ZohoCreatorController.php index a48533b7e..70a04c44d 100644 --- a/backend/Actions/ZohoCreator/ZohoCreatorController.php +++ b/backend/Actions/ZohoCreator/ZohoCreatorController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoCreator; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Core\Util\IpTool; @@ -14,6 +15,18 @@ */ class ZohoCreatorController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohocreator', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'accountOwner' => 'accountOwner', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -21,51 +34,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param mixed $requestsParams - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function refreshApplicationsAjaxHelper($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoDesk/Routes.php b/backend/Actions/ZohoDesk/Routes.php index 99e7bfd6f..caffa7a30 100644 --- a/backend/Actions/ZohoDesk/Routes.php +++ b/backend/Actions/ZohoDesk/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ZohoDesk\ZohoDeskController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zdesk_generate_token', [ZohoDeskController::class, 'generateTokens']); Route::post('zdesk_refresh_organizations', [ZohoDeskController::class, 'refreshOrganizations']); Route::post('zdesk_refresh_departments', [ZohoDeskController::class, 'refreshDepartments']); Route::post('zdesk_refresh_fields', [ZohoDeskController::class, 'refreshFields']); diff --git a/backend/Actions/ZohoDesk/ZohoDeskController.php b/backend/Actions/ZohoDesk/ZohoDeskController.php index 9980abf04..17d07d15a 100644 --- a/backend/Actions/ZohoDesk/ZohoDeskController.php +++ b/backend/Actions/ZohoDesk/ZohoDeskController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoDesk; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; @@ -15,6 +16,17 @@ */ class ZohoDeskController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohodesk', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -22,51 +34,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param mixed $requestsParams - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function refreshOrganizations($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoMarketingHub/Routes.php b/backend/Actions/ZohoMarketingHub/Routes.php index 6d88c08e5..e59a9827d 100644 --- a/backend/Actions/ZohoMarketingHub/Routes.php +++ b/backend/Actions/ZohoMarketingHub/Routes.php @@ -7,6 +7,5 @@ use BitApps\Integrations\Actions\ZohoMarketingHub\ZohoMarketingHubController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zmarketingHub_generate_token', [ZohoMarketingHubController::class, 'generateTokens']); Route::post('zmarketingHub_refresh_lists', [ZohoMarketingHubController::class, 'refreshLists']); Route::post('zmarketingHub_refresh_contact_fields', [ZohoMarketingHubController::class, 'refreshContactFields']); diff --git a/backend/Actions/ZohoMarketingHub/ZohoMarketingHubController.php b/backend/Actions/ZohoMarketingHub/ZohoMarketingHubController.php index a4c309b6f..003dd36d1 100644 --- a/backend/Actions/ZohoMarketingHub/ZohoMarketingHubController.php +++ b/backend/Actions/ZohoMarketingHub/ZohoMarketingHubController.php @@ -6,8 +6,8 @@ namespace BitApps\Integrations\Actions\ZohoMarketingHub; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; - use BitApps\Integrations\Flow\FlowController; use WP_Error; @@ -16,6 +16,17 @@ */ class ZohoMarketingHubController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohomarketinghub', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -23,58 +34,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to generate token - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh crm modules - * - * @param object $queryParams Params to refresh lists - * - * @return JSON crm module data - */ public static function refreshLists($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoRecruit/Routes.php b/backend/Actions/ZohoRecruit/Routes.php index 9b09ce1be..b8eff2c75 100644 --- a/backend/Actions/ZohoRecruit/Routes.php +++ b/backend/Actions/ZohoRecruit/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ZohoRecruit\ZohoRecruitController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zrecruit_generate_token', [ZohoRecruitController::class, 'generateTokens']); Route::post('zrecruit_refresh_modules', [ZohoRecruitController::class, 'refreshModules']); Route::post('zrecruit_refresh_notetypes', [ZohoRecruitController::class, 'refreshNoteTypes']); Route::post('zrecruit_refresh_related_lists', [ZohoRecruitController::class, 'refreshRelatedModules']); diff --git a/backend/Actions/ZohoRecruit/ZohoRecruitController.php b/backend/Actions/ZohoRecruit/ZohoRecruitController.php index dedec4935..30a56b1d6 100644 --- a/backend/Actions/ZohoRecruit/ZohoRecruitController.php +++ b/backend/Actions/ZohoRecruit/ZohoRecruitController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\ZohoRecruit; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; @@ -15,6 +16,17 @@ */ class ZohoRecruitController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohorecruit', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -22,58 +34,6 @@ public function __construct($integrationID) $this->_integrationID = $integrationID; } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params to generate token - * - * @return JSON zoho recruit api response and status - */ - public static function generateTokens($requestsParams) - { - if (empty($requestsParams->{'accounts-server'}) - || empty($requestsParams->dataCenter) - || empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = urldecode($requestsParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => urldecode($requestsParams->redirectURI), - 'code' => $requestsParams->code - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - - /** - * Process ajax request for refresh recruit modules - * - * @param object $queryParams Params to refresh module - * - * @return JSON recruit module data - */ public static function refreshModules($queryParams) { if (empty($queryParams->tokenDetails) diff --git a/backend/Actions/ZohoSheet/Routes.php b/backend/Actions/ZohoSheet/Routes.php index 27b10c084..5f4aba860 100644 --- a/backend/Actions/ZohoSheet/Routes.php +++ b/backend/Actions/ZohoSheet/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\ZohoSheet\ZohoSheetController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('zohoSheet_generate_token', [ZohoSheetController::class, 'generateTokens']); Route::post('zohoSheet_fetch_all_work_books', [ZohoSheetController::class, 'getAllWorkbooks']); Route::post('zohoSheet_fetch_all_work_sheets', [ZohoSheetController::class, 'getAllWorksheets']); Route::post('zohoSheet_fetch_all_work_sheet_header', [ZohoSheetController::class, 'getWorksheetHeader']); diff --git a/backend/Actions/ZohoSheet/ZohoSheetController.php b/backend/Actions/ZohoSheet/ZohoSheetController.php index f22e232e6..b0bedc7a1 100644 --- a/backend/Actions/ZohoSheet/ZohoSheetController.php +++ b/backend/Actions/ZohoSheet/ZohoSheetController.php @@ -2,47 +2,30 @@ namespace BitApps\Integrations\Actions\ZohoSheet; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; class ZohoSheetController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'zohosheet', + 'fields' => [ + 'dataCenter' => 'dataCenter', + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $integrationID; public function __construct($integrationID) { $this->integrationID = $integrationID; } - - public static function generateTokens($requestParams) - { - if ( - empty($requestParams->{'accounts-server'}) || empty($requestParams->dataCenter) || empty($requestParams->clientId) - || empty($requestParams->clientSecret) || empty($requestParams->redirectURI) || empty($requestParams->code) - ) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $apiEndpoint = urldecode($requestParams->{'accounts-server'}) . '/oauth/v2/token'; - $requestParams = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestParams->clientId, - 'client_secret' => $requestParams->clientSecret, - 'redirect_uri' => urldecode($requestParams->redirectURI), - 'code' => $requestParams->code - ]; - - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, 400); - } - - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllWorkbooks($requestParams) { if ( diff --git a/backend/Core/Util/CredentialInjector.php b/backend/Core/Util/CredentialInjector.php index 424608dfc..fc91b038e 100644 --- a/backend/Core/Util/CredentialInjector.php +++ b/backend/Core/Util/CredentialInjector.php @@ -45,6 +45,16 @@ public static function inject(object $target, string $controllerClass): void [$targetProp, $keys] = $authKey; $obj = new \stdClass(); foreach ($keys as $key) { + if ($key === 'generates_on' && empty($authDetails[$key]) && !empty($authDetails['generated_at'])) { + $obj->$key = (int) $authDetails['generated_at']; + continue; + } + + if ($key === 'generated_at' && empty($authDetails[$key]) && !empty($authDetails['generates_on'])) { + $obj->$key = (int) $authDetails['generates_on']; + continue; + } + $obj->$key = $authDetails[$key] ?? ''; } $target->$targetProp = $obj; diff --git a/frontend/src/components/AllIntegrations/Keap/Keap.jsx b/frontend/src/components/AllIntegrations/Keap/Keap.jsx index a4b544f4a..f8a69edb9 100644 --- a/frontend/src/components/AllIntegrations/Keap/Keap.jsx +++ b/frontend/src/components/AllIntegrations/Keap/Keap.jsx @@ -1,11 +1,10 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { setGrantTokenResponse } from './KeapIntegrationHelpers' import KeapAuthorization from './KeapAuthorization' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import { handleInput, checkMappedFields } from './KeapCommonFunc' @@ -32,11 +31,6 @@ function Keap({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('keap') - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -113,4 +107,4 @@ function Keap({ formFields, setFlow, flow, allIntegURL }) { ) } -export default Keap \ No newline at end of file +export default Keap diff --git a/frontend/src/components/AllIntegrations/Keap/KeapAuthorization.jsx b/frontend/src/components/AllIntegrations/Keap/KeapAuthorization.jsx index 590ddfb97..a2ca82948 100644 --- a/frontend/src/components/AllIntegrations/Keap/KeapAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Keap/KeapAuthorization.jsx @@ -1,13 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize, refreshCustomFields } from './KeapCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshCustomFields } from './KeapCommonFunc' export default function KeapAuthorization({ formID, @@ -15,140 +11,65 @@ export default function KeapAuthorization({ setKeapConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) - - const handleInput = e => { - const newConf = { ...keapConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setKeapConf(newConf) - } - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - refreshCustomFields(formID, keapConf, setKeapConf, setIsLoading, setSnackbar) - setstep(2) - } - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- + const loadCustomFields = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...keapConf, connection_id: connectionId } : keapConf + refreshCustomFields(formID, nextConf, setKeapConf, setIsLoading, setSnackbar) + }, + [formID, keapConf, setKeapConf, setIsLoading, setSnackbar] + ) -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !keapConf?.customFields) { + loadCustomFields() + } - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Get Keap client id and secret', 'bit-integrations')} - - + setstep(value) + }, + [keapConf?.customFields, loadCustomFields, setstep] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const note = `

${__('Get Keap client id and secret', 'bit-integrations')}

+
    +
  • ${__('Go to Keap developer apps page.', 'bit-integrations')}
  • +
  • ${__('Create or open app and copy Client ID and Client Secret.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js b/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js index c0216b1bd..8ff5947dd 100644 --- a/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js @@ -1,7 +1,16 @@ -import { __, sprintf } from '../../../Utils/i18nwrap' +import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' import { contactFields } from './staticData' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const handleInput = ( e, keapConf, @@ -23,116 +32,6 @@ export const handleInput = ( setKeapConf({ ...newConf }) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const apiEndpoint = `https://accounts.infusionsoft.com/app/oauth/authorize?scope=full&access_type=offline&prompt=consent&response_type=code&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}&client_id=${confTmp.clientId}` - const authWindow = window.open(apiEndpoint, 'keap', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsGoogleSheet = localStorage.getItem('__keap') - if (bitsGoogleSheet) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsGoogleSheet) - localStorage.removeItem('__keap') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi - ) - } - } - }, 500) -} - -const tokenHelper = ( - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar, - btcbi -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - bitsFetch(tokenRequestParams, 'keap_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = keapConf => { const mappedFleld = keapConf.field_map ? keapConf.field_map.filter(mapped => !mapped.formField && !mapped.keapField) @@ -155,9 +54,7 @@ export const getAllTags = (confTmp, setConf, setLoading) => { setLoading({ ...setLoading, tags: true }) const requestParams = { - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - tokenDetails: confTmp.tokenDetails + ...buildAuthRequestParams(confTmp), } bitsFetch(requestParams, 'keap_fetch_all_tags').then(result => { @@ -188,9 +85,7 @@ export const refreshCustomFields = (id, confTmp, setConf, setIsLoading, setSnack const requestParams = { id: id, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, - tokenDetails: confTmp.tokenDetails + ...buildAuthRequestParams(confTmp), } bitsFetch(requestParams, 'keap_fetch_all_custom_fields').then(result => { diff --git a/frontend/src/components/AllIntegrations/Keap/KeapIntegrationHelpers.js b/frontend/src/components/AllIntegrations/Keap/KeapIntegrationHelpers.js index d517fbd71..499e412fe 100644 --- a/frontend/src/components/AllIntegrations/Keap/KeapIntegrationHelpers.js +++ b/frontend/src/components/AllIntegrations/Keap/KeapIntegrationHelpers.js @@ -1,25 +1,6 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - export const addFieldMap = (i, confTmp, setConf, uploadFields, tab) => { const newConf = { ...confTmp } if (tab) { diff --git a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalytics.jsx b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalytics.jsx index cd46bf9a2..bd5ac5c8b 100644 --- a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalytics.jsx +++ b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalytics.jsx @@ -7,7 +7,7 @@ import Steps from '../../Utilities/Steps' import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoAnalyticsAuthorization from './ZohoAnalyticsAuthorization' -import { handleInput, setGrantTokenResponse } from './ZohoAnalyticsCommonFunc' +import { handleInput } from './ZohoAnalyticsCommonFunc' import ZohoAnalyticsIntegLayout from './ZohoAnalyticsIntegLayout' export default function ZohoAnalytics({ formFields, setFlow, flow, allIntegURL }) { @@ -23,10 +23,6 @@ export default function ZohoAnalytics({ formFields, setFlow, flow, allIntegURL } actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse() - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsAuthorization.jsx index 68ede7a52..abff46145 100644 --- a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsAuthorization.jsx @@ -1,12 +1,9 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import { checkValidEmail } from '../../../Utils/Helpers' -import CopyText from '../../Utilities/CopyText' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize, refreshWorkspaces } from './ZohoAnalyticsCommonFunc' -import BackIcn from '../../../Icons/BackIcn' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshWorkspaces } from './ZohoAnalyticsCommonFunc' export default function ZohoAnalyticsAuthorization({ formID, @@ -14,181 +11,93 @@ export default function ZohoAnalyticsAuthorization({ setAnalyticsConf, step, setStep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ - dataCenter: '', - clientId: '', - clientSecret: '', - ownerEmail: '' - }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - if (!checkValidEmail(analyticsConf.ownerEmail)) { - setError({ - ownerEmail: !checkValidEmail(analyticsConf.ownerEmail) - ? __('Email is invalid', 'bit-integrations') - : '' - }) - return - } - setStep(2) - refreshWorkspaces(formID, analyticsConf, setAnalyticsConf, setIsLoading, setSnackbar) - } + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] - const handleInput = e => { - const newConf = { ...analyticsConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setAnalyticsConf(newConf) - } + const scopes = + 'ZohoAnalytics.metadata.read,ZohoAnalytics.data.read,ZohoAnalytics.data.create,ZohoAnalytics.data.update,ZohoAnalytics.usermanagement.read,ZohoAnalytics.share.create' - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadWorkspaces = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...analyticsConf, connection_id: connectionId } + : analyticsConf + refreshWorkspaces(formID, nextConf, setAnalyticsConf, setIsLoading, setSnackbar) + }, + [analyticsConf, formID, setAnalyticsConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && !analyticsConf?.default?.workspaces) { + loadWorkspaces() + } + setStep(value) + }, + [analyticsConf?.default?.workspaces, loadWorkspaces, setStep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
-
- {__('Zoho Analytics Owner Email:', 'bit-integrations')} -
- -
{error.ownerEmail}
+ const note = `

${__('Zoho Analytics OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Add workspace owner email in connection form.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js index 2249317fd..4de455bf4 100644 --- a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js @@ -1,127 +1,16 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' -import { checkValidEmail } from '../../../Utils/Helpers' - -export const setGrantTokenResponse = () => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem('__zohoAnalytics', JSON.stringify(grantTokenResponse)) - window.close() -} -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.dataCenter || !confTmp.clientId || !confTmp.clientSecret) { - setError({ - dataCenter: !confTmp.dataCenter ? __("Data center can't be empty", 'bit-integrations') : '', - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - if (!checkValidEmail(confTmp.ownerEmail)) { - setError({ - ownerEmail: !checkValidEmail(confTmp.ownerEmail) ? __('Email is invalid', 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = - 'ZohoAnalytics.metadata.read,ZohoAnalytics.data.read,ZohoAnalytics.data.create,ZohoAnalytics.data.update,ZohoAnalytics.usermanagement.read,ZohoAnalytics.share.create' - const apiEndpoint = `https://accounts.zoho.${ - confTmp.dataCenter - }/oauth/v2/auth?scope=${scopes}&response_type=code&client_id=${ - confTmp.clientId - }&prompt=Consent&access_type=offline&redirect_uri=${encodeURIComponent(window.location.href)}/redirect` - const authWindow = window.open(apiEndpoint, 'zohoAnalytics', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitformsZoho = localStorage.getItem('__zohoAnalytics') - if (bitformsZoho) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitformsZoho) - localStorage.removeItem('__zohoAnalytics') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - grantTokenResponse['accounts-server'] = decodeURIComponent(grantTokenResponse['accounts-server']) - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setisAuthorized, setIsLoading, setSnackbar) +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails } - } - }, 500) -} -const tokenHelper = (grantToken, confTmp, setConf, setisAuthorized, setIsLoading, setSnackbar) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.dataCenter = confTmp.dataCenter - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${encodeURIComponent(window.location.href)}/redirect` - bitsFetch(tokenRequestParams, 'zanalytics_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} +import { checkValidEmail } from '../../../Utils/Helpers' export const handleInput = (e, analyticsConf, setAnalyticsConf, formID, setIsLoading, setSnackbar) => { let newConf = { ...analyticsConf } @@ -185,6 +74,7 @@ export const refreshWorkspaces = ( const refreshModulesRequestParams = { formID, id: analyticsConf.id, + ...buildAuthRequestParams(analyticsConf), dataCenter: analyticsConf.dataCenter, clientId: analyticsConf.clientId, clientSecret: analyticsConf.clientSecret, @@ -232,6 +122,7 @@ export const refreshUsers = (formID, analyticsConf, setAnalyticsConf, setIsLoadi const refreshUsersRequestParams = { formID, id: analyticsConf.id, + connection_id: analyticsConf.connection_id, dataCenter: analyticsConf.dataCenter, clientId: analyticsConf.clientId, clientSecret: analyticsConf.clientSecret, @@ -284,6 +175,7 @@ export const refreshTables = (formID, analyticsConf, setAnalyticsConf, setIsLoad const refreshTablesRequestParams = { formID, workspace, + connection_id: analyticsConf.connection_id, dataCenter: analyticsConf.dataCenter, clientId: analyticsConf.clientId, clientSecret: analyticsConf.clientSecret, @@ -334,10 +226,9 @@ export const refreshTableHeaders = ( formID, workspace, table, + connection_id: analyticsConf.connection_id, dataCenter: analyticsConf.dataCenter, - clientId: analyticsConf.clientId, - clientSecret: analyticsConf.clientSecret, - tokenDetails: analyticsConf.tokenDetails, + ownerEmail: analyticsConf.ownerEmail } bitsFetch(refreshTableHeadersRequestParams, 'zanalytics_refresh_table_headers') diff --git a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBigin.jsx b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBigin.jsx index b62fd30db..873c2c76c 100644 --- a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBigin.jsx +++ b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBigin.jsx @@ -5,7 +5,7 @@ import { __ } from '../../../Utils/i18nwrap' import 'react-multiple-select-dropdown-lite/dist/index.css' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveActionConf, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import { checkMappedFields, handleInput } from './ZohoBiginCommonFunc' import ZohoBiginIntegLayout from './ZohoBiginIntegLayout' @@ -25,7 +25,6 @@ function ZohoBigin({ allIntegURL }) { const formFields = useRecoilValue($formFields) useEffect(() => { - window.opener && setGrantTokenResponse('zohoBigin') setBiginConf({ name: 'Zoho Bigin', type: 'Zoho Bigin', diff --git a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginAuthorization.jsx index 37eb5f680..26d95da68 100644 --- a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { refreshModules } from './ZohoBiginCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshModules } from './ZohoBiginCommonFunc' export default function ZohoBiginAuthorization({ formID, @@ -16,164 +11,84 @@ export default function ZohoBiginAuthorization({ setBiginConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] + const scopes = 'ZohoBigin.settings.modules.READ,ZohoBigin.settings.fields.READ,ZohoBigin.settings.tags.READ,ZohoBigin.users.READ,ZohoBigin.modules.ALL,ZohoBigin.settings.layouts.READ' -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshModules(formID, biginConf, setBiginConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...biginConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setBiginConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadModules = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...biginConf, connection_id: connectionId } : biginConf + refreshModules(formID, nextConf, setBiginConf, setIsLoading, setSnackbar) + }, + [biginConf, formID, setBiginConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2) { + loadModules() + } + setstep(value) + }, + [loadModules, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho Bigin OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Choose right data center for your account.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginCommonFunc.js index 4275adc4a..03121d33f 100644 --- a/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoBigin/ZohoBiginCommonFunc.js @@ -1,6 +1,16 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = ( e, recordTab, @@ -95,10 +105,8 @@ export const refreshModules = (formID, biginConf, setBiginConf, setIsLoading, se const refreshModulesRequestParams = { formID, id: biginConf.id, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(refreshModulesRequestParams, 'zbigin_refresh_modules') .then(result => { @@ -142,10 +150,8 @@ export const refreshPipelinesLayout = (formID, biginConf, setBiginConf, setIsLoa const refreshLayoutRequestParams = { formID, id: biginConf.id, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(refreshLayoutRequestParams, 'zbigin_refresh_playouts') .then(result => { @@ -192,10 +198,8 @@ export const refreshRelatedList = (formID, biginConf, setBiginConf, setIsLoading const relatedListRequestParams = { formID, module: biginConf.module, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(relatedListRequestParams, 'zbigin_refresh_related_lists') .then(result => { @@ -240,10 +244,8 @@ export const getFields = (recordTab, formID, biginConf, setBiginConf, setIsLoadi const getFieldsRequestParams = { formID, module, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(getFieldsRequestParams, 'zbigin_refresh_fields') .then(result => { @@ -296,10 +298,8 @@ export const refreshTags = (recordTab, formID, biginConf, setBiginConf, setIsLoa const getTagsRequestParams = { formID, module, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(getTagsRequestParams, 'zbigin_refresh_tags') .then(result => { @@ -332,10 +332,8 @@ export const refreshUsers = (formID, biginConf, setBiginConf, setIsLoading, setS setIsLoading(true) const getUsersRequestParams = { formID, + ...buildAuthRequestParams(biginConf), dataCenter: biginConf.dataCenter, - clientId: biginConf.clientId, - clientSecret: biginConf.clientSecret, - tokenDetails: biginConf.tokenDetails } bitsFetch(getUsersRequestParams, 'zbigin_refresh_users') .then(result => { diff --git a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx index 4da3d0489..9c3145055 100644 --- a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRM.jsx @@ -1,11 +1,11 @@ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveActionConf, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoCRMAuthorization from './ZohoCRMAuthorization' import { checkMappedFields, handleInput } from './ZohoCRMCommonFunc' @@ -31,10 +31,6 @@ function ZohoCRM({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoCRM') - }, []) - const saveConfig = () => { saveActionConf({ flow, diff --git a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx index 849c8cacc..3dfdc4def 100644 --- a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMAuthorization.jsx @@ -1,13 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import CopyText from '../../Utilities/CopyText' -import LoaderSm from '../../Loaders/LoaderSm' import { refreshModules } from './ZohoCRMCommonFunc' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { $appConfigState } from '../../../GlobalStates' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' export default function ZohoCRMAuthorization({ formID, @@ -15,163 +11,86 @@ export default function ZohoCRMAuthorization({ setCrmConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const scopes = 'ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.users.Read,zohocrm.files.CREATE' - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] - setstep(2) - !crmConf.module && refreshModules(formID, crmConf, setCrmConf, setIsLoading, setSnackbar) - } - const handleInput = e => { - const newConf = { ...crmConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCrmConf(newConf) - } + const scopes = 'ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.users.Read,zohocrm.files.CREATE' - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadModules = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...crmConf, connection_id: connectionId } : crmConf + if (!nextConf.module) { + refreshModules(formID, nextConf, setCrmConf, setIsLoading, setSnackbar) + } + }, + [crmConf, formID, setCrmConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && !crmConf.module) { + loadModules() + } + setstep(value) + }, + [crmConf.module, loadModules, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho CRM OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Choose data center matching your Zoho account.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js index fbe9e44b7..306d2e36b 100644 --- a/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoCRM/ZohoCRMCommonFunc.js @@ -1,6 +1,16 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = ( e, recordTab, @@ -137,10 +147,8 @@ export const refreshModules = (formID, crmConf, setCrmConf, setIsLoading, setSna const refreshModulesRequestParams = { formID, id: crmConf.id, + ...buildAuthRequestParams(crmConf), dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, - tokenDetails: crmConf.tokenDetails } bitsFetch(refreshModulesRequestParams, 'zcrm_refresh_modules') .then(result => { @@ -187,10 +195,8 @@ export const refreshLayouts = (recordTab, formID, crmConf, setCrmConf, setIsLoad const refreshLayoutsRequestParams = { formID, module, + ...buildAuthRequestParams(newConf), dataCenter: newConf.dataCenter, - clientId: newConf.clientId, - clientSecret: newConf.clientSecret, - tokenDetails: newConf.tokenDetails } bitsFetch(refreshLayoutsRequestParams, 'zcrm_refresh_layouts') .then(result => { @@ -255,10 +261,8 @@ export const refreshRelatedList = (formID, crmConf, setCrmConf, setIsLoading, se const relatedListRequestParams = { formID, module: crmConf.module, + ...buildAuthRequestParams(crmConf), dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, - tokenDetails: crmConf.tokenDetails } bitsFetch(relatedListRequestParams, 'zcrm_get_related_lists') .then(result => { @@ -301,10 +305,8 @@ export const refreshTags = (recordTab, formID, crmConf, setCrmConf, setIsLoading const refreshTagsParams = { formID, module, + ...buildAuthRequestParams(crmConf), dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, - tokenDetails: crmConf.tokenDetails } bitsFetch(refreshTagsParams, 'zcrm_get_tags') .then(result => { @@ -343,10 +345,8 @@ export const refreshOwners = (formID, crmConf, setCrmConf, setIsLoading, setSnac setIsLoading(true) const getOwnersParams = { formID, + ...buildAuthRequestParams(crmConf), dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, - tokenDetails: crmConf.tokenDetails } bitsFetch(getOwnersParams, 'zcrm_get_users') .then(result => { @@ -375,10 +375,8 @@ export const refreshAssigmentRules = (recordTab, crmConf, setCrmConf, setIsLoadi setIsLoading(true) const getAssigmentRulesParams = { module, + ...buildAuthRequestParams(crmConf), dataCenter: crmConf.dataCenter, - clientId: crmConf.clientId, - clientSecret: crmConf.clientSecret, - tokenDetails: crmConf.tokenDetails } bitsFetch(getAssigmentRulesParams, 'zcrm_get_assignment_rules') .then(result => { diff --git a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaigns.jsx b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaigns.jsx index 2fe8585b3..5a07307dd 100644 --- a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaigns.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaigns.jsx @@ -5,7 +5,7 @@ import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveIntegConfig, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoCampaignsAuthorization from './ZohoCampaignsAuthorization' import { checkMappedFields, handleInput } from './ZohoCampaignsCommonFunc' @@ -26,10 +26,6 @@ function ZohoCampaigns({ formFields, setFlow, flow, allIntegURL }) { field_map: [{ formField: '', zohoFormField: '' }] }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoCampaigns') - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsAuthorization.jsx index 57f3724b2..d85a45aa7 100644 --- a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import CopyText from '../../Utilities/CopyText' -import LoaderSm from '../../Loaders/LoaderSm' -import { refreshLists } from './ZohoCampaignsCommonFunc' -import BackIcn from '../../../Icons/BackIcn' -import { $appConfigState } from '../../../GlobalStates' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshLists } from './ZohoCampaignsCommonFunc' export default function ZohoCampaignsAuthorization({ formID, @@ -16,163 +11,85 @@ export default function ZohoCampaignsAuthorization({ setCampaignsConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) - const scopes = 'ZohoCampaigns.contact.READ,ZohoCampaigns.contact.CREATE,ZohoCampaigns.contact.UPDATE' -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshLists(formID, campaignsConf, setCampaignsConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...campaignsConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCampaignsConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const scopes = 'ZohoCampaigns.contact.READ,ZohoCampaigns.contact.CREATE,ZohoCampaigns.contact.UPDATE' - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...campaignsConf, connection_id: connectionId } + : campaignsConf + refreshLists(formID, nextConf, setCampaignsConf, setIsLoading, setSnackbar) + }, + [campaignsConf, formID, setCampaignsConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2) { + loadLists() + } + setstep(value) + }, + [loadLists, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho Campaigns OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Use account data center for auth endpoints.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsCommonFunc.js index 299641ca8..d71b4a2a5 100644 --- a/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoCampaigns/ZohoCampaignsCommonFunc.js @@ -1,6 +1,16 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = (e, formID, campaignsConf, setCampaignsConf, setIsLoading, setSnackbar) => { let newConf = { ...campaignsConf } newConf[e.target.name] = e.target.value @@ -30,10 +40,8 @@ export const refreshLists = (formID, campaignsConf, setCampaignsConf, setIsLoadi const refreshListsRequestParams = { formID, id: campaignsConf.id, + ...buildAuthRequestParams(campaignsConf), dataCenter: campaignsConf.dataCenter, - clientId: campaignsConf.clientId, - clientSecret: campaignsConf.clientSecret, - tokenDetails: campaignsConf.tokenDetails } bitsFetch(refreshListsRequestParams, 'zcampaigns_refresh_lists') .then(result => { @@ -84,10 +92,8 @@ export const refreshContactFields = ( const refreshContactFieldsRequestParams = { formID, list, + ...buildAuthRequestParams(campaignsConf), dataCenter: campaignsConf.dataCenter, - clientId: campaignsConf.clientId, - clientSecret: campaignsConf.clientSecret, - tokenDetails: campaignsConf.tokenDetails } bitsFetch(refreshContactFieldsRequestParams, 'zcampaigns_refresh_contact_fields') .then(result => { diff --git a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreator.jsx b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreator.jsx index d63321bbd..f70d84739 100644 --- a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreator.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreator.jsx @@ -5,7 +5,7 @@ import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveIntegConfig, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoCreatorAuthorization from './ZohoCreatorAuthorization' import { checkMappedFields, handleInput } from './ZohoCreatorCommonFunc' @@ -27,10 +27,6 @@ function ZohoCreator({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoCreator') - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorAuthorization.jsx index a6c5376c1..d2ef540b9 100644 --- a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorAuthorization.jsx @@ -1,11 +1,9 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize, refreshApplications } from './ZohoCreatorCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshApplications } from './ZohoCreatorCommonFunc' export default function ZohoCreatorAuthorization({ formID, @@ -13,179 +11,94 @@ export default function ZohoCreatorAuthorization({ setCreatorConf, step, setStep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ - dataCenter: '', - clientId: '', - clientSecret: '', - ownerEmail: '' - }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - if (!creatorConf.accountOwner) { - setError({ accountOwner: __('Account Owner Name is mandatory!', 'bit-integrations') }) - return - } - setStep(2) - if (!creatorConf.department) { - refreshApplications(formID, creatorConf, setCreatorConf, setIsLoading, setSnackbar) - } - } + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] - const handleInput = e => { - const newConf = { ...creatorConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setCreatorConf(newConf) - } + const scopes = + 'ZohoCreator.dashboard.READ,ZohoCreator.meta.application.READ,ZohoCreator.meta.form.READ,ZohoCreator.form.CREATE,ZohoCreator.report.CREATE,ZohoCreator.report.UPDATE' - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- + const loadApplications = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...creatorConf, connection_id: connectionId } : creatorConf -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + if (!nextConf.department) { + refreshApplications(formID, nextConf, setCreatorConf, setIsLoading, setSnackbar) + } + }, + [creatorConf, formID, setCreatorConf, setIsLoading, setSnackbar] + ) - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const handleSetStep = useCallback( + value => { + if (value === 2 && !creatorConf.department) { + loadApplications() + } -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ setStep(value) + }, + [creatorConf.department, loadApplications, setStep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
-
- {__('Owner Name (Your Zoho Creator screen name):', 'bit-integrations')} -
- handleInput(e, creatorConf, setCreatorConf)} - name="accountOwner" - value={creatorConf.accountOwner} - type="text" - placeholder={__('Your Zoho Creator screen name...', 'bit-integrations')} - disabled={isInfo} - /> -
{error.accountOwner}
+ const note = `

${__('Zoho Creator OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Set account screen name as Account Owner field.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorCommonFunc.js index 787222674..a1c2912c9 100644 --- a/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoCreator/ZohoCreatorCommonFunc.js @@ -1,120 +1,15 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' -export const setGrantTokenResponse = () => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails } - }) - } - localStorage.setItem('__zohoCreator', JSON.stringify(grantTokenResponse)) - window.close() -} -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.dataCenter || !confTmp.clientId || !confTmp.clientSecret) { - setError({ - dataCenter: !confTmp.dataCenter ? __("Data center can't be empty", 'bit-integrations') : '', - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - const scopes = - 'ZohoCreator.dashboard.READ,ZohoCreator.meta.application.READ,ZohoCreator.meta.form.READ,ZohoCreator.form.CREATE,ZohoCreator.report.CREATE,ZohoCreator.report.UPDATE' - const apiEndpoint = `https://accounts.zoho.${ - confTmp.dataCenter - }/oauth/v2/auth?scope=${scopes}&response_type=code&client_id=${ - confTmp.clientId - }&prompt=Consent&access_type=offline&redirect_uri=${encodeURIComponent(window.location.href)}/redirect` - const authWindow = window.open(apiEndpoint, 'zohoCreator', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitformsZoho = localStorage.getItem('__zohoCreator') - if (bitformsZoho) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitformsZoho) - localStorage.removeItem('__zohoCreator') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - grantTokenResponse['accounts-server'] = decodeURIComponent(grantTokenResponse['accounts-server']) - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setisAuthorized, setIsLoading, setSnackbar) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setisAuthorized, setIsLoading, setSnackbar) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.dataCenter = confTmp.dataCenter - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${encodeURIComponent(window.location.href)}/redirect` - bitsFetch(tokenRequestParams, 'zcreator_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} export const handleInput = ( e, @@ -185,10 +80,8 @@ export const refreshApplications = (formID, creatorConf, setCreatorConf, setIsLo const refreshApplicationsRequestParams = { formID, id: creatorConf.id, + ...buildAuthRequestParams(creatorConf), dataCenter: creatorConf.dataCenter, - clientId: creatorConf.clientId, - clientSecret: creatorConf.clientSecret, - tokenDetails: creatorConf.tokenDetails } bitsFetch(refreshApplicationsRequestParams, 'zcreator_refresh_applications') .then(result => { @@ -226,10 +119,8 @@ export const refreshForms = (formID, creatorConf, setCreatorConf, setIsLoading, const refreshFormsRequestParams = { formID, id: creatorConf.id, + ...buildAuthRequestParams(creatorConf), dataCenter: creatorConf.dataCenter, - clientId: creatorConf.clientId, - clientSecret: creatorConf.clientSecret, - tokenDetails: creatorConf.tokenDetails, accountOwner, applicationId } @@ -274,10 +165,8 @@ export const refreshFields = (formID, creatorConf, setCreatorConf, setIsLoading, setIsLoading(true) const refreshFieldsRequestParams = { formID, + ...buildAuthRequestParams(creatorConf), dataCenter: creatorConf.dataCenter, - clientId: creatorConf.clientId, - clientSecret: creatorConf.clientSecret, - tokenDetails: creatorConf.tokenDetails, accountOwner, applicationId, formId diff --git a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDesk.jsx b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDesk.jsx index cf4ded903..15bffa60f 100644 --- a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDesk.jsx +++ b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDesk.jsx @@ -6,7 +6,7 @@ import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveActionConf, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoDeskAuthorization from './ZohoDeskAuthorization' import { checkMappedFields, handleInput, refreshOrganizations } from './ZohoDeskCommonFunc' @@ -28,10 +28,6 @@ function ZohoDesk({ formFields, setFlow, flow, allIntegURL }) { field_map: [{ formField: '', zohoFormField: '' }], actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoDesk') - }, []) - const nextPage = val => { if (val === 3) { if (!checkMappedFields(deskConf)) { diff --git a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskAuthorization.jsx index 169b06c62..0c5281ff9 100644 --- a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { refreshOrganizations } from './ZohoDeskCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshOrganizations } from './ZohoDeskCommonFunc' export default function ZohoDeskAuthorization({ formID, @@ -16,164 +11,84 @@ export default function ZohoDeskAuthorization({ setDeskConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] + const scopes = 'Desk.settings.READ,Desk.basic.READ,Desk.search.READ,Desk.contacts.READ,Desk.contacts.CREATE,Desk.contacts.UPDATE,Desk.tickets.CREATE,Desk.tickets.UPDATE' - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshOrganizations(formID, deskConf, setDeskConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...deskConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setDeskConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadOrganizations = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...deskConf, connection_id: connectionId } : deskConf + refreshOrganizations(formID, nextConf, setDeskConf, setIsLoading, setSnackbar) + }, + [deskConf, formID, setDeskConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2) { + loadOrganizations() + } + setstep(value) + }, + [loadOrganizations, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho Desk OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Choose correct data center.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskCommonFunc.js index 284c717f0..eea64a48e 100644 --- a/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoDesk/ZohoDeskCommonFunc.js @@ -1,6 +1,16 @@ import { __, sprintf } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = ( e, deskConf, @@ -64,10 +74,8 @@ export const refreshOrganizations = (formID, deskConf, setDeskConf, setIsLoading const refreshOrganizationsRequestParams = { formID, id: deskConf.id, + ...buildAuthRequestParams(deskConf), dataCenter: deskConf.dataCenter, - clientId: deskConf.clientId, - clientSecret: deskConf.clientSecret, - tokenDetails: deskConf.tokenDetails } bitsFetch(refreshOrganizationsRequestParams, 'zdesk_refresh_organizations') .then(result => { @@ -107,10 +115,8 @@ export const refreshDepartments = (formID, deskConf, setDeskConf, setIsLoading, const refreshDepartmentsRequestParams = { formID, id: deskConf.id, + ...buildAuthRequestParams(deskConf), dataCenter: deskConf.dataCenter, - clientId: deskConf.clientId, - clientSecret: deskConf.clientSecret, - tokenDetails: deskConf.tokenDetails, orgId: deskConf.orgId } bitsFetch(refreshDepartmentsRequestParams, 'zdesk_refresh_departments') @@ -159,10 +165,8 @@ export const refreshFields = (formID, deskConf, setDeskConf, setIsLoading, setSn setIsLoading(true) const refreshFieldsRequestParams = { formID, + ...buildAuthRequestParams(deskConf), dataCenter: deskConf.dataCenter, - clientId: deskConf.clientId, - clientSecret: deskConf.clientSecret, - tokenDetails: deskConf.tokenDetails, orgId: deskConf.orgId } bitsFetch(refreshFieldsRequestParams, 'zdesk_refresh_fields') @@ -208,10 +212,8 @@ export const refreshOwners = (formID, deskConf, setDeskConf, setIsLoading, setSn const refreshOwnersRequestParams = { formID, id: deskConf.id, + ...buildAuthRequestParams(deskConf), dataCenter: deskConf.dataCenter, - clientId: deskConf.clientId, - clientSecret: deskConf.clientSecret, - tokenDetails: deskConf.tokenDetails, orgId: deskConf.orgId } bitsFetch(refreshOwnersRequestParams, 'zdesk_refresh_owners') @@ -255,10 +257,8 @@ export const refreshProducts = (formID, deskConf, setDeskConf, setIsLoading, set const refreshProductsRequestParams = { formID, id: deskConf.id, + ...buildAuthRequestParams(deskConf), dataCenter: deskConf.dataCenter, - clientId: deskConf.clientId, - clientSecret: deskConf.clientSecret, - tokenDetails: deskConf.tokenDetails, orgId: deskConf.orgId, departmentId: deskConf.department } diff --git a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHub.jsx b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHub.jsx index 71bd6be68..ff70a07f2 100644 --- a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHub.jsx +++ b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHub.jsx @@ -11,8 +11,7 @@ import ZohoMarketingHubAuthorization from './ZohoMarketingHubAuthorization' import { checkMappedFields, handleInput, - refreshLists, - setGrantTokenResponse + refreshLists } from './ZohoMarketingHubCommonFunc' import ZohoMarketingHubIntegLayout from './ZohoMarketingHubIntegLayout' @@ -31,10 +30,6 @@ function ZohoMarketingHub({ formFields, setFlow, flow, allIntegURL }) { field_map: [{ formField: '', zohoFormField: '' }] }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoMarketingHub') - }, []) - const nextPage = val => { if (val === 3) { if (!checkMappedFields(marketingHubConf)) { diff --git a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubAuthorization.jsx index c39725601..e8695a13c 100644 --- a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { refreshLists } from './ZohoMarketingHubCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshLists } from './ZohoMarketingHubCommonFunc' export default function ZohoMarketingAuthorization({ formID, @@ -16,163 +11,85 @@ export default function ZohoMarketingAuthorization({ setMarketingHubConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) - const scopes = 'ZohoMarketingHub.lead.READ,ZohoMarketingHub.lead.CREATE,ZohoMarketingHub.lead.UPDATE' -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshLists(formID, marketingHubConf, setMarketingHubConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...marketingHubConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMarketingHubConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const scopes = 'ZohoMarketingHub.lead.READ,ZohoMarketingHub.lead.CREATE,ZohoMarketingHub.lead.UPDATE' - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...marketingHubConf, connection_id: connectionId } + : marketingHubConf + refreshLists(formID, nextConf, setMarketingHubConf, setIsLoading, setSnackbar) + }, + [formID, marketingHubConf, setMarketingHubConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2) { + loadLists() + } + setstep(value) + }, + [loadLists, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho Marketing Hub OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Choose matching data center.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubCommonFunc.js index fd396c501..6e1d9d4df 100644 --- a/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoMarketingHub/ZohoMarketingHubCommonFunc.js @@ -1,6 +1,16 @@ import { __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = ( e, formID, @@ -51,10 +61,8 @@ export const refreshLists = ( const refreshListsRequestParams = { formID, id: marketingHubConf.id, + ...buildAuthRequestParams(marketingHubConf), dataCenter: marketingHubConf.dataCenter, - clientId: marketingHubConf.clientId, - clientSecret: marketingHubConf.clientSecret, - tokenDetails: marketingHubConf.tokenDetails } bitsFetch(refreshListsRequestParams, 'zmarketingHub_refresh_lists') .then(result => { @@ -106,10 +114,8 @@ export const refreshContactFields = ( const refreshContactFieldsRequestParams = { formID, list, + ...buildAuthRequestParams(marketingHubConf), dataCenter: marketingHubConf.dataCenter, - clientId: marketingHubConf.clientId, - clientSecret: marketingHubConf.clientSecret, - tokenDetails: marketingHubConf.tokenDetails } bitsFetch(refreshContactFieldsRequestParams, 'zmarketingHub_refresh_contact_fields') .then(result => { @@ -162,22 +168,3 @@ export const checkMappedFields = marketingHubConf => { return true } - -export const setGrantTokenResponse = () => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem('__zohoMarkatingHub', JSON.stringify(grantTokenResponse)) - window.close() -} diff --git a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruit.jsx b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruit.jsx index 39b885d82..171710786 100644 --- a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruit.jsx +++ b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruit.jsx @@ -5,7 +5,7 @@ import BackIcn from '../../../Icons/BackIcn' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' -import { saveIntegConfig, setGrantTokenResponse } from '../IntegrationHelpers/IntegrationHelpers' +import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoRecruitAuthorization from './ZohoRecruitAuthorization' import { checkMappedFields, handleInput } from './ZohoRecruitCommonFunc' @@ -29,10 +29,6 @@ function ZohoRecruit({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoRecruit') - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitAuthorization.jsx index 2b4d320bb..7f837ea92 100644 --- a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitAuthorization.jsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' -import { $appConfigState } from '../../../GlobalStates' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleAuthorize } from '../IntegrationHelpers/IntegrationHelpers' -import { refreshModules } from './ZohoRecruitCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshModules } from './ZohoRecruitCommonFunc' export default function ZohoRecruitAuthorization({ formID, @@ -16,163 +11,83 @@ export default function ZohoRecruitAuthorization({ setRecruitConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) - const scopes = 'ZohoRecruit.users.ALL,ZohoRecruit.modules.all' -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshModules(formID, recruitConf, setRecruitConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...recruitConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setRecruitConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const scopes = 'ZohoRecruit.users.ALL,ZohoRecruit.modules.all' - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadModules = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...recruitConf, connection_id: connectionId } : recruitConf + refreshModules(formID, nextConf, setRecruitConf, setIsLoading, setSnackbar) + }, + [formID, recruitConf, setRecruitConf, setIsLoading, setSnackbar] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2) { + loadModules() + } + setstep(value) + }, + [loadModules, setstep] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+ const note = `

${__('Zoho Recruit OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Choose account data center.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitCommonFunc.js index f212cc610..48bc00fde 100644 --- a/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoRecruit/ZohoRecruitCommonFunc.js @@ -1,6 +1,16 @@ import { sprintf, __ } from '../../../Utils/i18nwrap' import bitsFetch from '../../../Utils/bitsFetch' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + + export const handleInput = ( e, recordTab, @@ -95,10 +105,8 @@ export const refreshModules = (formID, recruitConf, setRecruitConf, setIsLoading const refreshModulesRequestParams = { formID, id: recruitConf.id, + ...buildAuthRequestParams(recruitConf), dataCenter: recruitConf.dataCenter, - clientId: recruitConf.clientId, - clientSecret: recruitConf.clientSecret, - tokenDetails: recruitConf.tokenDetails } bitsFetch(refreshModulesRequestParams, 'zrecruit_refresh_modules') .then(result => { @@ -142,10 +150,8 @@ export const refreshNoteTypes = (formID, recruitConf, setRecruitConf, setIsLoadi const refreshModulesRequestParams = { formID, id: recruitConf.id, + ...buildAuthRequestParams(recruitConf), dataCenter: recruitConf.dataCenter, - clientId: recruitConf.clientId, - clientSecret: recruitConf.clientSecret, - tokenDetails: recruitConf.tokenDetails } bitsFetch(refreshModulesRequestParams, 'zrecruit_refresh_notetypes') .then(result => { @@ -192,10 +198,8 @@ export const refreshRelatedList = (formID, recruitConf, setRecruitConf, setIsLoa const relatedListRequestParams = { formID, module: recruitConf.module, + ...buildAuthRequestParams(recruitConf), dataCenter: recruitConf.dataCenter, - clientId: recruitConf.clientId, - clientSecret: recruitConf.clientSecret, - tokenDetails: recruitConf.tokenDetails } bitsFetch(relatedListRequestParams, 'zrecruit_refresh_related_lists') .then(result => { @@ -241,10 +245,8 @@ const getFields = (recordTab, formID, recruitConf, setRecruitConf, setIsLoading, const getFieldsRequestParams = { formID, module, + ...buildAuthRequestParams(recruitConf), dataCenter: recruitConf.dataCenter, - clientId: recruitConf.clientId, - clientSecret: recruitConf.clientSecret, - tokenDetails: recruitConf.tokenDetails } bitsFetch(getFieldsRequestParams, 'zrecruit_get_fields') .then(result => { diff --git a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheet.jsx b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheet.jsx index e5d493cff..d68517dfd 100644 --- a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheet.jsx +++ b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheet.jsx @@ -1,6 +1,6 @@ /* eslint-disable no-console */ /* eslint-disable no-unused-expressions */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import { useNavigate } from 'react-router' import toast from 'react-hot-toast' import { __ } from '../../../Utils/i18nwrap' @@ -9,7 +9,7 @@ import Steps from '../../Utilities/Steps' import { saveIntegConfig } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import ZohoSheetAuthorization from './ZohoSheetAuthorization' -import { checkMappedFields, setGrantTokenResponse } from './ZohoSheetCommonFunc' +import { checkMappedFields } from './ZohoSheetCommonFunc' import ZohoSheetIntegLayout from './ZohoSheetIntegLayout' function ZohoSheet({ formFields, setFlow, flow, allIntegURL }) { @@ -41,10 +41,6 @@ function ZohoSheet({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - window.opener && setGrantTokenResponse('zohoSheet') - }, []) - const saveConfig = () => { setIsLoading(true) const resp = saveIntegConfig( diff --git a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetAuthorization.jsx b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetAuthorization.jsx index 35622d44b..8e6a86e90 100644 --- a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetAuthorization.jsx @@ -1,14 +1,9 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable no-unused-expressions */ -import { useState } from 'react' -import { useRecoilValue } from 'recoil' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { $appConfigState } from '../../../GlobalStates' -import { handleAuthorization } from './ZohoSheetCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { getAllWorkbooks } from './ZohoSheetCommonFunc' export default function ZohoSheetAuthorization({ zohoSheetConf, @@ -17,160 +12,85 @@ export default function ZohoSheetAuthorization({ setStep, loading, setLoading, - isInfo, - setSnackbar, - redirectLocation + isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const btcbi = useRecoilValue($appConfigState) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const dataCenterOptions = [ + { value: 'com', label: 'zoho.com' }, + { value: 'eu', label: 'zoho.eu' }, + { value: 'com.cn', label: 'zoho.com.cn' }, + { value: 'in', label: 'zoho.in' }, + { value: 'com.au', label: 'zoho.com.au' } + ] - !zohoSheetConf?.default - setStep(2) - } + const scope = 'ZohoSheet.dataAPI.READ,ZohoSheet.dataAPI.UPDATE' - const handleInput = e => { - const newConf = { ...zohoSheetConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setZohoSheetConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Data Center:', 'bit-integrations')} -
- -
{error.dataCenter}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- - - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Zoho API Console', 'bit-integrations')} - - + const loadWorkbooks = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...zohoSheetConf, connection_id: connectionId } + : zohoSheetConf + if (!nextConf?.workbooks?.length) { + getAllWorkbooks(nextConf, setZohoSheetConf, loading, setLoading) + } + }, + [loading, setLoading, setZohoSheetConf, zohoSheetConf] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const handleSetStep = useCallback( + value => { + if (value === 2 && !zohoSheetConf?.workbooks?.length) { + loadWorkbooks() + } + setStep(value) + }, + [loadWorkbooks, setStep, zohoSheetConf?.workbooks?.length] + ) -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
-
-
+ const note = `

${__('Zoho Sheet OAuth setup', 'bit-integrations')}

+
    +
  • ${__('Create app in Zoho API Console.', 'bit-integrations')}
  • +
  • ${__('Select account data center.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` - {!isInfo && ( -
- -
- -
- )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetCommonFunc.js index 53cce7f97..ef86acf1d 100644 --- a/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoSheet/ZohoSheetCommonFunc.js @@ -4,6 +4,15 @@ import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const handleInput = (e, zohoSheetConf, setZohoSheetConf) => { const newConf = { ...zohoSheetConf } const { name } = e.target @@ -33,9 +42,7 @@ export const checkMappedFields = zohoSheetConf => { export const getAllWorkbooks = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, workbooks: true }) const requestParams = { - tokenDetails: confTmp.tokenDetails, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, + ...buildAuthRequestParams(confTmp), dataCenter: confTmp.dataCenter } @@ -58,9 +65,7 @@ export const getAllWorkbooks = (confTmp, setConf, loading, setLoading) => { export const getAllWorksheets = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, worksheets: true }) const requestParams = { - tokenDetails: confTmp.tokenDetails, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, + ...buildAuthRequestParams(confTmp), dataCenter: confTmp.dataCenter, workbook: confTmp.selectedWorkbook } @@ -84,9 +89,7 @@ export const getAllWorksheets = (confTmp, setConf, loading, setLoading) => { export const getWorksheetHeader = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, header: true, workSheetHeaders: false }) const requestParams = { - tokenDetails: confTmp.tokenDetails, - clientId: confTmp.clientId, - clientSecret: confTmp.clientSecret, + ...buildAuthRequestParams(confTmp), dataCenter: confTmp.dataCenter, workbook: confTmp.selectedWorkbook, worksheet: confTmp.selectedWorksheet, @@ -108,119 +111,3 @@ export const getWorksheetHeader = (confTmp, setConf, loading, setLoading) => { toast.error(__(`${result.data}`, 'bit-integrations')) }) } - -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleAuthorization = ( - confTmp, - setConf, - setError, - setisAuthorized, - loading, - setLoading, - btcbi -) => { - if (!confTmp.dataCenter || !confTmp.clientId || !confTmp.clientSecret) { - setError({ - dataCenter: !confTmp.dataCenter ? __("Data center can't be empty") : '', - clientId: !confTmp.clientId ? __("Client ID can't be empty") : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty") : '' - }) - return - } - setLoading({ ...loading, auth: true }) - const scopes = 'ZohoSheet.dataAPI.READ,ZohoSheet.dataAPI.UPDATE' - const apiEndpoint = `https://accounts.zoho.${ - confTmp.dataCenter - }/oauth/v2/auth?scope=${scopes}&response_type=code&client_id=${ - confTmp.clientId - }&prompt=Consent&access_type=offline&state=${encodeURIComponent( - window.location.href - )}/redirect&redirect_uri=${encodeURIComponent(`${btcbi.api}/redirect`)}` - const authWindow = window.open(apiEndpoint, '__zohoSheet', 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const ZohoSheet = localStorage.getItem('__zohoSheet') - if (ZohoSheet) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(ZohoSheet) - localStorage.removeItem('__zohoSheet') - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - toast.error( - __( - `${__('Authorization Failed')} ${errorCause}. ${__('please try again')}`, - 'bit-integrations' - ) - ) - setLoading({ ...loading, auth: false }) - } else { - const newConf = { ...confTmp } - grantTokenResponse['accounts-server'] = decodeURIComponent(grantTokenResponse['accounts-server']) - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper(grantTokenResponse, newConf, setConf, setisAuthorized, loading, setLoading, btcbi) - } - } - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setisAuthorized, loading, setLoading, btcbi) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.dataCenter = confTmp.dataCenter - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = `${btcbi.api}/redirect` - bitsFetch(tokenRequestParams, 'zohoSheet_generate_token') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - getAllWorkbooks(newConf, setConf, loading, setLoading) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - __( - `${__('Authorization failed Cause:')}${result.data.data || result.data}. ${__( - 'please try again' - )}`, - 'bit-integrations' - ) - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setLoading({ ...loading, auth: false }) - }) -} From 63b45d6d985df8f4ad8ee6b45f1478d93095ddb7 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 10:56:11 +0600 Subject: [PATCH 44/58] feat: implement Mailup OAuth2 authorization flow and refactor related components --- backend/Actions/Mailup/MailupController.php | 47 ++--- backend/Actions/Mailup/Routes.php | 1 - .../AllIntegrations/Mailup/Mailup.jsx | 9 +- .../Mailup/MailupAuthorization.jsx | 165 ++++++------------ .../Mailup/MailupCommonFunc.js | 153 ++-------------- 5 files changed, 94 insertions(+), 281 deletions(-) diff --git a/backend/Actions/Mailup/MailupController.php b/backend/Actions/Mailup/MailupController.php index efc6ca541..f68c4de2d 100644 --- a/backend/Actions/Mailup/MailupController.php +++ b/backend/Actions/Mailup/MailupController.php @@ -2,12 +2,23 @@ namespace BitApps\Integrations\Actions\Mailup; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use BitApps\Integrations\Flow\FlowController; use WP_Error; class MailupController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'mailup', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on']], + ], + ]; + private $integrationID; public function __construct($integrationID) @@ -15,33 +26,6 @@ public function __construct($integrationID) $this->integrationID = $integrationID; } - public static function authorization($requestParams) - { - if (empty($requestParams->clientId) || empty($requestParams->clientSecret) || empty($requestParams->code)) { - wp_send_json_error(__('Requested parameter is empty', 'bit-integrations'), 400); - } - - $authCode = explode('-', $requestParams->code)[0]; - - $body = [ - 'grant_type' => 'authorization_code', - 'client_id' => $requestParams->clientId, - 'client_secret' => $requestParams->clientSecret, - 'code' => $authCode - ]; - - $apiEndpoint = 'https://services.mailup.com/Authorization/OAuth/Token'; - $header['Content-Type'] = 'application/x-www-form-urlencoded'; - $header['Authorization'] = 'Basic ' . base64_encode("{$requestParams->clientId}:{$requestParams->clientSecret}"); - $apiResponse = HttpHelper::post($apiEndpoint, $body, $header); - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error(empty($apiResponse->error_description) ? 'Unknown' : $apiResponse->error_description, 400); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - public static function getAllField($requestParams) { if (empty($requestParams->tokenDetails) || empty($requestParams->clientId) || empty($requestParams->clientSecret)) { @@ -176,15 +160,21 @@ protected static function tokenExpiryCheck($token, $clientId, $clientSecret) return false; } - if ((\intval($token->generates_on) + (55 * 60)) < time()) { + $generatedOn = !empty($token->generates_on) + ? \intval($token->generates_on) + : \intval($token->generated_at ?? 0); + + if (($generatedOn + (55 * 60)) < time()) { $refreshToken = self::refreshToken($token->refresh_token, $clientId, $clientSecret); if (is_wp_error($refreshToken) || !empty($refreshToken->error)) { return false; } $token->access_token = $refreshToken->access_token; + $token->refresh_token = $refreshToken->refresh_token ?? $token->refresh_token; $token->expires_in = $refreshToken->expires_in; $token->generates_on = $refreshToken->generates_on; + $token->generated_at = $refreshToken->generated_at; } return $token; @@ -206,6 +196,7 @@ protected static function refreshToken($refresh_token, $clientId, $clientSecret) } $token = $apiResponse; $token->generates_on = time(); + $token->generated_at = $token->generates_on; return $token; } diff --git a/backend/Actions/Mailup/Routes.php b/backend/Actions/Mailup/Routes.php index 1b4189f75..c146e0ca2 100644 --- a/backend/Actions/Mailup/Routes.php +++ b/backend/Actions/Mailup/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\Mailup\MailupController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('mailup_authorization', [MailupController::class, 'authorization']); Route::post('mailup_fetch_all_list', [MailupController::class, 'getAllList']); Route::post('mailup_fetch_all_group', [MailupController::class, 'getAllGroup']); Route::post('mailup_fetch_all_field', [MailupController::class, 'getAllField']); diff --git a/frontend/src/components/AllIntegrations/Mailup/Mailup.jsx b/frontend/src/components/AllIntegrations/Mailup/Mailup.jsx index b937f17c8..d62a9293c 100644 --- a/frontend/src/components/AllIntegrations/Mailup/Mailup.jsx +++ b/frontend/src/components/AllIntegrations/Mailup/Mailup.jsx @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import { useEffect, useState } from 'react' +import { useState } from 'react' import toast from 'react-hot-toast' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' @@ -9,7 +9,7 @@ import Steps from '../../Utilities/Steps' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import MailupAuthorization from './MailupAuthorization' -import { handleInput, setGrantTokenResponse, checkMappedFields } from './MailupCommonFunc' +import { handleInput, checkMappedFields } from './MailupCommonFunc' import MailupIntegLayout from './MailupIntegLayout' function Mailup({ formFields, setFlow, flow, allIntegURL }) { @@ -33,11 +33,6 @@ function Mailup({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('mailup') - }, []) - const nextPage = pageNo => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 diff --git a/frontend/src/components/AllIntegrations/Mailup/MailupAuthorization.jsx b/frontend/src/components/AllIntegrations/Mailup/MailupAuthorization.jsx index 4df8c25d4..1a9b253b8 100644 --- a/frontend/src/components/AllIntegrations/Mailup/MailupAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mailup/MailupAuthorization.jsx @@ -1,129 +1,72 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleMailupAuthorize, fetchAllList } from './MailupCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchAllList } from './MailupCommonFunc' export default function MailupAuthorization({ - formID, mailupConf, setMailupConf, step, setStep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setStep(2) - fetchAllList(mailupConf, setMailupConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...mailupConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailupConf(newConf) - } + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...mailupConf, connection_id: connectionId } : mailupConf + fetchAllList(nextConf, setMailupConf, setIsLoading, setSnackbar) + }, + [mailupConf, setMailupConf, setIsLoading, setSnackbar] + ) - return ( -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !mailupConf?.allList?.length) { + loadLists() + } -
- {__('Integration Name:', 'bit-integrations')} -
- + setStep(value) + }, + [loadLists, mailupConf?.allList?.length, setStep] + ) -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const note = `

${__('Get Mailup client id and secret', 'bit-integrations')}

+
    +
  • ${__('Go to Mailup developer settings and create or open your app.', 'bit-integrations')}
  • +
  • ${__('Copy Client ID and Client Secret.', 'bit-integrations')}
  • +
  • ${__('Use callback URL shown in connection form.', 'bit-integrations')}
  • +
` -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/Mailup/MailupCommonFunc.js b/frontend/src/components/AllIntegrations/Mailup/MailupCommonFunc.js index c91e5675d..dd9f3dafd 100644 --- a/frontend/src/components/AllIntegrations/Mailup/MailupCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailup/MailupCommonFunc.js @@ -1,18 +1,27 @@ /* eslint-disable no-console */ -import toast from 'react-hot-toast' import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' import { sprintf, __ } from '../../../Utils/i18nwrap' import { create } from 'mutative' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + tokenDetails: conf.tokenDetails, + clientId: conf.clientId, + clientSecret: conf.clientSecret + } + export const handleInput = (e, mailupConf, setMailupConf, setIsLoading, setSnackbar) => { const newConf = { ...mailupConf } - const { name } = e.target + const { name, value } = e.target - if (e.target.value !== '') { - newConf[name] = e.target.value - fetchAllGroup(newConf, setMailupConf, setIsLoading, setSnackbar) - fetchAllField(newConf, setMailupConf, setIsLoading, setSnackbar) + if (value !== '') { + newConf[name] = value + if (name === 'listId') { + fetchAllGroup(newConf, setMailupConf, setIsLoading, setSnackbar) + fetchAllField(newConf, setMailupConf, setIsLoading, setSnackbar) + } } else { delete newConf[name] } @@ -22,11 +31,7 @@ export const handleInput = (e, mailupConf, setMailupConf, setIsLoading, setSnack export const fetchAllList = (mailupConf, setMailupConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - tokenDetails: mailupConf.tokenDetails, - clientId: mailupConf.clientId, - clientSecret: mailupConf.clientSecret - } + const requestParams = buildAuthRequestParams(mailupConf) bitsFetch(requestParams, 'mailup_fetch_all_list') .then(result => { if (result && result.success) { @@ -52,11 +57,7 @@ export const fetchAllList = (mailupConf, setMailupConf, setIsLoading, setSnackba export const fetchAllField = (mailupConf, setMailupConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const requestParams = { - tokenDetails: mailupConf.tokenDetails, - clientId: mailupConf.clientId, - clientSecret: mailupConf.clientSecret - } + const requestParams = buildAuthRequestParams(mailupConf) bitsFetch(requestParams, 'mailup_fetch_all_field') .then(result => { if (result && result.success) { @@ -84,9 +85,7 @@ export const fetchAllField = (mailupConf, setMailupConf, setIsLoading, setSnackb export const fetchAllGroup = (mailupConf, setMailupConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - tokenDetails: mailupConf.tokenDetails, - clientId: mailupConf.clientId, - clientSecret: mailupConf.clientSecret, + ...buildAuthRequestParams(mailupConf), listId: mailupConf.listId } bitsFetch(requestParams, 'mailup_fetch_all_group') @@ -109,120 +108,6 @@ export const fetchAllGroup = (mailupConf, setMailupConf, setIsLoading, setSnackb .catch(() => setIsLoading(false)) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleMailupAuthorize = ( - integ, - confTmp, - setConf, - setError, - setIsAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.clientId) { - setError({ - clientId: !confTmp.clientId ? __("Client ID can't be empty", 'bit-integrations') : '' - }) - return - } - if (!confTmp.clientSecret) { - setError({ - clientSecret: !confTmp.clientSecret ? __("Client Secret can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const apiEndpoint = `https://services.mailup.com/Authorization/OAuth/LogOn?client_id=${ - confTmp.clientId - }&response_type=code&redirect_uri=${encodeURIComponent(window.location.href)}` - - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsMailup = localStorage.getItem(`__${integ}`) - - if (bitsMailup) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsMailup) - localStorage.removeItem(`__${integ}`) - - if (grantTokenResponse.token) { - grantTokenResponse.code = grantTokenResponse.token - } - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - tokenHelper(grantTokenResponse, newConf, setConf, setIsAuthorized, setIsLoading) - } - } - setIsLoading(false) - }, 500) -} - -const tokenHelper = (grantToken, confTmp, setConf, setIsAuthorized, setIsLoading) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - // eslint-disable-next-line no-undef - - bitsFetch(tokenRequestParams, 'mailup_authorization').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - ) - } else { - toast.error(__('Authorization failed. please try again', 'bit-integrations')) - } - setIsLoading(false) - }) -} - export const generateMappedField = mailupConf => { const requiredFlds = mailupConf?.staticFields.filter(fld => fld.required === true) return requiredFlds.length > 0 From a90627dd4b5425fd53bc97bd20745310da8377b9 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 11:10:23 +0600 Subject: [PATCH 45/58] feat: add OAuth1 authorization support and refactor Trello integration components --- backend/Actions/Trello/TrelloController.php | 14 +- .../Authorization/AuthorizationFactory.php | 4 + backend/Authorization/AuthorizationType.php | 2 + .../OAuth1/OAuth1Authorization.php | 50 ++ backend/controller/ConnectionController.php | 1 + frontend/src/Utils/connectionAuth.js | 4 +- .../AllIntegrations/Trello/Trello.jsx | 13 +- .../Trello/TrelloAuthorization.jsx | 169 +++---- .../Trello/TrelloCommonFunc.js | 109 +---- .../Connections/AddNewConnection.jsx | 6 +- .../Connections/Oauth1Connection.jsx | 458 ++++++++++++++++++ 11 files changed, 609 insertions(+), 221 deletions(-) create mode 100644 backend/Authorization/OAuth1/OAuth1Authorization.php create mode 100644 frontend/src/components/Connections/Oauth1Connection.jsx diff --git a/backend/Actions/Trello/TrelloController.php b/backend/Actions/Trello/TrelloController.php index 2d90f7c9b..16ac60336 100644 --- a/backend/Actions/Trello/TrelloController.php +++ b/backend/Actions/Trello/TrelloController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\Trello; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Config; use BitApps\Integrations\Core\Util\Hooks; use BitApps\Integrations\Core\Util\HttpHelper; @@ -16,11 +17,16 @@ */ class TrelloController { - private $baseUrl = 'https://api.trello.com/1/'; - - private $_integrationID; + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH1, + 'slug' => 'trello', + 'fields' => [ + 'clientId' => 'consumer_key', + 'accessToken' => 'access_token', + ], + ]; - private $accessToken; + private $baseUrl = 'https://api.trello.com/1/'; public function fetchAllBoards($queryParams) { diff --git a/backend/Authorization/AuthorizationFactory.php b/backend/Authorization/AuthorizationFactory.php index da0823f30..e9e41c259 100644 --- a/backend/Authorization/AuthorizationFactory.php +++ b/backend/Authorization/AuthorizationFactory.php @@ -9,6 +9,7 @@ use BitApps\Integrations\Authorization\ApiKey\ApiKeyAuthorization; use BitApps\Integrations\Authorization\Basic\BasicAuthorization; use BitApps\Integrations\Authorization\Bearer\BearerTokenAuthorization; +use BitApps\Integrations\Authorization\OAuth1\OAuth1Authorization; use BitApps\Integrations\Authorization\OAuth2\OAuth2Authorization; use Exception; @@ -31,6 +32,9 @@ public static function getAuthorizationHandler($type, $connectionId, $appSlug = case AuthorizationType::OAUTH2: return new OAuth2Authorization($connectionId); + case AuthorizationType::OAUTH1: + return new OAuth1Authorization($connectionId); + case AuthorizationType::CUSTOM: $class = self::authorizationClassExists($appSlug); diff --git a/backend/Authorization/AuthorizationType.php b/backend/Authorization/AuthorizationType.php index e3b57bded..a94882096 100644 --- a/backend/Authorization/AuthorizationType.php +++ b/backend/Authorization/AuthorizationType.php @@ -18,5 +18,7 @@ class AuthorizationType public const OAUTH2 = 'oauth2'; + public const OAUTH1 = 'oauth1'; + public const CUSTOM = 'custom'; } diff --git a/backend/Authorization/OAuth1/OAuth1Authorization.php b/backend/Authorization/OAuth1/OAuth1Authorization.php new file mode 100644 index 000000000..81526c005 --- /dev/null +++ b/backend/Authorization/OAuth1/OAuth1Authorization.php @@ -0,0 +1,50 @@ +getAuthDetails(); + + if (empty($authDetails) || empty($authDetails['access_token'])) { + return [ + 'error' => true, + 'message' => __('OAuth1 access token field is missing', 'bit-integrations'), + ]; + } + + return $authDetails['access_token']; + } + + public function getAuthHeadersOrParams() + { + $authDetails = $this->getAuthDetails(); + + if (empty($authDetails['consumer_key']) || empty($authDetails['access_token'])) { + return [ + 'error' => true, + 'message' => __('OAuth1 consumer key or access token is missing', 'bit-integrations'), + ]; + } + + $consumerKeyParam = $authDetails['consumer_key_param'] ?? ($authDetails['consumerKeyParam'] ?? 'oauth_consumer_key'); + $tokenParam = $authDetails['token_param'] ?? ($authDetails['tokenParam'] ?? 'oauth_token'); + $location = $authDetails['addTo'] ?? 'query'; + + return [ + 'authLocation' => $location, + 'data' => [ + $consumerKeyParam => $authDetails['consumer_key'], + $tokenParam => $authDetails['access_token'], + ], + ]; + } +} diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 87fe8c903..6e43282c5 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -25,6 +25,7 @@ final class ConnectionController AuthorizationType::API_KEY, AuthorizationType::BEARER_TOKEN, AuthorizationType::OAUTH2, + AuthorizationType::OAUTH1, AuthorizationType::CUSTOM, ]; diff --git a/frontend/src/Utils/connectionAuth.js b/frontend/src/Utils/connectionAuth.js index 19c1cdb23..f537de4d8 100644 --- a/frontend/src/Utils/connectionAuth.js +++ b/frontend/src/Utils/connectionAuth.js @@ -3,6 +3,7 @@ import { reauthorizeConnection, saveConnection } from './connectionApi' export const AUTH_TYPES = Object.freeze({ WP_PLUGIN_CHECK: 'wp_plugin_check', OAUTH2: 'oauth2', + OAUTH1: 'oauth1', API_KEY: 'api_key', BEARER_TOKEN: 'bearer_token', BASIC_AUTH: 'basic_auth', @@ -13,7 +14,8 @@ export const defaultEncryptKeys = { [AUTH_TYPES.API_KEY]: ['value'], [AUTH_TYPES.BASIC_AUTH]: ['password'], [AUTH_TYPES.BEARER_TOKEN]: ['token'], - [AUTH_TYPES.OAUTH2]: ['client_secret', 'access_token', 'refresh_token'] + [AUTH_TYPES.OAUTH2]: ['client_secret', 'access_token', 'refresh_token'], + [AUTH_TYPES.OAUTH1]: ['consumer_secret', 'access_token', 'access_token_secret'] } export const isWpPluginCheckType = authType => authType === AUTH_TYPES.WP_PLUGIN_CHECK diff --git a/frontend/src/components/AllIntegrations/Trello/Trello.jsx b/frontend/src/components/AllIntegrations/Trello/Trello.jsx index 09bed071d..a719f99b3 100644 --- a/frontend/src/components/AllIntegrations/Trello/Trello.jsx +++ b/frontend/src/components/AllIntegrations/Trello/Trello.jsx @@ -1,7 +1,6 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' -import { useNavigate, useParams } from 'react-router' -import BackIcn from '../../../Icons/BackIcn' +import { useNavigate } from 'react-router' import { __ } from '../../../Utils/i18nwrap' import SnackMsg from '../../Utilities/SnackMsg' import Steps from '../../Utilities/Steps' @@ -10,7 +9,6 @@ import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import TrelloAuthorization from './TrelloAuthorization' import { handleInput, - setGrantTokenResponse, checkMappedFields, generateMappedField } from './TrelloCommonFunc' @@ -18,7 +16,6 @@ import TrelloIntegLayout from './TrelloIntegLayout' function Trello({ formFields, setFlow, flow, allIntegURL }) { const navigate = useNavigate() - const { formID } = useParams() const [isLoading, setIsLoading] = useState(false) const [step, setstep] = useState(1) const [snack, setSnackbar] = useState({ show: false }) @@ -42,10 +39,6 @@ function Trello({ formFields, setFlow, flow, allIntegURL }) { actions: {} }) - useEffect(() => { - // eslint-disable-next-line no-unused-expressions - window.opener && setGrantTokenResponse('trello') - }, []) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -69,12 +62,10 @@ function Trello({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} diff --git a/frontend/src/components/AllIntegrations/Trello/TrelloAuthorization.jsx b/frontend/src/components/AllIntegrations/Trello/TrelloAuthorization.jsx index 4e9d173d8..43ee4c08a 100644 --- a/frontend/src/components/AllIntegrations/Trello/TrelloAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Trello/TrelloAuthorization.jsx @@ -1,127 +1,78 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleTrelloAuthorize, fetchAllBoard } from './TrelloCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { fetchAllBoard } from './TrelloCommonFunc' export default function TrelloAuthorization({ - formID, trelloConf, setTrelloConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - - setstep(2) - fetchAllBoard(formID, trelloConf, setTrelloConf, setIsLoading, setSnackbar) - } - - const handleInput = e => { - const newConf = { ...trelloConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setTrelloConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- + const loadBoards = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...trelloConf, connection_id: connectionId } : trelloConf + fetchAllBoard(nextConf, setTrelloConf, setIsLoading, setSnackbar) + }, + [setIsLoading, setSnackbar, setTrelloConf, trelloConf] + ) -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !trelloConf?.default?.allBoardlist?.length) { + loadBoards() + } - - {__('To get Client ID , Please Visit', 'bit-integrations')}{' '} - - {__('Trello API Console', 'bit-integrations')} - - + setstep(value) + }, + [loadBoards, setstep, trelloConf?.default?.allBoardlist?.length] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const note = `

${__('Get Trello OAuth details', 'bit-integrations')}

+
    +
  • ${__('Open Trello API key page and copy your API key.', 'bit-integrations')}
  • +
  • ${__('Use the callback/return URL from this form when generating your user token.', 'bit-integrations')}
  • +
  • ${__('Authorize and save the connection, then continue to map fields.', 'bit-integrations')}
  • +
` -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/Trello/TrelloCommonFunc.js b/frontend/src/components/AllIntegrations/Trello/TrelloCommonFunc.js index f60f4d774..c1526a2cd 100644 --- a/frontend/src/components/AllIntegrations/Trello/TrelloCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Trello/TrelloCommonFunc.js @@ -1,7 +1,14 @@ import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' import { sprintf, __ } from '../../../Utils/i18nwrap' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + accessToken: conf.accessToken + } + export const handleInput = (e, trelloConf, setTrelloConf, setIsLoading, setSnackbar) => { const newConf = { ...trelloConf } const { name } = e.target @@ -18,13 +25,9 @@ export const handleInput = (e, trelloConf, setTrelloConf, setIsLoading, setSnack setTrelloConf({ ...newConf }) } -export const fetchAllBoard = (formID, trelloConf, setTrelloConf, setIsLoading, setSnackbar) => { +export const fetchAllBoard = (trelloConf, setTrelloConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const fetchBoardModulesRequestParams = { - formID, - clientId: trelloConf.clientId, - accessToken: trelloConf.accessToken - } + const fetchBoardModulesRequestParams = buildAuthRequestParams(trelloConf) bitsFetch(fetchBoardModulesRequestParams, 'trello_fetch_all_board') .then(result => { if (result && result.success) { @@ -65,9 +68,8 @@ export const fetchAllBoard = (formID, trelloConf, setTrelloConf, setIsLoading, s export const fetchAllList = (trelloConf, setTrelloConf, setIsLoading, setSnackbar) => { setIsLoading(true) const fetchListModulesRequestParams = { - clientId: trelloConf.clientId, - boardId: trelloConf.boardId, - accessToken: trelloConf.accessToken + ...buildAuthRequestParams(trelloConf), + boardId: trelloConf.boardId } bitsFetch(fetchListModulesRequestParams, 'trello_fetch_all_list_Individual_board') @@ -107,9 +109,8 @@ export const fetchAllList = (trelloConf, setTrelloConf, setIsLoading, setSnackba export const fetchAllCustomFields = (trelloConf, setTrelloConf, setIsLoading, setSnackbar) => { setIsLoading(true) const requestParams = { - clientId: trelloConf.clientId, - boardId: trelloConf.boardId, - accessToken: trelloConf.accessToken + ...buildAuthRequestParams(trelloConf), + boardId: trelloConf.boardId } bitsFetch(requestParams, 'trello_fetch_all_custom_fields') @@ -143,88 +144,6 @@ export const fetchAllCustomFields = (trelloConf, setTrelloConf, setIsLoading, se .catch(() => setIsLoading(false)) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleTrelloAuthorize = ( - integ, - ajaxInteg, - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.clientId) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const apiEndpoint = `https://trello.com/1/authorize?expiration=never&name=MyPersonalToken&scope=read,write&response_type=token&key=${ - confTmp.clientId - }&return_url=${encodeURIComponent(window.location.href)}/&response_type=token` - - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsTrello = localStorage.getItem(`__${integ}`) - - if (bitsTrello) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsTrello) - localStorage.removeItem(`__${integ}`) - - if (grantTokenResponse.token) { - grantTokenResponse.code = grantTokenResponse.token - } - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization Failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accessToken = grantTokenResponse.code - setConf(newConf) - setisAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } - } - setIsLoading(false) - }, 500) -} export const generateMappedField = cardFields => { const requiredFlds = cardFields.filter(fld => fld.required === true) diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index 69b4bad77..d1998f37a 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -1,5 +1,6 @@ import { AUTH_TYPES } from "../../Utils/connectionAuth" import ApiConnection from "./ApiConnection" +import Oauth1Connection from "./Oauth1Connection" import Oauth2Connection from "./Oauth2Connection" export default function AddNewConnection(props) { @@ -7,6 +8,9 @@ export default function AddNewConnection(props) { return } + if (props?.authDetails?.authType === AUTH_TYPES.OAUTH1) { + return + } + return } - diff --git a/frontend/src/components/Connections/Oauth1Connection.jsx b/frontend/src/components/Connections/Oauth1Connection.jsx new file mode 100644 index 000000000..c316907c5 --- /dev/null +++ b/frontend/src/components/Connections/Oauth1Connection.jsx @@ -0,0 +1,458 @@ +import { useCallback, useMemo, useState } from 'react' +import toast from 'react-hot-toast' +import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' +import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' +import { getCallbackState, openOauthPopup } from '../../Utils/oauthHelper' +import { __ } from '../../Utils/i18nwrap' +import { APP_CONFIG } from '../../config/app' +import LoaderSm from '../Loaders/LoaderSm' +import CopyText from '../Utilities/CopyText' + +const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } + +const resolveTemplate = (template, data) => { + if (!template) return '' + return template.replace(/\{(\w+)\}/g, (_, key) => { + const val = data[key] + if (val == null) return '' + return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) + }) +} + +const appendQueryParam = (url, key, value) => { + if (value == null || value === '') return + url.searchParams.append(key, String(value)) +} + +const buildOauth1AuthUrl = (authEndpoint, extraParams = {}) => { + const url = new URL(authEndpoint.url) + const queryParams = authEndpoint.queryParams || {} + + Object.entries(queryParams).forEach(([key, value]) => appendQueryParam(url, key, value)) + Object.entries(extraParams).forEach(([key, value]) => appendQueryParam(url, key, value)) + + return url.toString() +} + +const normalizeAdditionalHeaders = headers => { + if (!headers || typeof headers !== 'object') return {} + + return Object.entries(headers).reduce((acc, [key, value]) => { + const normalizedKey = String(key || '').trim() + const normalizedValue = value == null ? '' : String(value).trim() + + if (normalizedKey && normalizedValue) { + acc[normalizedKey] = normalizedValue + } + + return acc + }, {}) +} + +const resolveHeaderTemplates = (headers, data) => { + if (!headers || typeof headers !== 'object') return {} + + return Object.entries(headers).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value + return acc + }, {}) +} + +const resolvePayloadTemplates = (payload, data) => { + if (Array.isArray(payload)) { + return payload.map(item => resolvePayloadTemplates(item, data)) + } + + if (payload && typeof payload === 'object') { + return Object.entries(payload).reduce((acc, [key, value]) => { + acc[key] = resolvePayloadTemplates(value, data) + return acc + }, {}) + } + + if (typeof payload === 'string') { + return resolveTemplate(payload, data) + } + + return payload +} + +const resolveConfigValue = (value, data) => { + if (typeof value === 'function') { + return value(data) + } + + return value +} + +const normalizePopupResponse = popupResponse => { + if (popupResponse && typeof popupResponse === 'object') return popupResponse + + if (typeof popupResponse === 'string') { + const parsed = {} + const source = popupResponse.replace(/^#/, '').replace(/^\?/, '') + const params = new URLSearchParams(source) + for (const [key, value] of params.entries()) { + if (value) parsed[key] = value + } + return parsed + } + + return {} +} + +const getOauth1Payload = ({ + authDetails, + formData, + accessToken, + accessTokenSecret, + consumerKeyParam, + tokenParam +}) => { + const resolvedApiEndpoint = resolveConfigValue(authDetails?.apiEndpoint, formData) + const resolvedHeaders = resolveConfigValue(authDetails?.headers, formData) + const resolvedPayload = resolveConfigValue(authDetails?.payload, formData) + const additionalHeaders = resolveHeaderTemplates( + normalizeAdditionalHeaders(resolvedHeaders), + formData + ) + const sslVerify = authDetails?.ssl_verify !== false + + const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (formData[name] != null) acc[name] = formData[name] + return acc + }, {}) + + const payload = { + auth_type: AUTH_TYPES.OAUTH1, + api_endpoint: resolveTemplate(resolvedApiEndpoint, formData), + method: authDetails?.method || 'GET', + auth_details: { + ...extraAuthDetails, + consumer_key: formData.clientId, + consumer_secret: formData.clientSecret || '', + access_token: accessToken, + access_token_secret: accessTokenSecret || '', + consumer_key_param: consumerKeyParam, + token_param: tokenParam, + addTo: authDetails?.addTo || 'query', + ssl_verify: sslVerify + }, + headers: additionalHeaders + } + + if (authDetails?.signatureMethod) { + payload.auth_details.signature_method = authDetails.signatureMethod + } + + if (resolvedPayload !== undefined) { + payload.payload = resolvePayloadTemplates(resolvedPayload, formData) + } + + return payload +} + +export default function Oauth1Connection({ + authDetails, + config, + setConfig, + isInfo = false, + customAuthFields, + onConnectionSaved +}) { + const [formData, setFormData] = useState({}) + const [errors, setErrors] = useState({}) + const [isLoading, setIsLoading] = useState(false) + const [isAuthorized, setIsAuthorized] = useState(false) + + const callbackUrl = useMemo( + () => authDetails?.callbackUrl || getCallbackState(), + [authDetails?.callbackUrl] + ) + + const resolvedAuthEndpoint = useMemo(() => { + if (!authDetails?.authCodeEndpoint?.url) return null + + return { + ...authDetails.authCodeEndpoint, + url: resolveTemplate(authDetails.authCodeEndpoint.url, formData) + } + }, [authDetails?.authCodeEndpoint, formData]) + + const requireClientSecret = authDetails?.requireClientSecret !== false + const consumerKeyParam = authDetails?.consumerKeyParam || 'oauth_consumer_key' + const tokenParam = authDetails?.tokenParam || 'oauth_token' + const responseTokenField = authDetails?.responseTokenField || tokenParam + const responseTokenSecretField = authDetails?.responseTokenSecretField || 'oauth_token_secret' + + const handleChange = useCallback(event => { + const { name, value } = event.target + setFormData(prev => ({ ...prev, [name]: value })) + setErrors(prev => ({ ...prev, [name]: '' })) + }, []) + + const validate = useCallback(() => { + const nextErrors = {} + const extraFields = authDetails?.extraFields || [] + + if (!formData.connectionName?.trim()) { + nextErrors.connectionName = __('Connection name is required', 'bit-integrations') + } + + if (!formData.clientId?.trim()) { + nextErrors.clientId = __('Client ID is required', 'bit-integrations') + } + + if (requireClientSecret && !formData.clientSecret?.trim()) { + nextErrors.clientSecret = __('Client secret is required', 'bit-integrations') + } + + extraFields.forEach(field => { + if (field.required && !formData[field.name]?.trim()) { + nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` + } + }) + + if (!resolvedAuthEndpoint?.url) { + nextErrors.authorize = __('OAuth1 authorization URL is required', 'bit-integrations') + } + + setErrors(nextErrors) + return Object.keys(nextErrors).length === 0 + }, [authDetails?.extraFields, formData, requireClientSecret, resolvedAuthEndpoint?.url]) + + const saveOauth1Connection = useCallback( + async payload => { + const saveRes = await saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: AUTH_TYPES.OAUTH1, + connection_name: formData.connectionName, + account_name: formData.connectionName, + auth_details: payload.auth_details, + encrypt_keys: authDetails?.encryptKeys || defaultEncryptKeys[AUTH_TYPES.OAUTH1] || [] + }) + + if (!saveRes?.success) { + const reason = saveRes?.data?.data || saveRes?.data || '' + toast.error(`${__('Failed to save connection Cause:', 'bit-integrations')}${reason}`) + return null + } + + const connection = saveRes?.data?.data || null + const persistedExtraFields = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (formData[name] != null) acc[name] = formData[name] + return acc + }, {}) + + setConfig(prev => ({ ...prev, connection_id: connection?.id, ...persistedExtraFields })) + + if (onConnectionSaved) await onConnectionSaved(connection) + + setIsAuthorized(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + return connection + }, + [authDetails?.encryptKeys, authDetails?.extraFields, config, formData, onConnectionSaved, setConfig] + ) + + const handleAuthorize = useCallback(async () => { + if (!validate()) return + + const declaredQueryParams = resolvedAuthEndpoint?.queryParams || {} + const queryParams = { ...declaredQueryParams } + const authExtraParams = {} + const callbackUrlParam = authDetails?.callbackUrlParam || '' + + if (!queryParams[consumerKeyParam]) { + authExtraParams[consumerKeyParam] = formData.clientId + } + + if (callbackUrlParam && !queryParams[callbackUrlParam]) { + authExtraParams[callbackUrlParam] = callbackUrl + } + + setIsLoading(true) + + try { + const authUrl = buildOauth1AuthUrl( + { + ...resolvedAuthEndpoint, + queryParams + }, + authExtraParams + ) + + const popupResponse = normalizePopupResponse( + await openOauthPopup(authUrl, authDetails?.authorizationWindowLabel || 'OAuth1') + ) + + if (popupResponse?.error) { + throw new Error( + popupResponse.error === 'popup_blocked' + ? __('Popup blocked. Please allow popups and try again.', 'bit-integrations') + : popupResponse.error_description || + __('Authorization window closed before completing.', 'bit-integrations') + ) + } + + const accessToken = + popupResponse?.[responseTokenField] || popupResponse?.[tokenParam] || popupResponse?.token || '' + const accessTokenSecret = + popupResponse?.[responseTokenSecretField] || popupResponse?.oauth_token_secret || '' + + if (!accessToken) { + throw new Error(__('Authorization token missing', 'bit-integrations')) + } + + const payload = getOauth1Payload({ + authDetails, + formData, + accessToken, + accessTokenSecret, + consumerKeyParam, + tokenParam + }) + + if (!authDetails?.skipAuthorizationCheck) { + const authorizeRes = await authorizeConnection(payload) + + if (!authorizeRes?.success) { + throw new Error( + authorizeRes?.data?.data?.message || + authorizeRes?.data?.message || + authorizeRes?.data?.data || + authorizeRes?.data || + __('Authorization failed', 'bit-integrations') + ) + } + } + + await saveOauth1Connection(payload) + } catch (error) { + setIsAuthorized(false) + toast.error(`${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}`) + } finally { + setIsLoading(false) + } + }, [ + authDetails, + callbackUrl, + consumerKeyParam, + formData, + resolvedAuthEndpoint, + responseTokenField, + responseTokenSecretField, + saveOauth1Connection, + tokenParam, + validate + ]) + + return ( + <> +
+ {__('Connection Name:', 'bit-integrations')} +
+ +
{errors.connectionName || ''}
+ + {(authDetails?.showCallbackInfo !== false) && ( + <> +
+ {__('Homepage URL:', 'bit-integrations')} +
+ +
+ {authDetails?.callbackLabel || __('Callback / Return URL:', 'bit-integrations')} +
+ + + )} + + {customAuthFields} + + {(authDetails?.extraFields || []).map(field => ( +
+
+ {field.label}: +
+ {field.type === 'select' ? ( + + ) : ( + + )} +
{errors[field.name] || ''}
+
+ ))} + +
+ {authDetails?.clientIdLabel || __('Client ID:', 'bit-integrations')} +
+ +
{errors.clientId || ''}
+ + {requireClientSecret && ( + <> +
+ {authDetails?.clientSecretLabel || __('Client Secret:', 'bit-integrations')} +
+ +
{errors.clientSecret || ''}
+ + )} + +
{errors.authorize || ''}
+ + + + ) +} From bf5a5d8b3b1fcaebdf8682ab782ce0eddd47e0d6 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 11:21:39 +0600 Subject: [PATCH 46/58] refactor: clean up MailChimp integration by removing unused code and simplifying authorization logic --- .../Actions/MailChimp/MailChimpController.php | 44 +-- .../AllIntegrations/KirimEmail/KirimEmail.jsx | 2 +- .../AllIntegrations/MailChimp/MailChimp.jsx | 4 +- .../MailChimp/MailChimpAuthorization.jsx | 268 ++++++------------ .../MailChimp/MailChimpCommonFunc.js | 97 +------ 5 files changed, 94 insertions(+), 321 deletions(-) diff --git a/backend/Actions/MailChimp/MailChimpController.php b/backend/Actions/MailChimp/MailChimpController.php index 39e1a8445..467d0a4fc 100644 --- a/backend/Actions/MailChimp/MailChimpController.php +++ b/backend/Actions/MailChimp/MailChimpController.php @@ -6,8 +6,6 @@ namespace BitApps\Integrations\Actions\MailChimp; -use BitApps\Integrations\Authorization\AuthorizationFactory; -use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -108,9 +106,6 @@ public static function generateTokens($requestsParams) $metaData = HttpHelper::post($metaDataEndPoint, null, $authorizationHeader); $apiResponse->dc = $metaData->dc; - $apiResponse->accountname = $metaData->accountname ?? null; - $apiResponse->login_email = $metaData->login->email ?? null; - $apiResponse->login_name = $metaData->login->login_name ?? null; if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { wp_send_json_error( @@ -277,11 +272,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; - $tokenDetails = self::resolveTokenDetails($integrationDetails); - if (is_wp_error($tokenDetails)) { - return $tokenDetails; - } - + $tokenDetails = $integrationDetails->tokenDetails; $listId = $integrationDetails->listId; $module = isset($integrationDetails->module) ? $integrationDetails->module : ''; $tags = $integrationDetails->tags; @@ -317,37 +308,4 @@ public function execute($integrationData, $fieldValues) return $mChimpApiResponse; } - - /** - * Resolve MailChimp tokenDetails for execution. - * - * Prefers the new Connection store when `connection_id` is present in flow_details; - * falls back to legacy inline tokenDetails for older flows. - */ - private static function resolveTokenDetails($integrationDetails) - { - $connectionId = isset($integrationDetails->connection_id) ? (int) $integrationDetails->connection_id : 0; - - if ($connectionId > 0) { - try { - $handler = AuthorizationFactory::getAuthorizationHandler(AuthorizationType::OAUTH2, $connectionId, 'MailChimp'); - $authDetails = $handler->getAuthDetails(); - } catch (\Exception $e) { - return new WP_Error('CONN_AUTH_FAIL', $e->getMessage()); - } - - if (isset($authDetails['error']) && $authDetails['error']) { - return new WP_Error('CONN_AUTH_FAIL', $authDetails['message'] ?? 'Connection authorization failed'); - } - - return (object) [ - 'access_token' => $authDetails['access_token'] ?? '', - 'dc' => $authDetails['dc'] ?? ($authDetails['userInfo']['dc'] ?? ''), - 'expires_in' => $authDetails['expires_in'] ?? null, - 'generated_at' => $authDetails['generated_at'] ?? null, - ]; - } - - return $integrationDetails->tokenDetails ?? null; - } } diff --git a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmail.jsx b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmail.jsx index 266cbb0fc..ec48833c9 100644 --- a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmail.jsx +++ b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmail.jsx @@ -34,7 +34,7 @@ function KirimEmail({ formFields, setFlow, flow, allIntegURL }) { name: 'Kirim Email', type: 'Kirim Email', mainAction: '', - userName: 'vaishak', + userName: '', api_key: '', field_map: [{ formField: '', kirimEmailFormField: '' }], subscriberFields, diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx index 0426a2f1e..d6126d1f1 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx @@ -26,8 +26,6 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { const [mailChimpConf, setMailChimpConf] = useState({ name: 'Mail Chimp', type: 'Mail Chimp', - connection_id: null, - connectionName: 'Mail Chimp Connection', clientId: '', clientSecret: '', listId: '', @@ -141,4 +139,4 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { ) } -export default MailChimp +export default MailChimp \ No newline at end of file diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx index ce21724c5..1bc151ebd 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx @@ -1,19 +1,11 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import BackIcn from '../../../Icons/BackIcn' -import { listConnections } from '../../../Utils/connectionApi' import { __ } from '../../../Utils/i18nwrap' -import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import ConnectionAccountSelect from '../../Connections/ConnectionAccountSelect' import LoaderSm from '../../Loaders/LoaderSm' import CopyText from '../../Utilities/CopyText' +import { handleMailChimpAuthorize, refreshAudience, refreshModules } from './MailChimpCommonFunc' +import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import TutorialLink from '../../Utilities/TutorialLink' -import { - applyConnectionToConf, - handleMailChimpAuthorize, - refreshModules -} from './MailChimpCommonFunc' - -const APP_SLUG = 'MailChimp' export default function MailChimpAuthorization({ formID, @@ -28,29 +20,7 @@ export default function MailChimpAuthorization({ isInfo }) { const [isAuthorized, setisAuthorized] = useState(false) - const [connections, setConnections] = useState([]) - const [showNewForm, setShowNewForm] = useState(false) - const [error, setError] = useState({ connectionName: '', clientId: '', clientSecret: '' }) - - useEffect(() => { - setIsLoading(true) - listConnections(APP_SLUG) - .then(res => { - if (res?.success && Array.isArray(res.data?.data)) { - setConnections(res.data.data) - if (mailChimpConf.connection_id) { - const match = res.data.data.find(c => c.id === mailChimpConf.connection_id) - if (match) setisAuthorized(true) - } else if (res.data.data.length === 0) { - setShowNewForm(true) - } - } else { - setShowNewForm(true) - } - }) - .finally(() => setIsLoading(false)) - }, []) - + const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -68,69 +38,6 @@ export default function MailChimpAuthorization({ setMailChimpConf(newConf) } - const handleSelectConnection = conn => { - if (!conn) { - setMailChimpConf(prev => ({ - ...prev, - connection_id: null, - tokenDetails: null, - clientId: '', - clientSecret: '', - connectionName: '' - })) - setisAuthorized(false) - setShowNewForm(false) - return - } - - applyConnectionToConf(conn, setMailChimpConf) - setisAuthorized(true) - setShowNewForm(false) - } - - const enterNewForm = () => { - setShowNewForm(true) - setisAuthorized(false) - setMailChimpConf(prev => ({ - ...prev, - connection_id: null, - clientId: '', - clientSecret: '', - tokenDetails: null, - connectionName: '' - })) - } - - const handleReauthorize = conn => { - setShowNewForm(true) - setMailChimpConf(prev => ({ - ...prev, - connection_id: conn.id, - connectionName: conn.connection_name || '', - clientId: conn.auth_details?.client_id || '', - clientSecret: conn.auth_details?.client_secret || '' - })) - setisAuthorized(false) - } - - const onAuthorizeClick = () => { - if (!mailChimpConf.connectionName?.trim()) { - setError({ ...error, connectionName: __("Connection name can't be empty", 'bit-integrations') }) - return - } - handleMailChimpAuthorize( - 'mailChimp', - 'mChimp', - mailChimpConf, - setMailChimpConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar, - setConnections - ) - } - return (
- {!isInfo && ( - - )} - - {showNewForm && !isInfo && ( - <> -
- {__('Connection Name:', 'bit-integrations')} -
- -
{error.connectionName}
- -
- {__('Homepage URL:', 'bit-integrations')} -
- - -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- +
+ {__('Homepage URL:', 'bit-integrations')} +
+ - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Mail Chimp API Console', 'bit-integrations')} - - +
+ {__('Authorized Redirect URIs:', 'bit-integrations')} +
+ -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ + {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} + + {__('Mail Chimp API Console', 'bit-integrations')} + + -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
+
+ {__('Client id:', 'bit-integrations')} +
+ +
{error.clientId}
+
+ {__('Client secret:', 'bit-integrations')} +
+ +
{error.clientSecret}
+ {!isInfo && ( + <>
+ )} - - {!isInfo && ( - - )}
) -} +} \ No newline at end of file diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js index ad1af4ceb..955c890e1 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js @@ -1,33 +1,7 @@ -import { create } from 'mutative' import bitsFetch from '../../../Utils/bitsFetch' -import { reauthorizeConnection, saveConnection } from '../../../Utils/connectionApi' +import { deepCopy } from '../../../Utils/Helpers' import { __, sprintf } from '../../../Utils/i18nwrap' - -const APP_SLUG = 'MailChimp' -// Flat auth_details shape — matches OAuth2Authorization::getAccessToken contract. -const ENCRYPT_KEYS = ['access_token', 'refresh_token', 'client_secret'] - -export const applyConnectionToConf = (connection, setMailChimpConf) => { - if (!connection) return - const auth = connection.auth_details || {} - - setMailChimpConf(prev => - create(prev, draft => { - draft.connection_id = connection.id - draft.clientId = auth.client_id || '' - draft.clientSecret = auth.client_secret || '' - draft.tokenDetails = { - access_token: auth.access_token, - dc: auth.dc, - expires_in: auth.expires_in, - generates_on: auth.generated_at, - accountname: auth.accountname, - login_email: auth.login_email, - login_name: auth.login_name - } - }) - ) -} +import { create } from 'mutative' export const handleInput = ( e, @@ -288,8 +262,7 @@ export const handleMailChimpAuthorize = ( setError, setisAuthorized, setIsLoading, - setSnackbar, - setConnections + setSnackbar ) => { if (!confTmp.clientId || !confTmp.clientSecret) { setError({ @@ -344,8 +317,7 @@ export const handleMailChimpAuthorize = ( setConf, setisAuthorized, setIsLoading, - setSnackbar, - setConnections + setSnackbar ) } } @@ -359,8 +331,7 @@ const tokenHelper = ( setConf, setisAuthorized, setIsLoading, - setSnackbar, - setConnections + setSnackbar ) => { const tokenRequestParams = { ...grantToken } tokenRequestParams.clientId = confTmp.clientId @@ -369,59 +340,11 @@ const tokenHelper = ( bitsFetch(tokenRequestParams, `${ajaxInteg}_generate_token`) .then(result => result) - .then(async result => { + .then(result => { if (result && result.success) { - const tokenDetails = result.data - const accountName = tokenDetails.login_email || tokenDetails.accountname || tokenDetails.dc - const connectionName = confTmp.connectionName?.trim() || sprintf(__('MailChimp Connection (%s)', 'bit-integrations'), accountName) - - const authDetails = { - access_token: tokenDetails.access_token, - refresh_token: tokenDetails.refresh_token || '', - dc: tokenDetails.dc, - expires_in: tokenDetails.expires_in || 0, - generated_at: tokenDetails.generates_on || Math.floor(Date.now() / 1000), - client_id: confTmp.clientId, - client_secret: confTmp.clientSecret, - accountname: tokenDetails.accountname, - login_email: tokenDetails.login_email, - login_name: tokenDetails.login_name - } - - const persist = confTmp.connection_id - ? reauthorizeConnection({ - id: confTmp.connection_id, - auth_details: authDetails, - encrypt_keys: ENCRYPT_KEYS, - account_name: accountName, - connection_name: connectionName - }) - : saveConnection({ - app_slug: APP_SLUG, - auth_type: 'oauth2', - auth_details: authDetails, - encrypt_keys: ENCRYPT_KEYS, - account_name: accountName, - connection_name: connectionName - }) - - const connRes = await persist - const connection = connRes?.data?.data || null - - setConf(prev => - create(prev, draft => { - draft.tokenDetails = tokenDetails - if (connection?.id) draft.connection_id = connection.id - }) - ) - - if (connection && setConnections) { - setConnections(prev => { - const filtered = prev.filter(c => c.id !== connection.id) - return [connection, ...filtered] - }) - } - + const newConf = { ...confTmp } + newConf.tokenDetails = result.data + setConf(newConf) setisAuthorized(true) setSnackbar({ show: true, @@ -466,4 +389,4 @@ const generateMappedField = fields => { mailChimpField: field.tag })) : [{ formField: '', mailChimpField: '' }] -} +} \ No newline at end of file From bb4f1a832a6aa2d032005bb5ce12934da1026fc0 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 11:32:29 +0600 Subject: [PATCH 47/58] feat: implement OAuth2 authorization flow for MailChimp integration and refactor related components --- .../Actions/MailChimp/MailChimpController.php | 130 ++++++------ backend/Actions/MailChimp/Routes.php | 1 - .../AllIntegrations/MailChimp/MailChimp.jsx | 9 +- .../MailChimp/MailChimpAuthorization.jsx | 190 ++++++------------ .../MailChimp/MailChimpCommonFunc.js | 157 ++------------- 5 files changed, 135 insertions(+), 352 deletions(-) diff --git a/backend/Actions/MailChimp/MailChimpController.php b/backend/Actions/MailChimp/MailChimpController.php index 467d0a4fc..a3cf67b17 100644 --- a/backend/Actions/MailChimp/MailChimpController.php +++ b/backend/Actions/MailChimp/MailChimpController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\MailChimp; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -15,6 +16,16 @@ */ class MailChimpController { + public static array $authConfig = [ + 'authType' => AuthorizationType::OAUTH2, + 'slug' => 'mailchimp', + 'fields' => [ + 'clientId' => 'client_id', + 'clientSecret' => 'client_secret', + '__object' => ['tokenDetails', ['access_token', 'refresh_token', 'token_type', 'expires_in', 'generated_at', 'generates_on', 'dc']], + ], + ]; + private $_integrationID; public function __construct($integrationID) @@ -65,58 +76,6 @@ public static function refreshModules() return $allModules; } - /** - * Process ajax request for generate_token - * - * @param object $requestsParams Params for generate token - * - * @return JSON zoho crm api response and status - */ - public static function generateTokens($requestsParams) - { - if ( - empty($requestsParams->clientId) - || empty($requestsParams->clientSecret) - || empty($requestsParams->redirectURI) - || empty($requestsParams->code) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - - $apiEndpoint = 'https://login.mailchimp.com/oauth2/token'; - $authorizationHeader['Content-Type'] = 'application/x-www-form-urlencoded'; - $requestParams = [ - 'code' => $requestsParams->code, - 'client_id' => $requestsParams->clientId, - 'client_secret' => $requestsParams->clientSecret, - 'redirect_uri' => $requestsParams->redirectURI, - 'grant_type' => 'authorization_code' - ]; - $apiResponse = HttpHelper::post($apiEndpoint, $requestParams, $authorizationHeader); - - $metaDataEndPoint = 'https://login.mailchimp.com/oauth2/metadata'; - - $authorizationHeader['Authorization'] = "Bearer {$apiResponse->access_token}"; - $metaData = HttpHelper::post($metaDataEndPoint, null, $authorizationHeader); - - $apiResponse->dc = $metaData->dc; - - if (is_wp_error($apiResponse) || !empty($apiResponse->error)) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - $apiResponse->generates_on = time(); - wp_send_json_success($apiResponse, 200); - } - /** * Process ajax request for refresh MailChimp Audience list * @@ -126,12 +85,7 @@ public static function generateTokens($requestsParams) */ public static function refreshAudience($queryParams) { - if ( - empty($queryParams->tokenDetails) - || empty($queryParams->clientId) - || empty($queryParams->clientSecret) - || empty($queryParams->tokenDetails->dc) - ) { + if (empty($queryParams->tokenDetails)) { wp_send_json_error( __( 'Requested parameter is empty', @@ -140,10 +94,17 @@ public static function refreshAudience($queryParams) 400 ); } + + $tokenDetails = self::resolveTokenDetails($queryParams->tokenDetails); + + if (empty($tokenDetails->dc) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization info is missing. please authorize again', 'bit-integrations'), 400); + } + $response = []; - $apiEndpoint = self::apiEndPoint($queryParams->tokenDetails->dc) . '/lists?count=1000&offset=0'; + $apiEndpoint = self::apiEndPoint($tokenDetails->dc) . '/lists?count=1000&offset=0'; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $audienceResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); $allList = []; @@ -159,6 +120,7 @@ public static function refreshAudience($queryParams) uksort($allList, 'strnatcasecmp'); $response['audiencelist'] = $allList; + $response['tokenDetails'] = $tokenDetails; } else { wp_send_json_error( $audienceResponse->response->error->message, @@ -180,7 +142,6 @@ public static function refreshAudienceFields($queryParams) if ( empty($queryParams->tokenDetails) || empty($queryParams->listId) - || empty($queryParams->tokenDetails->dc) ) { wp_send_json_error( __( @@ -191,6 +152,12 @@ public static function refreshAudienceFields($queryParams) ); } + $tokenDetails = self::resolveTokenDetails($queryParams->tokenDetails); + + if (empty($tokenDetails->dc) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization info is missing. please authorize again', 'bit-integrations'), 400); + } + if (isset($queryParams->module) && ($queryParams->module == 'add_tag_to_a_member' || $queryParams->module == 'remove_tag_from_a_member')) { $fields['Email'] = (object) ['tag' => 'email_address', 'name' => 'Email', 'required' => true]; $response['audienceField'] = $fields; @@ -199,8 +166,8 @@ public static function refreshAudienceFields($queryParams) return; } - $apiEndpoint = self::apiEndPoint($queryParams->tokenDetails->dc) . "/lists/{$queryParams->listId}/merge-fields?count=1000&offset=0"; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + $apiEndpoint = self::apiEndPoint($tokenDetails->dc) . "/lists/{$queryParams->listId}/merge-fields?count=1000&offset=0"; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $mergeFieldResponse = HttpHelper::get($apiEndpoint, null, $authorizationHeader); $fields = []; @@ -218,6 +185,7 @@ public static function refreshAudienceFields($queryParams) } $fields['Email'] = (object) ['tag' => 'email_address', 'name' => 'Email', 'required' => true]; $response['audienceField'] = $fields; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response); } } @@ -234,7 +202,6 @@ public static function refreshTags($queryParams) if ( empty($queryParams->tokenDetails) || empty($queryParams->listId) - || empty($queryParams->tokenDetails->dc) ) { wp_send_json_error( __( @@ -244,8 +211,15 @@ public static function refreshTags($queryParams) 400 ); } - $apiEndpoint = self::apiEndPoint($queryParams->tokenDetails->dc) . "/lists/{$queryParams->listId}/segments?count=1000&offset=0"; - $authorizationHeader['Authorization'] = "Bearer {$queryParams->tokenDetails->access_token}"; + + $tokenDetails = self::resolveTokenDetails($queryParams->tokenDetails); + + if (empty($tokenDetails->dc) || empty($tokenDetails->access_token)) { + wp_send_json_error(__('Authorization info is missing. please authorize again', 'bit-integrations'), 400); + } + + $apiEndpoint = self::apiEndPoint($tokenDetails->dc) . "/lists/{$queryParams->listId}/segments?count=1000&offset=0"; + $authorizationHeader['Authorization'] = "Bearer {$tokenDetails->access_token}"; $tagsList = HttpHelper::get($apiEndpoint, null, $authorizationHeader); $allList = []; @@ -257,6 +231,7 @@ public static function refreshTags($queryParams) } uksort($allList, 'strnatcasecmp'); $response['audienceTags'] = $allList; + $response['tokenDetails'] = $tokenDetails; wp_send_json_success($response); } @@ -272,7 +247,7 @@ public function execute($integrationData, $fieldValues) { $integrationDetails = $integrationData->flow_details; - $tokenDetails = $integrationDetails->tokenDetails; + $tokenDetails = self::resolveTokenDetails($integrationDetails->tokenDetails); $listId = $integrationDetails->listId; $module = isset($integrationDetails->module) ? $integrationDetails->module : ''; $tags = $integrationDetails->tags; @@ -283,6 +258,8 @@ public function execute($integrationData, $fieldValues) if ( empty($tokenDetails) + || empty($tokenDetails->access_token) + || empty($tokenDetails->dc) || empty($listId) || empty($fieldMap) || empty($defaultDataConf) @@ -308,4 +285,23 @@ public function execute($integrationData, $fieldValues) return $mChimpApiResponse; } + + private static function resolveTokenDetails($tokenDetails) + { + if (empty($tokenDetails) || !\is_object($tokenDetails) || empty($tokenDetails->access_token) || !empty($tokenDetails->dc)) { + return $tokenDetails; + } + + $metadataResponse = HttpHelper::get( + 'https://login.mailchimp.com/oauth2/metadata', + null, + ['Authorization' => "Bearer {$tokenDetails->access_token}"] + ); + + if (!is_wp_error($metadataResponse) && empty($metadataResponse->error) && !empty($metadataResponse->dc)) { + $tokenDetails->dc = $metadataResponse->dc; + } + + return $tokenDetails; + } } diff --git a/backend/Actions/MailChimp/Routes.php b/backend/Actions/MailChimp/Routes.php index e1f9eb5e3..0ede1216b 100644 --- a/backend/Actions/MailChimp/Routes.php +++ b/backend/Actions/MailChimp/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\MailChimp\MailChimpController; use BitApps\Integrations\Core\Util\Route; -Route::no_sanitize()->post('mChimp_generate_token', [MailChimpController::class, 'generateTokens']); Route::post('mChimp_refresh_audience', [MailChimpController::class, 'refreshAudience']); Route::post('mChimp_refresh_fields', [MailChimpController::class, 'refreshAudienceFields']); Route::post('mChimp_refresh_tags', [MailChimpController::class, 'refreshTags']); diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx index d6126d1f1..c012edbf2 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import 'react-multiple-select-dropdown-lite/dist/index.css' import { useNavigate, useParams } from 'react-router' import BackIcn from '../../../Icons/BackIcn' @@ -11,7 +11,6 @@ import MailChimpAuthorization from './MailChimpAuthorization' import { checkAddressFieldMapRequired, handleInput, - setGrantTokenResponse, checkMappedFields } from './MailChimpCommonFunc' import MailChimpIntegLayout from './MailChimpIntegLayout' @@ -38,10 +37,6 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { moduleLists: [] }) - useEffect(() => { - window.opener && setGrantTokenResponse('mailChimp') - }, []) - const nextPage = () => { setTimeout(() => { document.getElementById('btcd-settings-wrp').scrollTop = 0 @@ -139,4 +134,4 @@ function MailChimp({ formFields, setFlow, flow, allIntegURL }) { ) } -export default MailChimp \ No newline at end of file +export default MailChimp diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx index 1bc151ebd..df8a8dc64 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx @@ -1,11 +1,9 @@ -import { useState } from 'react' -import BackIcn from '../../../Icons/BackIcn' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import CopyText from '../../Utilities/CopyText' -import { handleMailChimpAuthorize, refreshAudience, refreshModules } from './MailChimpCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { refreshAudience, refreshModules } from './MailChimpCommonFunc' export default function MailChimpAuthorization({ formID, @@ -13,140 +11,66 @@ export default function MailChimpAuthorization({ setMailChimpConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setisAuthorized] = useState(false) - const [error, setError] = useState({ dataCenter: '', clientId: '', clientSecret: '' }) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setstep(2) - refreshModules(setMailChimpConf, setIsLoading, setSnackbar) - } + const loadAudience = useCallback( + async connectionId => { + const nextConf = connectionId + ? { ...mailChimpConf, connection_id: connectionId } + : mailChimpConf - const handleInput = e => { - const newConf = { ...mailChimpConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setMailChimpConf(newConf) - } - - return ( -
- - -
- {__('Integration Name:', 'bit-integrations')} -
- - -
- {__('Homepage URL:', 'bit-integrations')} -
- + refreshModules(setMailChimpConf, setIsLoading, setSnackbar) + refreshAudience(formID, nextConf, setMailChimpConf, setIsLoading, setSnackbar) + }, + [formID, mailChimpConf, setMailChimpConf, setIsLoading, setSnackbar] + ) -
- {__('Authorized Redirect URIs:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !mailChimpConf?.default?.audiencelist) { + loadAudience() + } - - {__('To get Client ID and SECRET , Please Visit', 'bit-integrations')}{' '} - - {__('Mail Chimp API Console', 'bit-integrations')} - - + setstep(value) + }, + [loadAudience, mailChimpConf?.default?.audiencelist, setstep] + ) -
- {__('Client id:', 'bit-integrations')} -
- -
{error.clientId}
+ const note = `

${__('Get Mailchimp client id and secret', 'bit-integrations')}

+
    +
  • ${__('Open Mailchimp developer API keys/apps page.', 'bit-integrations')}
  • +
  • ${__('Create or open an app and copy Client ID and Client Secret.', 'bit-integrations')}
  • +
  • ${__('Use the callback URL shown in the connection form.', 'bit-integrations')}
  • +
` -
- {__('Client secret:', 'bit-integrations')} -
- -
{error.clientSecret}
- {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) -} \ No newline at end of file +} diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js index 955c890e1..9ec23f989 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpCommonFunc.js @@ -1,8 +1,16 @@ import bitsFetch from '../../../Utils/bitsFetch' -import { deepCopy } from '../../../Utils/Helpers' import { __, sprintf } from '../../../Utils/i18nwrap' import { create } from 'mutative' +const buildAuthRequestParams = conf => + conf?.connection_id + ? { connection_id: conf.connection_id } + : { + clientId: conf.clientId, + clientSecret: conf.clientSecret, + tokenDetails: conf.tokenDetails + } + export const handleInput = ( e, mailChimpConf, @@ -83,9 +91,7 @@ export const refreshAudience = (formID, mailChimpConf, setMailChimpConf, setIsLo setIsLoading(true) const refreshModulesRequestParams = { formID, - clientId: mailChimpConf.clientId, - clientSecret: mailChimpConf.clientSecret, - tokenDetails: mailChimpConf.tokenDetails + ...buildAuthRequestParams(mailChimpConf) } bitsFetch(refreshModulesRequestParams, 'mChimp_refresh_audience') .then(result => { @@ -140,9 +146,7 @@ export const refreshTags = ( setLoading({ ...loading, tags: true }) const refreshModulesRequestParams = { formID, - clientId: mailChimpConf.clientId, - clientSecret: mailChimpConf.clientSecret, - tokenDetails: mailChimpConf.tokenDetails, + ...buildAuthRequestParams(mailChimpConf), listId: mailChimpConf.listId } bitsFetch(refreshModulesRequestParams, 'mChimp_refresh_tags') @@ -199,9 +203,7 @@ export const refreshFields = ( formID, listId, module: mailChimpConf?.module, - clientId: mailChimpConf.clientId, - clientSecret: mailChimpConf.clientSecret, - tokenDetails: mailChimpConf.tokenDetails + ...buildAuthRequestParams(mailChimpConf) } bitsFetch(refreshSpreadsheetsRequestParams, 'mChimp_refresh_fields') .then(result => { @@ -237,139 +239,6 @@ export const refreshFields = ( .catch(() => setLoading({ ...loading, refreshFields: false })) } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation.replace(`${window.opener.location.href}`, '').split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - -export const handleMailChimpAuthorize = ( - integ, - ajaxInteg, - confTmp, - setConf, - setError, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.clientId || !confTmp.clientSecret) { - setError({ - clientId: !confTmp.clientId ? __("Client Id can't be empty", 'bit-integrations') : '', - clientSecret: !confTmp.clientSecret ? __("Secret key can't be empty", 'bit-integrations') : '' - }) - return - } - setIsLoading(true) - - const apiEndpoint = `https://login.mailchimp.com/oauth2/authorize?client_id=${ - confTmp.clientId - }&redirect_uri=${encodeURIComponent(window.location.href)}&response_type=code` - const authWindow = window.open(apiEndpoint, integ, 'width=400,height=609,toolbar=off') - const popupURLCheckTimer = setInterval(() => { - if (authWindow.closed) { - clearInterval(popupURLCheckTimer) - let grantTokenResponse = {} - let isauthRedirectLocation = false - const bitsMailChimp = localStorage.getItem(`__${integ}`) - if (bitsMailChimp) { - isauthRedirectLocation = true - grantTokenResponse = JSON.parse(bitsMailChimp) - localStorage.removeItem(`__${integ}`) - if (grantTokenResponse.code.search('#')) { - const [code] = grantTokenResponse.code.split('#') - grantTokenResponse.code = code - } - } - if ( - !grantTokenResponse.code || - grantTokenResponse.error || - !grantTokenResponse || - !isauthRedirectLocation - ) { - const errorCause = grantTokenResponse.error ? `Cause: ${grantTokenResponse.error}` : '' - setSnackbar({ - show: true, - msg: `${__('Authorization failed', 'bit-integrations')} ${errorCause}. ${__( - 'please try again', - 'bit-integrations' - )}` - }) - setIsLoading(false) - } else { - const newConf = { ...confTmp } - newConf.accountServer = grantTokenResponse['accounts-server'] - tokenHelper( - ajaxInteg, - grantTokenResponse, - newConf, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar - ) - } - } - }, 500) -} - -const tokenHelper = ( - ajaxInteg, - grantToken, - confTmp, - setConf, - setisAuthorized, - setIsLoading, - setSnackbar -) => { - const tokenRequestParams = { ...grantToken } - tokenRequestParams.clientId = confTmp.clientId - tokenRequestParams.clientSecret = confTmp.clientSecret - tokenRequestParams.redirectURI = window.location.href - - bitsFetch(tokenRequestParams, `${ajaxInteg}_generate_token`) - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - newConf.tokenDetails = result.data - setConf(newConf) - setisAuthorized(true) - setSnackbar({ - show: true, - msg: __('Authorized Successfully', 'bit-integrations') - }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) - } - setIsLoading(false) - }) -} - export const checkMappedFields = mailChimpConf => { const mappedFleld = mailChimpConf.field_map ? mailChimpConf.field_map.filter(mapped => !mapped.formField || !mapped.mailChimpField) @@ -389,4 +258,4 @@ const generateMappedField = fields => { mailChimpField: field.tag })) : [{ formField: '', mailChimpField: '' }] -} \ No newline at end of file +} From 50ebedf83a4db81897522bcd9df9525e34bfbc2c Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 11:41:45 +0600 Subject: [PATCH 48/58] feat: implement API key authorization for Monday.com integration and refactor related components --- .../Actions/MondayCom/MondayComController.php | 25 ++- backend/Actions/MondayCom/Routes.php | 1 - .../AllIntegrations/MondayCom/MondayCom.jsx | 2 - .../MondayCom/MondayComAuthorization.jsx | 145 ++++++------------ .../MondayCom/MondayComCommonFunc.js | 50 ++---- 5 files changed, 64 insertions(+), 159 deletions(-) diff --git a/backend/Actions/MondayCom/MondayComController.php b/backend/Actions/MondayCom/MondayComController.php index 9a3f01a77..c7edf29ec 100644 --- a/backend/Actions/MondayCom/MondayComController.php +++ b/backend/Actions/MondayCom/MondayComController.php @@ -6,31 +6,24 @@ namespace BitApps\Integrations\Actions\MondayCom; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; class MondayComController { + public static array $authConfig = [ + 'authType' => AuthorizationType::API_KEY, + 'slug' => 'mondaycom', + 'fields' => [ + 'apiToken' => 'value', + ], + ]; + private const API_URL = 'https://api.monday.com/v2'; private const API_VERSION = '2023-10'; - public static function authentication($requestParams) - { - if (empty($requestParams->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); diff --git a/backend/Actions/MondayCom/Routes.php b/backend/Actions/MondayCom/Routes.php index 73fe8153c..9da8d37fa 100644 --- a/backend/Actions/MondayCom/Routes.php +++ b/backend/Actions/MondayCom/Routes.php @@ -7,7 +7,6 @@ use BitApps\Integrations\Actions\MondayCom\MondayComController; use BitApps\Integrations\Core\Util\Route; -Route::post('mondayCom_authentication', [MondayComController::class, 'authentication']); Route::post('mondayCom_fetch_boards', [MondayComController::class, 'getBoards']); Route::post('mondayCom_fetch_groups', [MondayComController::class, 'getGroups']); Route::post('mondayCom_fetch_columns', [MondayComController::class, 'getColumns']); diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx index 2f8c10c04..36e555577 100644 --- a/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayCom.jsx @@ -94,9 +94,7 @@ function MondayCom({ formFields, setFlow, flow, allIntegURL }) { setMondayComConf={setMondayComConf} step={step} setStep={setStep} - loading={loading} setLoading={setLoading} - setSnackbar={setSnackbar} /> {/* STEP 2 */} diff --git a/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx b/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx index 8fe6f8e3b..092f60fa5 100644 --- a/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComAuthorization.jsx @@ -1,47 +1,38 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' 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' +import Authorization from '../../Connections/Authorization' +import { getAllBoards } from './MondayComCommonFunc' export default function MondayComAuthorization({ mondayComConf, setMondayComConf, step, setStep, - loading, setLoading, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ apiToken: '' }) + const loadBoards = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...mondayComConf, connection_id: connectionId } : mondayComConf + getAllBoards(nextConf, setMondayComConf, setLoading) + }, + [mondayComConf, setMondayComConf, setLoading] + ) - const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) - setStep(2) - } + const handleSetStep = useCallback( + value => { + if (value === 2 && !mondayComConf?.boards?.length) { + loadBoards() + } - const handleInput = e => { - const { name, value } = e.target - setError(err => - create(err, draft => { - draft[name] = '' - }) - ) - setMondayComConf(prev => - create(prev, draft => { - draft[name] = value - }) - ) - } + setStep(value) + }, + [loadBoards, mondayComConf?.boards?.length, setStep] + ) - const ActiveInstructions = ` + const note = `

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

  • ${__('Log in to your Monday.com account.', 'bit-integrations')}
  • @@ -51,76 +42,28 @@ export default function MondayComAuthorization({
` 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 index 12fa97eb6..22edec09b 100644 --- a/frontend/src/components/AllIntegrations/MondayCom/MondayComCommonFunc.js +++ b/frontend/src/components/AllIntegrations/MondayCom/MondayComCommonFunc.js @@ -6,6 +6,13 @@ import { __ } from '../../../Utils/i18nwrap' import { create } from 'mutative' import { staticFieldsMap, needsColumnMap } from './staticData' +const buildAuthRequestParams = confTmp => + confTmp?.connection_id + ? { connection_id: confTmp.connection_id } + : { + apiToken: confTmp?.apiToken + } + export const handleInput = (e, mondayComConf, setMondayComConf) => { setMondayComConf(mondayComConf => create(mondayComConf, draftConf => { @@ -48,45 +55,10 @@ export const checkMappedFields = mondayComConf => { 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 - } + const requestParams = buildAuthRequestParams(confTmp) bitsFetch(requestParams, 'mondayCom_fetch_boards').then(result => { if (result && result.success) { @@ -114,7 +86,7 @@ export const getAllGroups = (confTmp, setConf, boardId, setLoading) => { setLoading(prev => ({ ...prev, group: true })) const requestParams = { - apiToken: confTmp.apiToken, + ...buildAuthRequestParams(confTmp), boardId } @@ -143,7 +115,7 @@ export const getAllColumns = (confTmp, setConf, boardId, setLoading) => { setLoading(prev => ({ ...prev, column: true })) const requestParams = { - apiToken: confTmp.apiToken, + ...buildAuthRequestParams(confTmp), boardId } @@ -177,7 +149,7 @@ export const getAllItems = (confTmp, setConf, boardId, setLoading) => { setLoading(prev => ({ ...prev, item: true })) const requestParams = { - apiToken: confTmp.apiToken, + ...buildAuthRequestParams(confTmp), boardId } From 58b788fe8e1c53a61891f299eb0b8cc59caee549 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 12:02:54 +0600 Subject: [PATCH 49/58] feat: implement Kirim Email integration with custom authorization and refactor related components --- .../KirimEmail/KirimEmailAuthorization.php | 53 ++++++ .../KirimEmail/KirimEmailController.php | 80 +++------ backend/Actions/KirimEmail/Routes.php | 1 - .../KirimEmail/EditKirimEmail.jsx | 4 +- .../AllIntegrations/KirimEmail/KirimEmail.jsx | 8 +- .../KirimEmail/KirimEmailAuthorization.jsx | 167 ++++++------------ .../KirimEmail/KirimEmailCommonFunc.js | 57 +----- .../KirimEmail/KirimEmailIntegLayout.jsx | 6 +- .../components/Connections/ApiConnection.jsx | 5 +- 9 files changed, 155 insertions(+), 226 deletions(-) create mode 100644 backend/Actions/KirimEmail/KirimEmailAuthorization.php diff --git a/backend/Actions/KirimEmail/KirimEmailAuthorization.php b/backend/Actions/KirimEmail/KirimEmailAuthorization.php new file mode 100644 index 000000000..a3461fb05 --- /dev/null +++ b/backend/Actions/KirimEmail/KirimEmailAuthorization.php @@ -0,0 +1,53 @@ +getAuthDetails(); + $apiKey = $authDetails['api_key'] ?? ''; + + if ($apiKey === '') { + return [ + 'error' => true, + 'message' => __('Kirim Email api key is missing', 'bit-integrations'), + ]; + } + + return $apiKey; + } + + public function getAuthHeadersOrParams() + { + $authDetails = $this->getAuthDetails(); + $username = $authDetails['userName'] ?? ''; + $apiKey = $authDetails['api_key'] ?? ''; + + if ($username === '' || $apiKey === '') { + return [ + 'error' => true, + 'message' => __('Kirim Email username or api key is missing', 'bit-integrations'), + ]; + } + + $time = time(); + $generatedToken = hash_hmac('sha256', "{$username}" . '::' . "{$apiKey}" . '::' . $time, "{$apiKey}"); + + return [ + 'authLocation' => 'header', + 'data' => [ + 'Auth-Id' => $username, + 'Auth-Token' => $generatedToken, + 'Timestamp' => $time, + ], + ]; + } +} diff --git a/backend/Actions/KirimEmail/KirimEmailController.php b/backend/Actions/KirimEmail/KirimEmailController.php index 9a8463bfa..5ebecda2b 100644 --- a/backend/Actions/KirimEmail/KirimEmailController.php +++ b/backend/Actions/KirimEmail/KirimEmailController.php @@ -6,6 +6,7 @@ namespace BitApps\Integrations\Actions\KirimEmail; +use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Core\Util\HttpHelper; use WP_Error; @@ -14,52 +15,23 @@ */ class KirimEmailController { - // $time = time(); - // $generated_token = hash_hmac("sha256","YOUR USERNAME"."::"."YOUR API TOKEN"."::".$time,"YOUR API TOKEN") - - public function checkAuthorization($tokenRequestParams) - { - if ( - empty($tokenRequestParams->username) - || empty($tokenRequestParams->api_key) - ) { - wp_send_json_error( - __( - 'Requested parameter is empty', - 'bit-integrations' - ), - 400 - ); - } - $userName = $tokenRequestParams->username; - $apiKey = $tokenRequestParams->api_key; - $time = time(); - $generated_token = hash_hmac('sha256', "{$userName}" . '::' . "{$apiKey}" . '::' . $time, "{$apiKey}"); - $header = [ - 'Auth-Id' => $userName, - 'Auth-Token' => $generated_token, - 'Timestamp' => $time, - ]; - - $apiEndpoint = 'https://api.kirim.email/v3/list'; - - $apiResponse = HttpHelper::get($apiEndpoint, null, $header); - - if (is_wp_error($apiResponse) || $apiResponse->code !== 200) { - wp_send_json_error( - empty($apiResponse->error) ? 'Unknown' : $apiResponse->error, - 400 - ); - } - - wp_send_json_success($apiResponse->data, 200); - } + public static array $authConfig = [ + 'authType' => AuthorizationType::CUSTOM, + 'slug' => 'KirimEmail', + 'fields' => [ + 'api_key' => 'api_key', + 'userName' => 'userName', + ], + ]; public function getAllList($tokenRequestParams) { + $userName = $tokenRequestParams->userName ?? ''; + $apiKey = $tokenRequestParams->api_key ?? ''; + if ( - empty($tokenRequestParams->username) - || empty($tokenRequestParams->api_key) + empty($userName) + || empty($apiKey) ) { wp_send_json_error( __( @@ -69,17 +41,9 @@ public function getAllList($tokenRequestParams) 400 ); } - $userName = $tokenRequestParams->username; - $apiKey = $tokenRequestParams->api_key; - $time = time(); - $generated_token = hash_hmac('sha256', "{$userName}" . '::' . "{$apiKey}" . '::' . $time, "{$apiKey}"); - $header = [ - 'Auth-Id' => $userName, - 'Auth-Token' => $generated_token, - 'Timestamp' => $time, - ]; $apiEndpoint = 'https://api.kirim.email/v3/list'; + $header = self::buildAuthHeaders($userName, $apiKey); $apiResponse = HttpHelper::get($apiEndpoint, null, $header); @@ -98,7 +62,7 @@ public function execute($integrationData, $fieldValues) $integrationDetails = $integrationData->flow_details; $integrationId = $integrationData->id; $api_key = $integrationDetails->api_key; - $userName = $integrationDetails->userName; + $userName = $integrationDetails->userName ?? ''; $fieldMap = $integrationDetails->field_map; $mainAction = $integrationDetails->mainAction; @@ -128,4 +92,16 @@ public function execute($integrationData, $fieldValues) return $kirinEmailApiResponse; } + + private static function buildAuthHeaders(string $userName, string $apiKey): array + { + $time = time(); + $generatedToken = hash_hmac('sha256', "{$userName}" . '::' . "{$apiKey}" . '::' . $time, "{$apiKey}"); + + return [ + 'Auth-Id' => $userName, + 'Auth-Token' => $generatedToken, + 'Timestamp' => $time, + ]; + } } diff --git a/backend/Actions/KirimEmail/Routes.php b/backend/Actions/KirimEmail/Routes.php index 541d3b3db..0b3b01ee3 100644 --- a/backend/Actions/KirimEmail/Routes.php +++ b/backend/Actions/KirimEmail/Routes.php @@ -7,5 +7,4 @@ use BitApps\Integrations\Actions\KirimEmail\KirimEmailController; use BitApps\Integrations\Core\Util\Route; -Route::post('kirimEmail_authorization', [KirimEmailController::class, 'checkAuthorization']); Route::post('kirimEmail_fetch_all_list', [KirimEmailController::class, 'getAllList']); diff --git a/frontend/src/components/AllIntegrations/KirimEmail/EditKirimEmail.jsx b/frontend/src/components/AllIntegrations/KirimEmail/EditKirimEmail.jsx index 6f63627fc..23948203a 100644 --- a/frontend/src/components/AllIntegrations/KirimEmail/EditKirimEmail.jsx +++ b/frontend/src/components/AllIntegrations/KirimEmail/EditKirimEmail.jsx @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ import { useState } from 'react' -import { useNavigate, useParams } from 'react-router' +import { useNavigate } from 'react-router' import { useRecoilState, useRecoilValue } from 'recoil' import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates' import { __ } from '../../../Utils/i18nwrap' @@ -15,7 +15,6 @@ import { checkMappedFields, handleInput } from './KirimEmailCommonFunc' function EditKirimEmail({ allIntegURL }) { const navigate = useNavigate() - const { formID } = useParams() const [kirimEmailConf, setKirimEmailConf] = useRecoilState($actionConf) const [flow, setFlow] = useRecoilState($newFlow) @@ -42,7 +41,6 @@ function EditKirimEmail({ allIntegURL }) { handleInput(e, kirimEmailConf, setKirimEmailConf, setIsLoading, setSnackbar)} kirimEmailConf={kirimEmailConf} setKirimEmailConf={setKirimEmailConf} isLoading={isLoading} diff --git a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailAuthorization.jsx b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailAuthorization.jsx index 02da0323e..d7a1b05ca 100644 --- a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailAuthorization.jsx @@ -1,128 +1,77 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { AUTH_TYPES } from '../../../Utils/connectionAuth' import { __ } from '../../../Utils/i18nwrap' -import LoaderSm from '../../Loaders/LoaderSm' -import { handleAuthorize } from './KirimEmailCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -import TutorialLink from '../../Utilities/TutorialLink' +import Authorization from '../../Connections/Authorization' +import { getAllList } from './KirimEmailCommonFunc' export default function KirmilEmailAuthorization({ - formID, kirimEmailConf, setKirimEmailConf, step, setstep, - isLoading, setIsLoading, setSnackbar, - redirectLocation, isInfo }) { - const [isAuthorized, setIsAuthorized] = useState(false) - const [error, setError] = useState({ api_key: '' }) -const nextPage = () => { - setTimeout(() => { - document.getElementById('btcd-settings-wrp').scrollTop = 0 - }, 300) + const loadLists = useCallback( + async connectionId => { + const nextConf = connectionId ? { ...kirimEmailConf, connection_id: connectionId } : kirimEmailConf - setstep(2) - } - const handleInput = e => { - const newConf = { ...kirimEmailConf } - const rmError = { ...error } - rmError[e.target.name] = '' - newConf[e.target.name] = e.target.value - setError(rmError) - setKirimEmailConf(newConf) - } - - return ( -
- + await getAllList(nextConf, setKirimEmailConf, setIsLoading, setSnackbar) + }, + [kirimEmailConf, setKirimEmailConf, setIsLoading, setSnackbar] + ) -
- {__('Integration Name:', 'bit-integrations')} -
- + const handleSetStep = useCallback( + value => { + if (value === 2 && !kirimEmailConf?.default?.allLists?.length) { + loadLists() + } -
- {__('Your username:', 'bit-integrations')} -
- -
{error.userName}
+ setstep(value) + }, + [kirimEmailConf?.default?.allLists?.length, loadLists, setstep] + ) -
- {__('App api key:', 'bit-integrations')} -
- -
{error.api_key}
- - {__('To get Api key , Please Visit ', 'bit-integrations')} - - {__('Kirim Email', 'bit-integrations')} - - -
-
+ const note = `

${__('Get Kirim Email credentials', 'bit-integrations')}

+
    +
  • ${__('Log in to your Kirim Email account.', 'bit-integrations')}
  • +
  • ${__('Copy your username and App API key.', 'bit-integrations')}
  • +
  • ${__('Authorize and save the connection.', 'bit-integrations')}
  • +
` - {!isInfo && ( - <> - -
- - - )} -
+ return ( + ) } diff --git a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailCommonFunc.js b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailCommonFunc.js index bbe98fcbf..c32734993 100644 --- a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailCommonFunc.js +++ b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailCommonFunc.js @@ -14,60 +14,17 @@ export const handleInput = (e, slackConf, setSlackConf) => { setSlackConf({ ...newConf }) } -export const handleAuthorize = ( - confTmp, - setConf, - setError, - setIsAuthorized, - setIsLoading, - setSnackbar -) => { - if (!confTmp.api_key) { - setError({ api_key: !confTmp.api_key ? __("Api Key can't be empty", 'bit-integrations') : '' }) - return - } - setError({}) - setIsLoading(true) - - const tokenRequestParams = { username: confTmp.userName, api_key: confTmp.api_key } - - bitsFetch(tokenRequestParams, 'kirimEmail_authorization') - .then(result => result) - .then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - if (!newConf.default) { - newConf.default = {} - } - if (result.data) { - newConf.default.allLists = result.data - } - setConf(newConf) - setIsAuthorized(true) - setSnackbar({ show: true, msg: __('Authorized Successfully', 'bit-integrations') }) - } else if ( - (result && result.data && result.data.data) || - (!result.success && typeof result.data === 'string') - ) { - setSnackbar({ - show: true, - msg: `${__('Authorization failed Cause:', 'bit-integrations')}${ - result.data.data || result.data - }. ${__('please try again', 'bit-integrations')}` - }) - } else { - setSnackbar({ - show: true, - msg: __('Authorization failed. please try again', 'bit-integrations') - }) +const buildAuthRequestParams = confTmp => + confTmp?.connection_id + ? { connection_id: confTmp.connection_id } + : { + userName: confTmp.userName, + api_key: confTmp.api_key } - setIsLoading(false) - }) -} export const getAllList = (kirimEmailConf, setKirimEmailConf, setIsLoading, setSnackbar) => { setIsLoading(true) - const tokenRequestParams = { username: kirimEmailConf.userName, api_key: kirimEmailConf.api_key } + const tokenRequestParams = buildAuthRequestParams(kirimEmailConf) bitsFetch(tokenRequestParams, 'kirimEmail_fetch_all_list') .then(result => { diff --git a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailIntegLayout.jsx b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailIntegLayout.jsx index 62edd2010..3ba1fa2d3 100644 --- a/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/KirimEmail/KirimEmailIntegLayout.jsx @@ -1,5 +1,4 @@ import MultiSelect from 'react-multiple-select-dropdown-lite' -import { useState } from 'react' import { __ } from '../../../Utils/i18nwrap' import Loader from '../../Loaders/Loader' import { addFieldMap } from './KirimEmailIntegrationHelpers' @@ -9,15 +8,12 @@ import TableCheckBox from '../../Utilities/TableCheckBox' import { getAllList } from './KirimEmailCommonFunc' export default function KirimEmailIntegLayout({ - formID, formFields, - handleInput, kirimEmailConf, setKirimEmailConf, isLoading, setIsLoading, - setSnackbar, - a + setSnackbar }) { const inputHandler = e => { const newConf = { ...kirimEmailConf } diff --git a/frontend/src/components/Connections/ApiConnection.jsx b/frontend/src/components/Connections/ApiConnection.jsx index ebecbadb1..d2dde9926 100644 --- a/frontend/src/components/Connections/ApiConnection.jsx +++ b/frontend/src/components/Connections/ApiConnection.jsx @@ -95,7 +95,10 @@ const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails } auth_type: authType, api_endpoint: resolveTemplate(resolvedApiEndpoint, authData), method: method || 'GET', - auth_details: {}, + auth_details: { + ...extraAuthDetails, + ssl_verify: sslVerify + }, headers: additionalHeaders } From 7e2166a67ff6751e41837e129df285e1fb4219d3 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 12:58:31 +0600 Subject: [PATCH 50/58] feat: enhance token handling and improve error messaging in integrations --- backend/Actions/Keap/KeapController.php | 7 +++---- .../KirimEmail/KirimEmailController.php | 6 +++--- backend/Actions/ZohoAnalytics/Routes.php | 2 +- .../Basic/BasicAuthorization.php | 8 +++++--- .../GoogleIntegrationHelpers.js | 19 ------------------ .../IntegrationHelpers/IntegrationHelpers.js | 19 ------------------ .../AllIntegrations/Keap/KeapActions.jsx | 2 +- .../AllIntegrations/Keap/KeapCommonFunc.js | 20 +++++++++---------- .../ZohoAnalytics/ZohoAnalyticsCommonFunc.js | 13 +++--------- 9 files changed, 25 insertions(+), 71 deletions(-) diff --git a/backend/Actions/Keap/KeapController.php b/backend/Actions/Keap/KeapController.php index afc3c4b1a..d446c8777 100644 --- a/backend/Actions/Keap/KeapController.php +++ b/backend/Actions/Keap/KeapController.php @@ -72,12 +72,11 @@ public static function refreshTagListAjaxHelper($queryParams) 'name' => $tag->name ]; } + if (!empty($response['tokenDetails']) && !empty($queryParams->id)) { + static::saveRefreshedToken($queryParams->id, $response['tokenDetails']); + } wp_send_json_success($tags, 200); } - if (!empty($response['tokenDetails']) && $response['tokenDetails'] && !empty($queryParams->id)) { - static::saveRefreshedToken($queryParams->id, $response['tokenDetails'], $response); - } - wp_send_json_success($response, 200); } public static function refreshCustomFieldAjaxHelper($queryParams) diff --git a/backend/Actions/KirimEmail/KirimEmailController.php b/backend/Actions/KirimEmail/KirimEmailController.php index 5ebecda2b..a5d3dd94c 100644 --- a/backend/Actions/KirimEmail/KirimEmailController.php +++ b/backend/Actions/KirimEmail/KirimEmailController.php @@ -26,7 +26,7 @@ class KirimEmailController public function getAllList($tokenRequestParams) { - $userName = $tokenRequestParams->userName ?? ''; + $userName = $tokenRequestParams->userName ?? $tokenRequestParams->username ?? ''; $apiKey = $tokenRequestParams->api_key ?? ''; if ( @@ -62,7 +62,7 @@ public function execute($integrationData, $fieldValues) $integrationDetails = $integrationData->flow_details; $integrationId = $integrationData->id; $api_key = $integrationDetails->api_key; - $userName = $integrationDetails->userName ?? ''; + $userName = $integrationDetails->userName ?? $integrationDetails->username ?? ''; $fieldMap = $integrationDetails->field_map; $mainAction = $integrationDetails->mainAction; @@ -74,7 +74,7 @@ public function execute($integrationData, $fieldValues) ) { // translators: %s: Placeholder value - return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Freshdesk')); + return new WP_Error('REQ_FIELD_EMPTY', wp_sprintf(__('module, fields are required for %s api', 'bit-integrations'), 'Kirim Email')); } $recordApiHelper = new RecordApiHelper($integrationId); $kirinEmailApiResponse = $recordApiHelper->execute( diff --git a/backend/Actions/ZohoAnalytics/Routes.php b/backend/Actions/ZohoAnalytics/Routes.php index ef3737384..6e6294b3f 100644 --- a/backend/Actions/ZohoAnalytics/Routes.php +++ b/backend/Actions/ZohoAnalytics/Routes.php @@ -10,7 +10,7 @@ Route::post('zanalytics_refresh_workspaces', [ZohoAnalyticsController::class, 'refreshWorkspacesAjaxHelper']); Route::post('zanalytics_refresh_users', [ZohoAnalyticsController::class, 'refreshUsersAjaxHelper']); Route::post('zanalytics_refresh_tables', [ZohoAnalyticsController::class, 'refreshTablesAjaxHelper']); -Route::post('wp_ajax_zanalytics_refresh_table_headers', [ZohoAnalyticsController::class, 'refreshTableHeadersAjaxHelper']); +Route::post('zanalytics_refresh_table_headers', [ZohoAnalyticsController::class, 'refreshTableHeadersAjaxHelper']); // public static function registerAjax() // { diff --git a/backend/Authorization/Basic/BasicAuthorization.php b/backend/Authorization/Basic/BasicAuthorization.php index 7d78b2a1a..2f276bb9a 100644 --- a/backend/Authorization/Basic/BasicAuthorization.php +++ b/backend/Authorization/Basic/BasicAuthorization.php @@ -14,14 +14,16 @@ public function getAccessToken() { $authDetails = $this->getAuthDetails(); - if (empty($authDetails) || empty($authDetails['username']) || !isset($authDetails['password'])) { + if (empty($authDetails) || empty($authDetails['username'])) { return [ 'error' => true, - 'message' => __('username or password field is missing', 'bit-integrations'), + 'message' => __('username field is missing', 'bit-integrations'), ]; } - return 'Basic ' . base64_encode($authDetails['username'] . ':' . $authDetails['password']); + $password = $authDetails['password'] ?? ''; + + return 'Basic ' . base64_encode($authDetails['username'] . ':' . $password); } public function getAuthHeadersOrParams() diff --git a/frontend/src/components/AllIntegrations/IntegrationHelpers/GoogleIntegrationHelpers.js b/frontend/src/components/AllIntegrations/IntegrationHelpers/GoogleIntegrationHelpers.js index cba68f863..393239f59 100644 --- a/frontend/src/components/AllIntegrations/IntegrationHelpers/GoogleIntegrationHelpers.js +++ b/frontend/src/components/AllIntegrations/IntegrationHelpers/GoogleIntegrationHelpers.js @@ -1,25 +1,6 @@ import bitsFetch from '../../../Utils/bitsFetch' import { __ } from '../../../Utils/i18nwrap' -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - const tokenHelper = ( ajaxInteg, grantToken, diff --git a/frontend/src/components/AllIntegrations/IntegrationHelpers/IntegrationHelpers.js b/frontend/src/components/AllIntegrations/IntegrationHelpers/IntegrationHelpers.js index a507f5653..df93f57d5 100644 --- a/frontend/src/components/AllIntegrations/IntegrationHelpers/IntegrationHelpers.js +++ b/frontend/src/components/AllIntegrations/IntegrationHelpers/IntegrationHelpers.js @@ -460,25 +460,6 @@ export const saveActionConf = async ({ } } -export const setGrantTokenResponse = integ => { - const grantTokenResponse = {} - const authWindowLocation = window.location.href - const queryParams = authWindowLocation - .replace(`${window.opener.location.href}/redirect`, '') - .split('&') - if (queryParams) { - queryParams.forEach(element => { - const gtKeyValue = element.split('=') - if (gtKeyValue[1]) { - // eslint-disable-next-line prefer-destructuring - grantTokenResponse[gtKeyValue[0]] = gtKeyValue[1] - } - }) - } - localStorage.setItem(`__${integ}`, JSON.stringify(grantTokenResponse)) - window.close() -} - export const handleAuthorize = ( integ, ajaxInteg, diff --git a/frontend/src/components/AllIntegrations/Keap/KeapActions.jsx b/frontend/src/components/AllIntegrations/Keap/KeapActions.jsx index 3d43b1cd9..eb50860a4 100644 --- a/frontend/src/components/AllIntegrations/Keap/KeapActions.jsx +++ b/frontend/src/components/AllIntegrations/Keap/KeapActions.jsx @@ -72,7 +72,7 @@ export default function KeapActions({ keapConf, setKeapConf, loading, setLoading title={__('Tags', 'bit-integrations')}>
{__('Select tags', 'bit-integrations')}
- {loading.tags ? ( + {loading ? ( { } export const getAllTags = (confTmp, setConf, setLoading) => { - setLoading({ ...setLoading, tags: true }) + setLoading(true) const requestParams = { ...buildAuthRequestParams(confTmp), @@ -65,19 +66,16 @@ export const getAllTags = (confTmp, setConf, setLoading) => { } setConf(newConf) setLoading(false) - - setSnackbar({ - show: true, - msg: __('Tag Fetched Successfully', 'bit-integrations') - }) + toast.success(__('Tag Fetched Successfully', 'bit-integrations')) return } - setLoading({ ...setLoading, tags: false }) - setSnackbar({ - show: true, - msg: __("Tag Couldn't Fetched Successfully", 'bit-integrations') - }) + setLoading(false) + toast.error(__("Tag Couldn't Fetched Successfully", 'bit-integrations')) }) + .catch(() => { + setLoading(false) + toast.error(__("Tag Couldn't Fetched Successfully", 'bit-integrations')) + }) } export const refreshCustomFields = (id, confTmp, setConf, setIsLoading, setSnackbar) => { diff --git a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js index 4de455bf4..847d640df 100644 --- a/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js +++ b/frontend/src/components/AllIntegrations/ZohoAnalytics/ZohoAnalyticsCommonFunc.js @@ -122,11 +122,8 @@ export const refreshUsers = (formID, analyticsConf, setAnalyticsConf, setIsLoadi const refreshUsersRequestParams = { formID, id: analyticsConf.id, - connection_id: analyticsConf.connection_id, + ...buildAuthRequestParams(analyticsConf), dataCenter: analyticsConf.dataCenter, - clientId: analyticsConf.clientId, - clientSecret: analyticsConf.clientSecret, - tokenDetails: analyticsConf.tokenDetails, ownerEmail: analyticsConf.ownerEmail } bitsFetch(refreshUsersRequestParams, 'zanalytics_refresh_users') @@ -175,11 +172,8 @@ export const refreshTables = (formID, analyticsConf, setAnalyticsConf, setIsLoad const refreshTablesRequestParams = { formID, workspace, - connection_id: analyticsConf.connection_id, + ...buildAuthRequestParams(analyticsConf), dataCenter: analyticsConf.dataCenter, - clientId: analyticsConf.clientId, - clientSecret: analyticsConf.clientSecret, - tokenDetails: analyticsConf.tokenDetails, ownerEmail: analyticsConf.ownerEmail } bitsFetch(refreshTablesRequestParams, 'zanalytics_refresh_tables') @@ -226,9 +220,8 @@ export const refreshTableHeaders = ( formID, workspace, table, - connection_id: analyticsConf.connection_id, + ...buildAuthRequestParams(analyticsConf), dataCenter: analyticsConf.dataCenter, - ownerEmail: analyticsConf.ownerEmail } bitsFetch(refreshTableHeadersRequestParams, 'zanalytics_refresh_table_headers') From ff6eae32eaa9f49691f3d4496e021fd4db5139f8 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 14:32:53 +0600 Subject: [PATCH 51/58] feat: enhance authorization error handling and improve connection response formatting --- .../AbstractBaseAuthorization.php | 2 +- .../ApiKey/ApiKeyAuthorization.php | 4 +- .../OAuth2/OAuth2Authorization.php | 8 ++- backend/Core/Util/PluginCheck.php | 10 ++-- backend/controller/ConnectionController.php | 34 ++++++++++++- frontend/src/Utils/oauthHelper.js | 48 ++++++++++++++++-- .../Connections/ConnectionAccountList.jsx | 9 ++-- .../Connections/Oauth1Connection.jsx | 22 +++++++-- .../Connections/Oauth2Connection.jsx | 12 +++-- frontend/src/pages/Connections.jsx | 49 ++++++++++++------- 10 files changed, 157 insertions(+), 41 deletions(-) diff --git a/backend/Authorization/AbstractBaseAuthorization.php b/backend/Authorization/AbstractBaseAuthorization.php index 98a4d7a7a..3b1ef440d 100644 --- a/backend/Authorization/AbstractBaseAuthorization.php +++ b/backend/Authorization/AbstractBaseAuthorization.php @@ -194,7 +194,7 @@ public function getAuthDetails() $connection = $this->getConnection(); if (!$connection) { - return; + return null; } $authDetails = $this->decodeAuthDetails($connection->auth_details ?? null); diff --git a/backend/Authorization/ApiKey/ApiKeyAuthorization.php b/backend/Authorization/ApiKey/ApiKeyAuthorization.php index 61012b4bb..1f5da8fca 100644 --- a/backend/Authorization/ApiKey/ApiKeyAuthorization.php +++ b/backend/Authorization/ApiKey/ApiKeyAuthorization.php @@ -17,7 +17,7 @@ public function getAccessToken() if (empty($authDetails) || !isset($authDetails['value']) || $authDetails['value'] === '') { return [ 'error' => true, - 'message' => __('token field is missing', 'bit-integrations'), + 'message' => __('Token field is missing', 'bit-integrations'), ]; } @@ -31,7 +31,7 @@ public function getAuthHeadersOrParams() if (empty($authDetails) || !isset($authDetails['value'])) { return [ 'error' => true, - 'message' => __('token field is missing', 'bit-integrations'), + 'message' => __('Token field is missing', 'bit-integrations'), ]; } diff --git a/backend/Authorization/OAuth2/OAuth2Authorization.php b/backend/Authorization/OAuth2/OAuth2Authorization.php index 1be2b5887..53ad6d6be 100644 --- a/backend/Authorization/OAuth2/OAuth2Authorization.php +++ b/backend/Authorization/OAuth2/OAuth2Authorization.php @@ -120,7 +120,13 @@ public function refreshAccessToken(array $authDetails): ?array $response = HttpHelper::post($url, $body, $headers, $requestOptions); - if (HttpHelper::$responseCode !== 200 || (\is_object($response) && isset($response->error))) { + if (is_wp_error($response)) { + $this->setLastError($response->get_error_message(), $response); + + return null; + } + + if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300 || (\is_object($response) && isset($response->error))) { $message = \is_object($response) && isset($response->error) ? $response->error : __('Token refresh failed', 'bit-integrations'); diff --git a/backend/Core/Util/PluginCheck.php b/backend/Core/Util/PluginCheck.php index 5a9fadc19..1f2a13cf6 100644 --- a/backend/Core/Util/PluginCheck.php +++ b/backend/Core/Util/PluginCheck.php @@ -68,11 +68,11 @@ public static function evaluate(array $spec): array $type = $check['type'] ?? null; $value = $check['value'] ?? null; - if (!\in_array($type, self::ALLOWED_TYPES, true) || empty($value)) { + if (!\in_array($type, self::ALLOWED_TYPES, true) || !\is_scalar($value) || $value === '') { continue; } - $checkResults[] = self::matches($type, $value); + $checkResults[] = self::matches($type, (string) $value); } if (empty($checkResults)) { @@ -143,7 +143,11 @@ private static function normalizeGroups(array $spec): array private static function normalizeLogic($raw): string { - $normalized = strtoupper($raw); + if (!\is_scalar($raw)) { + return 'AND'; + } + + $normalized = strtoupper((string) $raw); return \in_array($normalized, self::ALLOWED_LOGIC, true) ? $normalized : 'AND'; } diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 6e43282c5..ef7ffca3b 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -63,7 +63,7 @@ public function index($request) $payload = []; foreach ($rows as $row) { - $payload[] = $this->formatRow($row); + $payload[] = $this->formatListRow($row); } wp_send_json_success(['data' => $payload]); @@ -250,6 +250,10 @@ public function authorize($request) wp_send_json_error(__('API endpoint is required', 'bit-integrations')); } + if (!$this->isPublicHttpsUrl($apiEndpoint)) { + wp_send_json_error(__('API endpoint must be a public https endpoint', 'bit-integrations'), 400); + } + $authDetails = $this->normalizeArray($request->auth_details); if (empty($authDetails)) { @@ -388,9 +392,17 @@ public function oauth2Exchange($request) $decoded = \is_object($response) ? Helper::jsonEncodeDecode($response) : $response; if ($responseCode < 200 || $responseCode >= 300 || (\is_array($decoded) && isset($decoded['error']))) { + if (\is_array($decoded) && isset($decoded['error_description'])) { + $errorMessage = $decoded['error_description']; + } elseif (\is_array($decoded) && isset($decoded['error']) && \is_string($decoded['error'])) { + $errorMessage = $decoded['error']; + } else { + $errorMessage = 'Token exchange failed'; + } + wp_send_json_error( [ - 'message' => \is_array($decoded) && isset($decoded['error_description']) ? $decoded['error_description'] : (\is_array($decoded) && isset($decoded['error']) ? (\is_string($decoded['error']) ? $decoded['error'] : 'Token exchange failed') : 'Token exchange failed'), + 'message' => $errorMessage, 'response' => $decoded, 'status' => $responseCode, ], @@ -600,6 +612,24 @@ private function formatRow($row): array ]; } + /** + * List responses should not expose decrypted credential payloads. + */ + private function formatListRow($row): array + { + return [ + 'id' => (int) $row->id, + 'app_slug' => $row->app_slug, + 'auth_type' => (string) ($row->auth_type ?? ''), + 'connection_name' => $row->connection_name, + 'account_name' => $row->account_name, + 'status' => isset($row->status) ? (int) $row->status : ConnectionModel::STATUS_VERIFIED, + 'user_id' => isset($row->user_id) ? (int) $row->user_id : 0, + 'created_at' => $row->created_at ?? null, + 'updated_at' => $row->updated_at ?? null, + ]; + } + private function resolveEncryptKeys($request): array { if (!isset($request->encrypt_keys)) { diff --git a/frontend/src/Utils/oauthHelper.js b/frontend/src/Utils/oauthHelper.js index 53e151571..93bd198fd 100644 --- a/frontend/src/Utils/oauthHelper.js +++ b/frontend/src/Utils/oauthHelper.js @@ -39,6 +39,22 @@ export const getCallbackState = () => { return `${baseURL}/auth-response/` } +const randomToken = (bytes = 16) => { + const arr = new Uint8Array(bytes) + window.crypto.getRandomValues(arr) + return base64UrlEncode(arr) +} + +const getChannelName = channelKey => + channelKey ? `${OAUTH_CHANNEL}:${channelKey}` : OAUTH_CHANNEL + +export const createOauthChannelKey = () => randomToken(18) + +export const buildCallbackState = channelKey => { + const baseState = getCallbackState() + return channelKey ? `${baseState}&oauth_channel=${encodeURIComponent(channelKey)}` : baseState +} + const appendQueryParam = (url, key, value) => { url.searchParams.append(key, String(value)) } @@ -55,7 +71,23 @@ export const buildAuthUrl = (authCodeEndpoint, { state, redirectUri, extraParams return url.toString() } -export const openOauthPopup = (authUrl, label = 'OAuth') => +const extractChannelFromState = stateValue => { + if (!stateValue || typeof stateValue !== 'string') return '' + + try { + const decoded = decodeURIComponent(stateValue) + const match = decoded.match(/(?:\?|&)oauth_channel=([^&]+)/) + return match?.[1] ? decodeURIComponent(match[1]) : '' + } catch { + return '' + } +} + +export const openOauthPopup = ( + authUrl, + label = 'OAuth', + { channelKey = '', includeLegacyFallback = false } = {} +) => new Promise(resolve => { const popup = window.open(authUrl, label, 'width=500,height=650,toolbar=off') @@ -65,20 +97,27 @@ export const openOauthPopup = (authUrl, label = 'OAuth') => } let resolved = false - const channel = new BroadcastChannel(OAUTH_CHANNEL) + const channel = new BroadcastChannel(getChannelName(channelKey)) + const fallbackChannel = + includeLegacyFallback && channelKey + ? new BroadcastChannel(OAUTH_CHANNEL) + : null const cleanup = () => { channel.close() + fallbackChannel?.close() clearInterval(closeTimer) } - channel.onmessage = event => { + const resolveMessage = event => { if (resolved) return resolved = true cleanup() try { popup.close() } catch (_) {} // eslint-disable-line no-unused-vars, no-empty resolve(event.data || {}) } + channel.onmessage = resolveMessage + if (fallbackChannel) fallbackChannel.onmessage = resolveMessage const closeTimer = setInterval(() => { if (popup.closed && !resolved) { @@ -90,7 +129,8 @@ export const openOauthPopup = (authUrl, label = 'OAuth') => }) export const broadcastAuthCodeResponse = response => { - const channel = new BroadcastChannel(OAUTH_CHANNEL) + const channelKey = response?.oauth_channel || extractChannelFromState(response?.state) + const channel = new BroadcastChannel(getChannelName(channelKey)) channel.postMessage(response) setTimeout(() => channel.close(), 200) } diff --git a/frontend/src/components/Connections/ConnectionAccountList.jsx b/frontend/src/components/Connections/ConnectionAccountList.jsx index 59055e7f9..e59ffb76e 100644 --- a/frontend/src/components/Connections/ConnectionAccountList.jsx +++ b/frontend/src/components/Connections/ConnectionAccountList.jsx @@ -49,11 +49,10 @@ export default function ConnectionAccountList({
{connections.map(conn => { - const userInfo = conn.auth_details?.userInfo?.user || conn.auth_details?.userInfo || {} - const displayName = - userInfo.displayName || userInfo.name || conn.account_name || conn.connection_name - const email = userInfo.emailAddress || userInfo.email || conn.account_name - const photo = userInfo.photoLink || userInfo.picture || '' + const displayName = conn.connection_name || conn.account_name + const email = + conn.account_name && conn.account_name !== displayName ? conn.account_name : '' + const photo = '' return (
diff --git a/frontend/src/components/Connections/Oauth1Connection.jsx b/frontend/src/components/Connections/Oauth1Connection.jsx index c316907c5..36bdc078d 100644 --- a/frontend/src/components/Connections/Oauth1Connection.jsx +++ b/frontend/src/components/Connections/Oauth1Connection.jsx @@ -2,7 +2,12 @@ import { useCallback, useMemo, useState } from 'react' import toast from 'react-hot-toast' import { authorizeConnection, saveConnection } from '../../Utils/connectionApi' import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' -import { getCallbackState, openOauthPopup } from '../../Utils/oauthHelper' +import { + buildCallbackState, + createOauthChannelKey, + getCallbackState, + openOauthPopup +} from '../../Utils/oauthHelper' import { __ } from '../../Utils/i18nwrap' import { APP_CONFIG } from '../../config/app' import LoaderSm from '../Loaders/LoaderSm' @@ -262,6 +267,7 @@ export default function Oauth1Connection({ const queryParams = { ...declaredQueryParams } const authExtraParams = {} const callbackUrlParam = authDetails?.callbackUrlParam || '' + const stateParam = authDetails?.stateParam || 'state' if (!queryParams[consumerKeyParam]) { authExtraParams[consumerKeyParam] = formData.clientId @@ -274,16 +280,26 @@ export default function Oauth1Connection({ setIsLoading(true) try { + const oauthChannelKey = createOauthChannelKey() + const callbackState = buildCallbackState(oauthChannelKey) + const authUrl = buildOauth1AuthUrl( { ...resolvedAuthEndpoint, queryParams }, - authExtraParams + { + ...authExtraParams, + ...(queryParams[stateParam] ? {} : { [stateParam]: callbackState }) + } ) const popupResponse = normalizePopupResponse( - await openOauthPopup(authUrl, authDetails?.authorizationWindowLabel || 'OAuth1') + await openOauthPopup( + authUrl, + authDetails?.authorizationWindowLabel || 'OAuth1', + { channelKey: oauthChannelKey, includeLegacyFallback: true } + ) ) if (popupResponse?.error) { diff --git a/frontend/src/components/Connections/Oauth2Connection.jsx b/frontend/src/components/Connections/Oauth2Connection.jsx index 6f3279839..376551bba 100644 --- a/frontend/src/components/Connections/Oauth2Connection.jsx +++ b/frontend/src/components/Connections/Oauth2Connection.jsx @@ -4,11 +4,12 @@ import { AUTH_TYPES, defaultEncryptKeys } from '../../Utils/connectionAuth' import { saveConnection } from '../../Utils/connectionApi' import { buildAuthUrl, + buildCallbackState, + createOauthChannelKey, exchangeAuthCodeForToken, exchangeClientCredentialsForToken, generateCodeChallengeS256, generateCodeVerifier, - getCallbackState, getRedirectUri, openOauthPopup } from '../../Utils/oauthHelper' @@ -193,9 +194,14 @@ export default function Oauth2Connection({ const { client_id: _ignored, ...queryParams } = declaredQueryParams const populatedAuthCodeEndpoint = { ...resolvedAuthCodeEndpoint, queryParams } - const state = getCallbackState() + const oauthChannelKey = createOauthChannelKey() + const state = buildCallbackState(oauthChannelKey) const authUrl = buildAuthUrl(populatedAuthCodeEndpoint, { state, redirectUri, extraParams }) - const popupResponse = await openOauthPopup(authUrl, formData.connectionName || 'OAuth') + const popupResponse = await openOauthPopup( + authUrl, + formData.connectionName || 'OAuth', + { channelKey: oauthChannelKey, includeLegacyFallback: true } + ) if (popupResponse?.error) { throw new Error( diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index 2c56d0565..0c2ef97d4 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -18,6 +18,7 @@ export default function Connections() { const [editingId, setEditingId] = useState(null) const [deletingId, setDeletingId] = useState(null) const [savingId, setSavingId] = useState(null) + const [isConfirmPending, setIsConfirmPending] = useState(false) const inputRef = useRef(null) const editValueRef = useRef('') @@ -111,7 +112,10 @@ export default function Connections() { const confirmDelete = useCallback(() => { const id = deletingId - if (!id) return + if (!id || isConfirmPending) return + + setDeletingId(null) + setIsConfirmPending(true) const promise = deleteConnection(id).then(res => { if (!res?.success) throw new Error('delete_failed') @@ -119,14 +123,14 @@ export default function Connections() { return __('Connection deleted', 'bit-integrations') }) - toast.promise(promise, { - success: msg => msg, - error: __('Failed to delete', 'bit-integrations'), - loading: __('Deleting...', 'bit-integrations') - }) - - setDeletingId(null) - }, [deletingId]) + toast + .promise(promise, { + success: msg => msg, + error: __('Failed to delete', 'bit-integrations'), + loading: __('Deleting...', 'bit-integrations') + }) + .finally(() => setIsConfirmPending(false)) + }, [deletingId, isConfirmPending]) const setBulkDelete = useCallback(rows => { const ids = [] @@ -145,20 +149,31 @@ export default function Connections() { return } - const promise = Promise.all( + const promise = Promise.allSettled( ids.map(id => deleteConnection(id).then(res => { - if (!res?.success) { - throw new Error('bulk_delete_failed') - } - + if (!res?.success) throw new Error('bulk_delete_failed') return id }) ) - ).then(() => { - setConnections(prev => prev.filter(item => !ids.includes(item.id))) + ).then(results => { + const deletedIds = results + .filter(r => r.status === 'fulfilled') + .map(r => r.value) + + if (deletedIds.length > 0) { + setConnections(prev => prev.filter(item => !deletedIds.includes(item.id))) + } + + const failedCount = results.filter(r => r.status === 'rejected').length + + if (failedCount > 0) { + throw new Error( + failedCount === ids.length ? 'bulk_delete_failed' : 'bulk_delete_partial' + ) + } - return ids.length > 1 + return deletedIds.length > 1 ? __('Connections deleted', 'bit-integrations') : __('Connection deleted', 'bit-integrations') }) From b92b65c10612373ee467c4177ed097a90e3af849 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 15:50:21 +0600 Subject: [PATCH 52/58] feat: refactor Connections component layout and enhance styling for better usability --- .../Connections/ConnectionAccountList.jsx | 124 ------------------ frontend/src/pages/Connections.jsx | 71 +++++----- frontend/src/resource/sass/app.scss | 122 +++++++++++++++-- 3 files changed, 145 insertions(+), 172 deletions(-) delete mode 100644 frontend/src/components/Connections/ConnectionAccountList.jsx diff --git a/frontend/src/components/Connections/ConnectionAccountList.jsx b/frontend/src/components/Connections/ConnectionAccountList.jsx deleted file mode 100644 index e59ffb76e..000000000 --- a/frontend/src/components/Connections/ConnectionAccountList.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import TrashIcn from '../../Icons/TrashIcn' -import { deleteConnection } from '../../Utils/connectionApi' -import { __ } from '../../Utils/i18nwrap' - -export default function ConnectionAccountList({ - connections, - setConnections, - selectedId, - onSelect, - onReauthorize, - setIsLoading, - setSnackbar, - isInfo -}) { - const [confirmId, setConfirmId] = useState(null) - const popoverRef = useRef(null) - - useEffect(() => { - const handleClickOutside = event => { - if (popoverRef.current && !popoverRef.current.contains(event.target)) { - setConfirmId(null) - } - } - document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) - - const handleDelete = id => { - if (setIsLoading) setIsLoading(true) - deleteConnection(id) - .then(res => { - if (res?.success) { - setConnections(prev => prev.filter(item => item.id !== id)) - if (selectedId === id) onSelect(null) - } else if (setSnackbar) { - setSnackbar({ show: true, msg: __('Failed to delete account', 'bit-integrations') }) - } - }) - .finally(() => { - if (setIsLoading) setIsLoading(false) - setConfirmId(null) - }) - } - - if (!connections?.length) return null - - return ( -
-
- {connections.map(conn => { - const displayName = conn.connection_name || conn.account_name - const email = - conn.account_name && conn.account_name !== displayName ? conn.account_name : '' - const photo = '' - - return ( -
- - - {!isInfo && ( -
- {onReauthorize && ( - - )} - {confirmId === conn.id ? ( -
-

{__('Are you sure?', 'bit-integrations')}

- - -
- ) : ( - - )} -
- )} -
- ) - })} -
-
- ) -} diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index 0c2ef97d4..b98e936f9 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import toast from 'react-hot-toast' +import CloseIcn from '../Icons/CloseIcn' import EditIcn from '../Icons/EditIcn' import TrashIcn from '../Icons/TrashIcn' import Table from '../components/Utilities/Table' @@ -205,11 +206,11 @@ export default function Connections() { if (editingId === conn.id) { return ( -
+
{ editValueRef.current = e.target.value @@ -219,17 +220,19 @@ export default function Connections() { />
) @@ -237,11 +240,11 @@ export default function Connections() { return (
- {value || '—'} + {value || '—'} @@ -308,12 +311,8 @@ export default function Connections() { btnClass="" /> -
+

{__('Connections', 'bit-integrations')}

- - {__('Showing', 'bit-integrations')} {filteredConnections.length} {__('of', 'bit-integrations')}{' '} - {connections.length} -
@@ -331,28 +330,32 @@ export default function Connections() { setBulkDelete={setBulkDelete} bulkDeleteLabel={__('Delete Connection', 'bit-integrations')} topLeftContent={ -
- +
+
+ +
- +
+ +
} /> diff --git a/frontend/src/resource/sass/app.scss b/frontend/src/resource/sass/app.scss index 9d1085798..5cd524f5a 100644 --- a/frontend/src/resource/sass/app.scss +++ b/frontend/src/resource/sass/app.scss @@ -7391,20 +7391,42 @@ _:-ms-fullscreen, padding-left: 10px; } + .connections-toolbar { + align-items: center; + justify-content: start; + gap: 12px; + flex-wrap: wrap; + } + + .connections-toolbar-meta { + align-items: center; + gap: 8px; + } + .connections-count-txt { - font-size: 13px; + display: inline-flex; + align-items: center; + font-size: 12px; + font-weight: 600; color: #5f6d8a; - align-self: center; + line-height: 1; + border: 1px solid #dce4f3; + border-radius: 999px; + background: #f3f6fd; + padding: 7px 10px; } .connections-table-filters { align-items: center; gap: 8px; flex-wrap: wrap; + min-width: 220px; + flex: 1 1 220px; } .connections-filter-select { width: 220px; + margin: 0 !important; } .connections-app-tag { @@ -7436,40 +7458,104 @@ _:-ms-fullscreen, .connections-name-cell { line-height: 1.35; overflow: visible !important; + white-space: normal !important; position: relative; - z-index: 20; + z-index: 0; + padding-top: 6px !important; + padding-bottom: 6px !important; } .connections-name-row { + align-items: center; + justify-content: space-between; gap: 6px; min-width: 0; + width: 100%; + position: relative; + z-index: 1; - & > span { + .connections-name-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } - .connections-name-row .tooltip { - position: relative; - z-index: 30; - } + .connections-rename-btn { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + border-radius: 7px; + background: transparent; + color: #9246f7; + margin: 0; + + &:hover { + background: #efe4ff; + color: #6f26dc; + } - .connections-name-row .tooltip::after { - z-index: 1200; + &:active { + background: #e5d6ff; + } } .connections-edit-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; width: 100%; align-items: center; gap: 6px; + } + + .connections-edit-input { + min-width: 0 !important; + margin: 0 !important; + min-height: 34px !important; + padding: 6px 10px !important; + font-size: 13px !important; + } + + .connections-edit-btn { + margin: 0 !important; + min-height: 28px; + font-size: 11px; + line-height: 1.1; + border-radius: 6px; + padding: 5px 8px; + } + + .connections-edit-cancel-btn { + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + margin: 0 !important; + border-radius: 7px; + background: #f2f4fa; + color: #6a7693; + border: 1px solid #e0e7f3; - .btcd-paper-inp { - min-width: 140px; + &:hover { + background: #e8edf8; + color: #4c5979; + } + + &:active { + background: #dfe6f3; } } + .connections-edit-cancel-icn { + display: block; + margin: 0 auto; + } + + .connections-edit-cancel-icn .svg-icn { + stroke: currentcolor; + } + .connections-action-cell { justify-content: center; width: 100%; @@ -7484,8 +7570,15 @@ _:-ms-fullscreen, @media only screen and (max-width: 767px) { #connections-page { + .connections-toolbar { + width: 100%; + align-items: stretch; + } + .connections-table-filters { width: 100%; + min-width: 0; + flex: 1 1 auto; } .connections-filter-select { @@ -7493,8 +7586,9 @@ _:-ms-fullscreen, max-width: none; } - .connections-count-txt { - display: none; + .connections-toolbar-meta { + width: 100%; + justify-content: space-between; } } } From c606826854ed56541d24639967f069ab92a6d90e Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 16:01:33 +0600 Subject: [PATCH 53/58] feat: update database version to 1.2 in Config and main plugin file --- backend/Config.php | 2 +- bitwpfi.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Config.php b/backend/Config.php index ed81f15b7..88221f9b4 100644 --- a/backend/Config.php +++ b/backend/Config.php @@ -24,7 +24,7 @@ class Config public const VERSION = '2.8.5'; - public const DB_VERSION = '1.3'; + public const DB_VERSION = '1.2'; public const REQUIRED_PHP_VERSION = '7.4'; diff --git a/bitwpfi.php b/bitwpfi.php index 33ae59f19..529a9298b 100644 --- a/bitwpfi.php +++ b/bitwpfi.php @@ -25,7 +25,7 @@ * @deprecated since version 2.7.8. use Config::DB_VERSION instead. */ global $btcbi_db_version; -$btcbi_db_version = '1.1'; +$btcbi_db_version = '1.2'; // Define most essential constants. /** From ffee57aea522344dff79175d33b60bc9dc80e691 Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 16:08:10 +0600 Subject: [PATCH 54/58] feat: remove GoogleSheetInfo component and its related functionality --- .../GoogleSheet/GoogleSheetInfo.jsx | 70 ------------------- 1 file changed, 70 deletions(-) delete mode 100644 frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetInfo.jsx diff --git a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetInfo.jsx b/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetInfo.jsx deleted file mode 100644 index 70b37379a..000000000 --- a/frontend/src/components/AllIntegrations/GoogleSheet/GoogleSheetInfo.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useState } from 'react' -import bitsFetch from '../../../Utils/bitsFetch' -import { __ } from '../../../Utils/i18nwrap' -import AuthorizationAccountList from '../../OneClickRadioComponents/AuthorizationAccountList' - -export default function GoogleSheetInfo({ sheetConf, isInfo }) { - const [authData, setAuthData] = useState([]) - - useEffect(() => { - const queryParams = { - id: sheetConf.authId - } - bitsFetch(null, 'auth/getbyId', queryParams, 'GET').then(res => { - if (res.success) { - if (res.data.data.length > 0) { - setAuthData(res.data.data) - } - } - }) - }, []) - return ( -
- {sheetConf.tokenDetails.selectedAuthType == 'Custom Authorization' && ( -
-

- Account Details (Custom Authorization){' '} -

-
- {__('Client id:', 'bit-integrations')} -
- - -
- {__('Client secret:', 'bit-integrations')} -
- -
- )} - {sheetConf.tokenDetails.selectedAuthType == 'One Click Authorization' && authData.length !== 0 && ( -
-

- Account Details (One Click Authorization){' '} -

- -
- )} - {sheetConf.tokenDetails.selectedAuthType == 'One Click Authorization' && authData.length === 0 && ( -
-

- The Authorized Account Has been Deleted. (One Click Authorization){' '} -

-
- )} -
- ) -} From 8d343fd08954bc1c0547f7e93d167f2988f4795b Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 16:34:11 +0600 Subject: [PATCH 55/58] feat: implement linked integrations handling in Connections component and enhance delete error messaging --- backend/controller/ConnectionController.php | 69 ++++++++++++++++++ frontend/src/pages/Connections.jsx | 80 +++++++++++++++++++-- 2 files changed, 142 insertions(+), 7 deletions(-) diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index ef7ffca3b..2c8525f4e 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -10,6 +10,7 @@ use BitApps\Integrations\Authorization\AuthorizationType; use BitApps\Integrations\Authorization\Support\AuthDataCodec; use BitApps\Integrations\Core\Database\ConnectionModel; +use BitApps\Integrations\Core\Database\FlowModel; use BitApps\Integrations\Core\Util\Capabilities; use BitApps\Integrations\Core\Util\Helper; use BitApps\Integrations\Core\Util\HttpHelper; @@ -297,6 +298,27 @@ public function delete($request) wp_send_json_error(__('Connection id is required', 'bit-integrations')); } + $linkedIntegrations = $this->getLinkedIntegrations($id); + + if (is_wp_error($linkedIntegrations)) { + wp_send_json_error($linkedIntegrations->get_error_message()); + } + + if (!empty($linkedIntegrations)) { + $linkedIntegrationCount = \count($linkedIntegrations); + wp_send_json_error( + [ + 'message' => wp_sprintf( + __('Connection is linked with %d integration(s). Remove this connection from those integrations before deleting.', 'bit-integrations'), + $linkedIntegrationCount + ), + 'linked_integrations' => $linkedIntegrations, + 'linked_count' => $linkedIntegrationCount, + ], + 409 + ); + } + $result = (new ConnectionModel())->delete(['id' => $id]); if (is_wp_error($result)) { @@ -717,6 +739,53 @@ private function normalizeHeaders($value): array return $headers; } + private function getLinkedIntegrations(int $connectionId) + { + $flows = (new FlowModel())->get(['id', 'name', 'flow_details']); + + if (is_wp_error($flows)) { + return $flows; + } + + if (empty($flows)) { + return []; + } + + $linkedIntegrations = []; + + foreach ($flows as $flow) { + $flowDetails = json_decode((string) ($flow->flow_details ?? ''), true); + + if (!\is_array($flowDetails)) { + continue; + } + + if ($this->extractConnectionIdFromFlowDetails($flowDetails) !== $connectionId) { + continue; + } + + $linkedIntegrations[] = [ + 'id' => absint($flow->id ?? 0), + 'name' => sanitize_text_field((string) ($flow->name ?? '')), + ]; + } + + return $linkedIntegrations; + } + + private function extractConnectionIdFromFlowDetails(array $flowDetails): int + { + foreach (['connection_id', 'connectionId'] as $key) { + if (!isset($flowDetails[$key]) || $flowDetails[$key] === '') { + continue; + } + + return absint($flowDetails[$key]); + } + + return 0; + } + private function normalizeId($request): int { if (is_numeric($request)) { diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index b98e936f9..e8c6100fa 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -13,6 +13,45 @@ import { import { __ } from '../Utils/i18nwrap' export default function Connections() { + const getLinkedIntegrationSummary = useCallback(res => { + const linkedIntegrations = Array.isArray(res?.data?.linked_integrations) + ? res.data.linked_integrations + : [] + + if (linkedIntegrations.length < 1) { + return '' + } + + const previewLimit = 5 + const previewNames = linkedIntegrations + .slice(0, previewLimit) + .map(item => item?.name || `#${item?.id || ''}`) + .filter(Boolean) + + if (previewNames.length < 1) { + return '' + } + + const extraCount = linkedIntegrations.length - previewNames.length + const extrasText = extraCount > 0 ? ` ${__('and', 'bit-integrations')} ${extraCount} ${__('more', 'bit-integrations')}` : '' + + return `${__('Linked integrations:', 'bit-integrations')} ${previewNames.join(', ')}${extrasText}.` + }, []) + + const getDeleteErrorMessage = useCallback(res => { + const linkedSummary = getLinkedIntegrationSummary(res) + + if (typeof res?.data?.message === 'string' && res.data.message.trim() !== '') { + return linkedSummary ? `${res.data.message} ${linkedSummary}` : res.data.message + } + + if (typeof res?.data === 'string' && res.data.trim() !== '') { + return res.data + } + + return linkedSummary || __('Failed to delete', 'bit-integrations') + }, [getLinkedIntegrationSummary]) + const [connections, setConnections] = useState([]) const [isLoading, setIsLoading] = useState(true) const [filterApp, setFilterApp] = useState('') @@ -119,7 +158,9 @@ export default function Connections() { setIsConfirmPending(true) const promise = deleteConnection(id).then(res => { - if (!res?.success) throw new Error('delete_failed') + if (!res?.success) { + throw new Error(getDeleteErrorMessage(res)) + } setConnections(prev => prev.filter(c => c.id !== id)) return __('Connection deleted', 'bit-integrations') }) @@ -127,11 +168,11 @@ export default function Connections() { toast .promise(promise, { success: msg => msg, - error: __('Failed to delete', 'bit-integrations'), + error: error => error?.message || __('Failed to delete', 'bit-integrations'), loading: __('Deleting...', 'bit-integrations') }) .finally(() => setIsConfirmPending(false)) - }, [deletingId, isConfirmPending]) + }, [deletingId, isConfirmPending, getDeleteErrorMessage]) const setBulkDelete = useCallback(rows => { const ids = [] @@ -153,7 +194,9 @@ export default function Connections() { const promise = Promise.allSettled( ids.map(id => deleteConnection(id).then(res => { - if (!res?.success) throw new Error('bulk_delete_failed') + if (!res?.success) { + throw new Error(getDeleteErrorMessage(res)) + } return id }) ) @@ -169,6 +212,19 @@ export default function Connections() { const failedCount = results.filter(r => r.status === 'rejected').length if (failedCount > 0) { + const uniqueFailureMessages = [ + ...new Set( + results + .filter(r => r.status === 'rejected') + .map(r => r.reason?.message) + .filter(Boolean) + ) + ] + + if (failedCount === ids.length && uniqueFailureMessages.length === 1) { + throw new Error(uniqueFailureMessages[0]) + } + throw new Error( failedCount === ids.length ? 'bulk_delete_failed' : 'bulk_delete_partial' ) @@ -181,10 +237,20 @@ export default function Connections() { toast.promise(promise, { success: msg => msg, - error: __('Failed to delete', 'bit-integrations'), + error: error => { + if (error?.message === 'bulk_delete_partial') { + return __('Some selected connections could not be deleted.', 'bit-integrations') + } + + if (error?.message === 'bulk_delete_failed') { + return __('Failed to delete', 'bit-integrations') + } + + return error?.message || __('Failed to delete', 'bit-integrations') + }, loading: __('Deleting...', 'bit-integrations') }) - }, []) + }, [getDeleteErrorMessage]) const columns = useMemo( () => [ @@ -302,7 +368,7 @@ export default function Connections() { Date: Fri, 15 May 2026 16:41:45 +0600 Subject: [PATCH 56/58] feat: add support for linked integrations in Connections component and API --- backend/controller/ConnectionController.php | 98 ++++++++++++++++----- frontend/src/Utils/connectionApi.js | 11 ++- frontend/src/pages/Connections.jsx | 81 +++++++++-------- 3 files changed, 132 insertions(+), 58 deletions(-) diff --git a/backend/controller/ConnectionController.php b/backend/controller/ConnectionController.php index 2c8525f4e..d6f0f7b7e 100644 --- a/backend/controller/ConnectionController.php +++ b/backend/controller/ConnectionController.php @@ -49,6 +49,7 @@ public function index($request) $this->guard(); $appSlug = $this->sanitizeScalar($request->app_slug ?? ''); + $includeLinkedIntegrations = $this->isTruthy($request->include_linked_integrations ?? false); $condition = ['status' => ConnectionModel::STATUS_VERIFIED]; if ($appSlug !== '') { @@ -61,10 +62,24 @@ public function index($request) wp_send_json_success(['data' => []]); } + $linkedIntegrationMap = []; + + if ($includeLinkedIntegrations) { + $linkedIntegrationMap = $this->buildLinkedIntegrationMap(); + + if (is_wp_error($linkedIntegrationMap)) { + wp_send_json_error($linkedIntegrationMap->get_error_message()); + } + } + $payload = []; foreach ($rows as $row) { - $payload[] = $this->formatListRow($row); + $linkedIntegrations = $includeLinkedIntegrations + ? ($linkedIntegrationMap[(int) ($row->id ?? 0)] ?? []) + : null; + + $payload[] = $this->formatListRow($row, $linkedIntegrations); } wp_send_json_success(['data' => $payload]); @@ -637,9 +652,9 @@ private function formatRow($row): array /** * List responses should not expose decrypted credential payloads. */ - private function formatListRow($row): array + private function formatListRow($row, ?array $linkedIntegrations = null): array { - return [ + $payload = [ 'id' => (int) $row->id, 'app_slug' => $row->app_slug, 'auth_type' => (string) ($row->auth_type ?? ''), @@ -650,6 +665,13 @@ private function formatListRow($row): array 'created_at' => $row->created_at ?? null, 'updated_at' => $row->updated_at ?? null, ]; + + if (\is_array($linkedIntegrations)) { + $payload['linked_integrations'] = $linkedIntegrations; + $payload['linked_count'] = \count($linkedIntegrations); + } + + return $payload; } private function resolveEncryptKeys($request): array @@ -740,6 +762,30 @@ private function normalizeHeaders($value): array } private function getLinkedIntegrations(int $connectionId) + { + $linkedIntegrationMap = $this->buildLinkedIntegrationMap(); + + if (is_wp_error($linkedIntegrationMap)) { + return $linkedIntegrationMap; + } + + return $linkedIntegrationMap[$connectionId] ?? []; + } + + private function extractConnectionIdFromFlowDetails(array $flowDetails): int + { + foreach (['connection_id', 'connectionId'] as $key) { + if (!isset($flowDetails[$key]) || $flowDetails[$key] === '') { + continue; + } + + return absint($flowDetails[$key]); + } + + return 0; + } + + private function buildLinkedIntegrationMap() { $flows = (new FlowModel())->get(['id', 'name', 'flow_details']); @@ -751,7 +797,7 @@ private function getLinkedIntegrations(int $connectionId) return []; } - $linkedIntegrations = []; + $linkedIntegrationMap = []; foreach ($flows as $flow) { $flowDetails = json_decode((string) ($flow->flow_details ?? ''), true); @@ -760,30 +806,23 @@ private function getLinkedIntegrations(int $connectionId) continue; } - if ($this->extractConnectionIdFromFlowDetails($flowDetails) !== $connectionId) { + $connectionId = $this->extractConnectionIdFromFlowDetails($flowDetails); + + if ($connectionId < 1) { continue; } - $linkedIntegrations[] = [ + if (!isset($linkedIntegrationMap[$connectionId])) { + $linkedIntegrationMap[$connectionId] = []; + } + + $linkedIntegrationMap[$connectionId][] = [ 'id' => absint($flow->id ?? 0), 'name' => sanitize_text_field((string) ($flow->name ?? '')), ]; } - return $linkedIntegrations; - } - - private function extractConnectionIdFromFlowDetails(array $flowDetails): int - { - foreach (['connection_id', 'connectionId'] as $key) { - if (!isset($flowDetails[$key]) || $flowDetails[$key] === '') { - continue; - } - - return absint($flowDetails[$key]); - } - - return 0; + return $linkedIntegrationMap; } private function normalizeId($request): int @@ -820,6 +859,25 @@ private function sanitizeScalar($value): string return sanitize_text_field((string) $value); } + private function isTruthy($value): bool + { + if (\is_bool($value)) { + return $value; + } + + if (is_numeric($value)) { + return absint($value) === 1; + } + + if (!\is_scalar($value)) { + return false; + } + + $value = strtolower(trim((string) $value)); + + return \in_array($value, ['1', 'true', 'yes', 'on'], true); + } + private function guard(): void { if ( diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index 067e830c1..ed9095e0a 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -5,8 +5,15 @@ import bitsFetch from './bitsFetch' * * @param {string} appSlug */ -export const listConnections = appSlug => - bitsFetch(null, 'connections/list', { app_slug: appSlug }, 'GET') +export const listConnections = (appSlug, options = {}) => { + const query = { app_slug: appSlug } + + if (options?.includeLinkedIntegrations) { + query.include_linked_integrations = 1 + } + + return bitsFetch(null, 'connections/list', query, 'GET') +} export const getConnection = id => bitsFetch(null, 'connections/get', { id }, 'GET') diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index e8c6100fa..7b599c42b 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -10,47 +10,29 @@ import { listConnections, updateConnection } from '../Utils/connectionApi' -import { __ } from '../Utils/i18nwrap' +import { __, sprintf } from '../Utils/i18nwrap' export default function Connections() { - const getLinkedIntegrationSummary = useCallback(res => { - const linkedIntegrations = Array.isArray(res?.data?.linked_integrations) - ? res.data.linked_integrations - : [] - - if (linkedIntegrations.length < 1) { - return '' - } - - const previewLimit = 5 - const previewNames = linkedIntegrations - .slice(0, previewLimit) - .map(item => item?.name || `#${item?.id || ''}`) - .filter(Boolean) + const getDeleteErrorMessage = useCallback(res => { + const linkedCount = Number(res?.data?.linked_count || 0) - if (previewNames.length < 1) { - return '' + if (linkedCount > 0) { + return sprintf( + __('Connection is used in %d integrations. Unlink first, then delete.', 'bit-integrations'), + linkedCount + ) } - const extraCount = linkedIntegrations.length - previewNames.length - const extrasText = extraCount > 0 ? ` ${__('and', 'bit-integrations')} ${extraCount} ${__('more', 'bit-integrations')}` : '' - - return `${__('Linked integrations:', 'bit-integrations')} ${previewNames.join(', ')}${extrasText}.` - }, []) - - const getDeleteErrorMessage = useCallback(res => { - const linkedSummary = getLinkedIntegrationSummary(res) - if (typeof res?.data?.message === 'string' && res.data.message.trim() !== '') { - return linkedSummary ? `${res.data.message} ${linkedSummary}` : res.data.message + return res.data.message } if (typeof res?.data === 'string' && res.data.trim() !== '') { return res.data } - return linkedSummary || __('Failed to delete', 'bit-integrations') - }, [getLinkedIntegrationSummary]) + return __('Failed to delete', 'bit-integrations') + }, []) const [connections, setConnections] = useState([]) const [isLoading, setIsLoading] = useState(true) @@ -64,7 +46,7 @@ export default function Connections() { const fetchConnections = useCallback(() => { setIsLoading(true) - listConnections('') + listConnections('', { includeLinkedIntegrations: true }) .then(res => { if (res?.success && Array.isArray(res.data?.data)) { setConnections(res.data.data) @@ -255,7 +237,7 @@ export default function Connections() { const columns = useMemo( () => [ { - Header: __('App', 'bit-integrations'), + Header: __('Action', 'bit-integrations'), accessor: 'app_slug', width: 130, minWidth: 90, @@ -319,11 +301,38 @@ export default function Connections() { } }, { - Header: __('Account', 'bit-integrations'), - accessor: 'account_name', - width: 180, - minWidth: 120, - Cell: ({ value }) => value || '—' + Header: __('Linked Integrations', 'bit-integrations'), + accessor: 'linked_count', + width: 250, + minWidth: 180, + Cell: ({ row }) => { + const linkedIntegrations = Array.isArray(row?.original?.linked_integrations) + ? row.original.linked_integrations + : [] + + if (linkedIntegrations.length < 1) { + return '—' + } + + const previewLimit = 3 + const previewNames = linkedIntegrations + .slice(0, previewLimit) + .map(item => item?.name || `#${item?.id || ''}`) + .filter(Boolean) + + const extraCount = linkedIntegrations.length - previewNames.length + const linkedText = extraCount > 0 ? `${previewNames.join(', ')} +${extraCount}` : previewNames.join(', ') + const tooltipText = linkedIntegrations + .map(item => item?.name || `#${item?.id || ''}`) + .filter(Boolean) + .join(', ') + + return ( + + {linkedText} + + ) + } }, { Header: __('Auth Type', 'bit-integrations'), From 8779e6986f1ff36ec5511540c02eee0549a0708f Mon Sep 17 00:00:00 2001 From: Rishad Alam <101513331+RishadAlam@users.noreply.github.com> Date: Fri, 15 May 2026 16:59:31 +0600 Subject: [PATCH 57/58] fix: improve access token validation in GoogleSheetController --- backend/Actions/Fabman/RecordApiHelper.php | 2 -- backend/Actions/GoogleSheet/GoogleSheetController.php | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/Actions/Fabman/RecordApiHelper.php b/backend/Actions/Fabman/RecordApiHelper.php index e8734db84..01adc5199 100644 --- a/backend/Actions/Fabman/RecordApiHelper.php +++ b/backend/Actions/Fabman/RecordApiHelper.php @@ -170,8 +170,6 @@ private function createMember($data) return $apiResponse; } - // $errorMessage = isset($apiResponse->error) ? $apiResponse->error : \__('Failed to create member', 'bit-integrations'); - return new WP_Error('API_ERROR', __('Failed to create member', 'bit-integrations'), $apiResponse); } diff --git a/backend/Actions/GoogleSheet/GoogleSheetController.php b/backend/Actions/GoogleSheet/GoogleSheetController.php index 81332ca68..0a73c90ed 100644 --- a/backend/Actions/GoogleSheet/GoogleSheetController.php +++ b/backend/Actions/GoogleSheet/GoogleSheetController.php @@ -119,7 +119,7 @@ public static function refreshSpreadsheetsAjaxHelper($queryParams) $response = []; if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); - if (!empty($response['tokenDetails']->access_token)) { + if ($response['tokenDetails'] && !empty($response['tokenDetails']->access_token)) { $authorizationHeader['Authorization'] = 'Bearer ' . $response['tokenDetails']->access_token; } } @@ -177,7 +177,7 @@ public static function refreshWorksheetsAjaxHelper($queryParams) $response = []; if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); - if (!empty($response['tokenDetails']->access_token)) { + if ($response['tokenDetails'] && !empty($response['tokenDetails']->access_token)) { $queryParams->tokenDetails = $response['tokenDetails']; } } @@ -232,7 +232,7 @@ public static function refreshWorksheetHeadersAjaxHelper($queryParams) $response = []; if (!$isConnectionAuth && (\intval($queryParams->tokenDetails->generates_on) + (55 * 60)) < time()) { $response['tokenDetails'] = GoogleSheetController::refreshAccessToken($queryParams); - if (!empty($response['tokenDetails']->access_token)) { + if ($response['tokenDetails'] && !empty($response['tokenDetails']->access_token)) { $queryParams->tokenDetails = $response['tokenDetails']; } } From e485a6f55aa6161d105ae7c41da02fac6abe2247 Mon Sep 17 00:00:00 2001 From: Rishad Alam Date: Mon, 29 Jun 2026 11:16:35 +0600 Subject: [PATCH 58/58] refactor: format the frontend files by pretty fix --- frontend/src/Utils/connectionApi.js | 3 +- frontend/src/Utils/oauthHelper.js | 47 +- .../ACPT/ACPTAuthorization.jsx | 6 +- .../Acumbamail/AcumbamailAuthorization.jsx | 4 +- .../Affiliate/AffiliateAuthorization.jsx | 5 +- .../AgiledCRM/AgiledIntegLayout.jsx | 4 +- .../Airtable/AirtableIntegLayout.jsx | 4 +- .../CampaignMonitorCommonFunc.js | 8 +- .../ClinchPad/ClinchPadCommonFunc.js | 4 +- .../CompanyHub/CompanyHubAuthorization.jsx | 4 +- .../ConstantContactAuthorization.jsx | 10 +- .../ConvertKit/ConvertKitAuthorization.jsx | 4 +- .../DirectIq/DirectIqAuthorization.jsx | 4 +- .../Discord/DiscordAuthorization.jsx | 4 +- .../Dokan/DokanAuthorization.jsx | 8 +- .../Dokan/dokanCommonFunctions.js | 1 - .../AllIntegrations/Drip/DripIntegLayout.jsx | 9 +- .../Dropbox/DropboxAuthorization.jsx | 8 +- .../ElasticEmailAuthorization.jsx | 2 +- .../EmailOctopus/EmailOctopusIntegLayout.jsx | 4 +- .../Fabman/FabmanAuthorization.jsx | 10 +- .../Flowlu/FlowluAuthorization.jsx | 8 +- .../FluentCRM/FluentCrmAuthorization.jsx | 5 +- .../FreshSales/FreshSalesAuthorization.jsx | 18 +- .../Freshdesk/FreshdeskAuthorization.jsx | 36 +- .../GamiPress/GamiPressAuthorization.jsx | 5 +- .../GiveWp/GiveWpAuthorization.jsx | 8 +- .../GoogleSheet/GoogleSheetAuthorization.jsx | 8 +- .../Gravitec/GravitecAuthorization.jsx | 8 +- .../AllIntegrations/Groundhogg/Groundhogg.jsx | 4 +- .../Groundhogg/GroundhoggAuthorization.jsx | 4 +- .../Groundhogg/GroundhoggCommonFunc.js | 5 +- .../Hubspot/HubspotAuthorization.jsx | 8 +- .../JetEngine/JetEngineAuthorization.jsx | 5 +- .../JetEngine/jetEngineCommonFunctions.js | 1 - .../AllIntegrations/Keap/KeapCommonFunc.js | 29 +- .../Klaviyo/KlaviyoAuthorization.jsx | 8 +- .../LearnDash/LearnDashAuthorization.jsx | 5 +- .../LifterLms/LifterLmsAuthorization.jsx | 5 +- .../components/AllIntegrations/Line/Line.jsx | 7 +- .../LionDesk/LionDeskAuthorization.jsx | 8 +- .../Livestorm/LivestormAuthorization.jsx | 6 +- .../Livestorm/LivestormCommonFunc.js | 9 +- .../AllIntegrations/MailChimp/MailChimp.jsx | 6 +- .../MailChimp/MailChimpAuthorization.jsx | 4 +- .../MailMint/MailMintAuthorization.jsx | 17 +- .../MailRelay/MailRelayIntegLayout.jsx | 8 +- .../Mailjet/MailjetCommonFunc.js | 10 +- .../Mailster/MailsterAuthorization.jsx | 8 +- .../Mailster/MailsterCommonFunc.js | 1 - .../MasterStudyLmsAuthorization.jsx | 30 +- .../Memberpress/MemberpressAuthorization.jsx | 22 +- .../Moosend/MoosendCommonFunc.js | 4 +- .../MoxieCRM/MoxieCRMAuthorization.jsx | 8 +- .../Newsletter/NewsletterCommonFunc.js | 1 - .../Nimble/NimbleCommonFunc.js | 4 +- .../NotificationX/NotificationXCommonFunc.js | 56 +- .../OmniSend/OmniSendAuthorization.jsx | 8 +- .../OneDrive/OneDriveAuthorization.jsx | 8 +- .../OneHashCRM/OneHashCRMAuthorization.jsx | 2 +- .../PCloud/PCloudAuthorization.jsx | 8 +- .../PCloud/PCloudCommonFunc.js | 4 +- .../PaidMembershipProAuthorization.jsx | 5 +- .../PeepSo/PeepSoAuthorization.jsx | 8 +- .../RestrictContentAuthorization.jsx | 25 +- .../Selzy/SelzyAuthorization.jsx | 10 +- .../SendFox/SendFoxAuthorization.jsx | 8 +- .../SendPulse/SendPulseAuthorization.jsx | 4 +- .../SendPulse/SendPulseCommonFunc.js | 2 +- .../SendinBlue/SendinBlueAuthorization.jsx | 4 +- .../Slack/SlackAuthorization.jsx | 4 +- .../AllIntegrations/Slack/SlackCommonFunc.js | 7 +- .../Slack/SlackIntegLayout.jsx | 8 +- .../SliceWp/SliceWpAuthorization.jsx | 8 +- .../SmartSuite/SmartSuiteAuthorization.jsx | 4 +- .../SureCart/SureCartAuthorization.jsx | 8 +- .../SureMembers/SureMembersAuthorization.jsx | 5 +- .../SureMembers/SureMembersCommonFunc.js | 1 - .../SystemeIO/SystemeIOCommonFunc.js | 4 +- .../Telegram/TelegramAuthorization.jsx | 4 +- .../theEventsCalendarCommonFunctions.js | 1 - .../AllIntegrations/Trello/Trello.jsx | 6 +- .../TutorLms/TutorLmsAuthorization.jsx | 8 +- .../UltimateAffiliateProAuthorization.jsx | 17 +- .../UserRegistrationMembershipCommonFunc.js | 1 - .../Voxel/VoxelAuthorization.jsx | 8 +- .../Voxel/VoxelCommonFunctions.js | 1 - .../WPCafe/WPCafeAuthorization.jsx | 8 +- .../WPCafe/WPCafeCommonFunc.js | 1 - .../WPCoursewareAuthorization.jsx | 5 +- .../WPForo/WPForoAuthorization.jsx | 8 +- .../WPForo/WPForoCommonFunc.js | 1 - .../WeDocs/WeDocsAuthorization.jsx | 8 +- .../WeDocs/WeDocsCommonFunc.js | 1 - .../WhatsApp/WhatsAppCommonFunc.js | 2 +- .../WishlistMemberCommonFunc.js | 1 - .../WooCommerce/WooCommerceAuthorization.jsx | 13 +- .../WpErp/WpErpAuthorization.jsx | 8 +- .../Zendesk/ZendeskAuthorization.jsx | 8 +- .../ZohoAnalyticsAuthorization.jsx | 4 +- .../ZohoBigin/ZohoBiginCommonFunc.js | 13 +- .../ZohoCRM/ZohoCRMCommonFunc.js | 13 +- .../ZohoCampaignsAuthorization.jsx | 4 +- .../ZohoCampaigns/ZohoCampaignsCommonFunc.js | 5 +- .../ZohoCreator/ZohoCreatorCommonFunc.js | 3 +- .../ZohoDesk/ZohoDeskCommonFunc.js | 3 +- .../ZohoMarketingHub/ZohoMarketingHub.jsx | 6 +- .../ZohoMarketingHubCommonFunc.js | 5 +- .../ZohoRecruit/ZohoRecruitCommonFunc.js | 9 +- .../ZohoSheet/ZohoSheetAuthorization.jsx | 4 +- .../components/AllIntegrations/Zoom/Zoom.jsx | 7 +- .../Zoom/ZoomAuthorization.jsx | 8 +- .../Connections/AddNewConnection.jsx | 8 +- .../components/Connections/ApiConnection.jsx | 700 +++++++++--------- .../components/Connections/Authorization.jsx | 55 +- .../Connections/Oauth1Connection.jsx | 26 +- .../Connections/Oauth2Connection.jsx | 46 +- frontend/src/hooks/useFetch.js | 2 +- frontend/src/pages/Connections.jsx | 139 ++-- 119 files changed, 836 insertions(+), 1011 deletions(-) diff --git a/frontend/src/Utils/connectionApi.js b/frontend/src/Utils/connectionApi.js index ed9095e0a..d2c6fbaee 100644 --- a/frontend/src/Utils/connectionApi.js +++ b/frontend/src/Utils/connectionApi.js @@ -21,7 +21,8 @@ export const authorizeConnection = payload => bitsFetch(payload, 'connections/au export const oauthConnectionExchange = payload => bitsFetch(payload, 'connections/oauth2/exchange') -export const verifyPluginActivation = payload => bitsFetch(payload, 'connections/verify-plugin-activation') +export const verifyPluginActivation = payload => + bitsFetch(payload, 'connections/verify-plugin-activation') export const saveConnection = payload => bitsFetch(payload, 'connections/save') diff --git a/frontend/src/Utils/oauthHelper.js b/frontend/src/Utils/oauthHelper.js index 93bd198fd..89db9d5f5 100644 --- a/frontend/src/Utils/oauthHelper.js +++ b/frontend/src/Utils/oauthHelper.js @@ -45,8 +45,7 @@ const randomToken = (bytes = 16) => { return base64UrlEncode(arr) } -const getChannelName = channelKey => - channelKey ? `${OAUTH_CHANNEL}:${channelKey}` : OAUTH_CHANNEL +const getChannelName = channelKey => (channelKey ? `${OAUTH_CHANNEL}:${channelKey}` : OAUTH_CHANNEL) export const createOauthChannelKey = () => randomToken(18) @@ -99,9 +98,7 @@ export const openOauthPopup = ( let resolved = false const channel = new BroadcastChannel(getChannelName(channelKey)) const fallbackChannel = - includeLegacyFallback && channelKey - ? new BroadcastChannel(OAUTH_CHANNEL) - : null + includeLegacyFallback && channelKey ? new BroadcastChannel(OAUTH_CHANNEL) : null const cleanup = () => { channel.close() @@ -113,7 +110,9 @@ export const openOauthPopup = ( if (resolved) return resolved = true cleanup() - try { popup.close() } catch (_) {} // eslint-disable-line no-unused-vars, no-empty + try { + popup.close() + } catch (_) {} // eslint-disable-line no-unused-vars, no-empty resolve(event.data || {}) } channel.onmessage = resolveMessage @@ -180,15 +179,15 @@ export const exchangeAuthCodeForToken = ({ if (codeVerifier) bodyParams.code_verifier = codeVerifier return oauthConnectionExchange({ - url: tokenEndpoint.url, - method: tokenEndpoint.method || 'POST', - body_params: bodyParams, - headers: { - ...(tokenEndpoint?.headers || {}), - ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) - }, - ssl_verify: sslVerify - }) + url: tokenEndpoint.url, + method: tokenEndpoint.method || 'POST', + body_params: bodyParams, + headers: { + ...(tokenEndpoint?.headers || {}), + ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) + }, + ssl_verify: sslVerify + }) } export const exchangeClientCredentialsForToken = ({ @@ -208,13 +207,13 @@ export const exchangeClientCredentialsForToken = ({ if (scope) bodyParams.scope = scope return oauthConnectionExchange({ - url: tokenEndpoint.url, - method: tokenEndpoint.method || 'POST', - body_params: bodyParams, - headers: { - ...(tokenEndpoint?.headers || {}), - ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) - }, - ssl_verify: sslVerify - }) + url: tokenEndpoint.url, + method: tokenEndpoint.method || 'POST', + body_params: bodyParams, + headers: { + ...(tokenEndpoint?.headers || {}), + ...buildClientAuthHeaders({ clientId, clientSecret, clientAuthentication }) + }, + ssl_verify: sslVerify + }) } diff --git a/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx b/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx index d91d1ad87..ae85836f4 100644 --- a/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/ACPT/ACPTAuthorization.jsx @@ -7,9 +7,9 @@ export default function ACPTAuthorization({ acptConf, setAcptConf, step, setStep const note = ` ${__('Please note', 'bit-integrations')}

${__( - 'The secret key will no longer be displayed, so please take note of it. Eventually, you can regenerate your API keys.', - 'bit-integrations' - )}

+ 'The secret key will no longer be displayed, so please take note of it. Eventually, you can regenerate your API keys.', + 'bit-integrations' + )}

${__('To get API key-secret', 'bit-integrations')}

  • ${__('Go to the ACPT dashboard.', 'bit-integrations')}
  • diff --git a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx index 24738ddc9..b1fab293b 100644 --- a/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Acumbamail/AcumbamailAuthorization.jsx @@ -16,9 +16,7 @@ export default function AcumbamailAuthorization({ }) { const loadLists = useCallback( connectionId => { - const nextConf = connectionId - ? { ...acumbamailConf, connection_id: connectionId } - : acumbamailConf + const nextConf = connectionId ? { ...acumbamailConf, connection_id: connectionId } : acumbamailConf fetchAllList(nextConf, setAcumbamailConf, setIsLoading, setSnackbar) }, [acumbamailConf, setAcumbamailConf, setIsLoading, setSnackbar] diff --git a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx index 42e4c77d9..f7d403f20 100644 --- a/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Affiliate/AffiliateAuthorization.jsx @@ -22,7 +22,10 @@ export default function AffiliateAuthorization({ tutorialLinks={tutorialLinks?.affiliate || {}} authDetails={{ authType: AUTH_TYPES.WP_PLUGIN_CHECK, - pluginCheck: { checks: [{ type: 'plugin_file', value: 'affiliate-wp/affiliate-wp.php' }], logic: 'AND' } + pluginCheck: { + checks: [{ type: 'plugin_file', value: 'affiliate-wp/affiliate-wp.php' }], + logic: 'AND' + } }} noteDetails={{ note: __( diff --git a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx index b93c45bf6..90e82e223 100644 --- a/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/AgiledCRM/AgiledIntegLayout.jsx @@ -131,9 +131,7 @@ export default function AgiledIntegLayout({ singleSelect />

    diff --git a/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx b/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx index d27b38980..7b378b918 100644 --- a/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Gravitec/GravitecAuthorization.jsx @@ -3,13 +3,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function GravitecAuthorization({ - gravitecConf, - setGravitecConf, - step, - setStep, - isInfo -}) { +export default function GravitecAuthorization({ gravitecConf, setGravitecConf, step, setStep, isInfo }) { const note = `

    ${__('To Get App key & App Secret', 'bit-integrations')}

      diff --git a/frontend/src/components/AllIntegrations/Groundhogg/Groundhogg.jsx b/frontend/src/components/AllIntegrations/Groundhogg/Groundhogg.jsx index 0c4236bbc..d4b1a2320 100644 --- a/frontend/src/components/AllIntegrations/Groundhogg/Groundhogg.jsx +++ b/frontend/src/components/AllIntegrations/Groundhogg/Groundhogg.jsx @@ -117,9 +117,7 @@ function Groundhogg({ formFields, setFlow, flow, allIntegURL }) { style={{ ...(step === 2 && { width: 900, height: 'auto', overflow: 'visible' }) }}> - handleInput(e, groundhoggConf, setGroundhoggConf, setIsLoading, setSnackbar) - } + handleInput={e => handleInput(e, groundhoggConf, setGroundhoggConf, setIsLoading, setSnackbar)} groundhoggConf={groundhoggConf} setGroundhoggConf={setGroundhoggConf} isLoading={isLoading} diff --git a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggAuthorization.jsx b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggAuthorization.jsx index 7ef0d239f..e7fcb2047 100644 --- a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggAuthorization.jsx @@ -15,9 +15,7 @@ export default function GroundhoggAuthorization({ }) { const loadTags = useCallback( async connectionId => { - const nextConf = connectionId - ? { ...groundhoggConf, connection_id: connectionId } - : groundhoggConf + const nextConf = connectionId ? { ...groundhoggConf, connection_id: connectionId } : groundhoggConf await fetchAllTags(nextConf, setGroundhoggConf, setIsLoading) }, diff --git a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js index efad4ed40..5ac8cb5ab 100644 --- a/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Groundhogg/GroundhoggCommonFunc.js @@ -62,7 +62,10 @@ const buildAuthRequestParams = confTmp => } export const fetchAllTags = (groundhoggConf, setGroundhoggConf, setIsLoading, setSnackbar) => { - if (!groundhoggConf.connection_id && (!groundhoggConf.public_key || !groundhoggConf.token || !groundhoggConf.domainName)) { + if ( + !groundhoggConf.connection_id && + (!groundhoggConf.public_key || !groundhoggConf.token || !groundhoggConf.domainName) + ) { toast.error(__('Authorization data is missing', 'bit-integrations')) return } diff --git a/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx b/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx index d89421ad7..0f45a2bae 100644 --- a/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Hubspot/HubspotAuthorization.jsx @@ -6,13 +6,7 @@ import { __ } from '../../../Utils/i18nwrap' import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -export default function HubspotAuthorization({ - hubspotConf, - setHubspotConf, - step, - setstep, - isInfo -}) { +export default function HubspotAuthorization({ hubspotConf, setHubspotConf, step, setstep, isInfo }) { const note = `

      ${__('Step of generating Access Token:', 'bit-integrations')}

        diff --git a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx index a1b5a5134..7a6e637ce 100644 --- a/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/JetEngine/JetEngineAuthorization.jsx @@ -22,7 +22,10 @@ export default function JetEngineAuthorization({ tutorialLinks={tutorialLinks?.jetEngine || {}} authDetails={{ authType: AUTH_TYPES.WP_PLUGIN_CHECK, - pluginCheck: { checks: [{ type: 'plugin_file', value: 'jet-engine/jet-engine.php' }], logic: 'AND' } + pluginCheck: { + checks: [{ type: 'plugin_file', value: 'jet-engine/jet-engine.php' }], + logic: 'AND' + } }} noteDetails={{ note: __( diff --git a/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js b/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js index a6115dc48..5e3018caf 100644 --- a/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js +++ b/frontend/src/components/AllIntegrations/JetEngine/jetEngineCommonFunctions.js @@ -34,7 +34,6 @@ export const checkMappedFields = jetEngineConf => { return true } - export const getJetEngineOptions = ( route, actionOptions, diff --git a/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js b/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js index 2fceb5de8..5481b3c50 100644 --- a/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Keap/KeapCommonFunc.js @@ -55,23 +55,24 @@ export const getAllTags = (confTmp, setConf, setLoading) => { setLoading(true) const requestParams = { - ...buildAuthRequestParams(confTmp), + ...buildAuthRequestParams(confTmp) } - bitsFetch(requestParams, 'keap_fetch_all_tags').then(result => { - if (result && result.success) { - const newConf = { ...confTmp } - if (result.data) { - newConf.tags = result.data + bitsFetch(requestParams, 'keap_fetch_all_tags') + .then(result => { + if (result && result.success) { + const newConf = { ...confTmp } + if (result.data) { + newConf.tags = result.data + } + setConf(newConf) + setLoading(false) + toast.success(__('Tag Fetched Successfully', 'bit-integrations')) + return } - setConf(newConf) setLoading(false) - toast.success(__('Tag Fetched Successfully', 'bit-integrations')) - return - } - setLoading(false) - toast.error(__("Tag Couldn't Fetched Successfully", 'bit-integrations')) - }) + toast.error(__("Tag Couldn't Fetched Successfully", 'bit-integrations')) + }) .catch(() => { setLoading(false) toast.error(__("Tag Couldn't Fetched Successfully", 'bit-integrations')) @@ -83,7 +84,7 @@ export const refreshCustomFields = (id, confTmp, setConf, setIsLoading, setSnack const requestParams = { id: id, - ...buildAuthRequestParams(confTmp), + ...buildAuthRequestParams(confTmp) } bitsFetch(requestParams, 'keap_fetch_all_custom_fields').then(result => { diff --git a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx index 2851134c7..bbcd62216 100644 --- a/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Klaviyo/KlaviyoAuthorization.jsx @@ -5,13 +5,7 @@ import Authorization from '../../Connections/Authorization' import { getAllLists } from './KlaviyoCommonFunc' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -function KlaviyoAuthorization({ - klaviyoConf, - setKlaviyoConf, - step, - setStep, - isInfo -}) { +function KlaviyoAuthorization({ klaviyoConf, setKlaviyoConf, step, setStep, isInfo }) { const loadLists = useCallback( connectionId => { const nextConf = connectionId ? { ...klaviyoConf, connection_id: connectionId } : klaviyoConf diff --git a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx index 10cf37fb2..ec5036ee7 100644 --- a/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LearnDash/LearnDashAuthorization.jsx @@ -24,7 +24,10 @@ export default function LearnDashAuthorization({ authType: AUTH_TYPES.WP_PLUGIN_CHECK, pluginCheck: { groups: [ - { logic: 'AND', checks: [{ type: 'plugin_file', value: 'learndash-propanel/learndash_propanel.php' }] }, + { + logic: 'AND', + checks: [{ type: 'plugin_file', value: 'learndash-propanel/learndash_propanel.php' }] + }, { logic: 'AND', checks: [{ type: 'plugin_file', value: 'learndash/learndash.php' }] }, { logic: 'AND', checks: [{ type: 'plugin_file', value: 'sfwd-lms/sfwd_lms.php' }] } ], diff --git a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx index 72ef76d72..f2b1c4d73 100644 --- a/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/LifterLms/LifterLmsAuthorization.jsx @@ -22,7 +22,10 @@ export default function LifterLmsAuthorization({ tutorialLinks={tutorialLinks?.lifterLms || {}} authDetails={{ authType: AUTH_TYPES.WP_PLUGIN_CHECK, - pluginCheck: { checks: [{ type: 'plugin_file', value: 'lifterlms/lifterlms.php' }], logic: 'AND' } + pluginCheck: { + checks: [{ type: 'plugin_file', value: 'lifterlms/lifterlms.php' }], + logic: 'AND' + } }} noteDetails={{ note: __( diff --git a/frontend/src/components/AllIntegrations/Line/Line.jsx b/frontend/src/components/AllIntegrations/Line/Line.jsx index 0de42cfe1..647cef12c 100644 --- a/frontend/src/components/AllIntegrations/Line/Line.jsx +++ b/frontend/src/components/AllIntegrations/Line/Line.jsx @@ -149,12 +149,7 @@ function Line({ formFields, setFlow, flow, allIntegURL }) { {/* STEP 1 */} - + {/* STEP 2 */}
        ${__('Get Redirect URI, Client ID and Client Secret', 'bit-integrations')}
          diff --git a/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx b/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx index a048a864e..79f21c007 100644 --- a/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Livestorm/LivestormAuthorization.jsx @@ -58,7 +58,11 @@ export default function LivestormAuthorization({ method: 'GET', key: 'X-BI-Auth', addTo: 'header', - headers: { Authorization: '{api_key}', Accept: 'application/json', 'Content-Type': 'application/json' } + headers: { + Authorization: '{api_key}', + Accept: 'application/json', + 'Content-Type': 'application/json' + } }} noteDetails={{ note: ActiveInstructions }} onConnectionSelected={loadEvents} diff --git a/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js b/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js index a5f37778f..3c08f5bc0 100644 --- a/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Livestorm/LivestormCommonFunc.js @@ -79,14 +79,7 @@ export const getAllEvents = (confTmp, setConf, loading, setLoading, setSnackbar }) } -export const getAllSessions = ( - confTmp, - setConf, - event_id, - loading, - setLoading, - setSnackbar = null -) => { +export const getAllSessions = (confTmp, setConf, event_id, loading, setLoading, setSnackbar = null) => { setLoading({ ...loading, session: true }) const requestParams = { diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx index c012edbf2..e4a0fe936 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimp.jsx @@ -8,11 +8,7 @@ import Steps from '../../Utilities/Steps' import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers' import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree' import MailChimpAuthorization from './MailChimpAuthorization' -import { - checkAddressFieldMapRequired, - handleInput, - checkMappedFields -} from './MailChimpCommonFunc' +import { checkAddressFieldMapRequired, handleInput, checkMappedFields } from './MailChimpCommonFunc' import MailChimpIntegLayout from './MailChimpIntegLayout' function MailChimp({ formFields, setFlow, flow, allIntegURL }) { diff --git a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx index df8a8dc64..06896caa4 100644 --- a/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailChimp/MailChimpAuthorization.jsx @@ -17,9 +17,7 @@ export default function MailChimpAuthorization({ }) { const loadAudience = useCallback( async connectionId => { - const nextConf = connectionId - ? { ...mailChimpConf, connection_id: connectionId } - : mailChimpConf + const nextConf = connectionId ? { ...mailChimpConf, connection_id: connectionId } : mailChimpConf refreshModules(setMailChimpConf, setIsLoading, setSnackbar) refreshAudience(formID, nextConf, setMailChimpConf, setIsLoading, setSnackbar) diff --git a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx index 1f4b6792b..f037cc595 100644 --- a/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MailMint/MailMintAuthorization.jsx @@ -14,13 +14,16 @@ export default function MailMintAuthorization({ setIsLoading, setSnackbar }) { - const handleSetStep = useCallback(value => { - if (value === 2) { - getAllList(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) - getAllTags(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) - } - setStep(value) - }, [setStep, mailMintConf, setMailMintConf, setIsLoading, setSnackbar]) + const handleSetStep = useCallback( + value => { + if (value === 2) { + getAllList(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) + getAllTags(mailMintConf, setMailMintConf, setIsLoading, setSnackbar) + } + setStep(value) + }, + [setStep, mailMintConf, setMailMintConf, setIsLoading, setSnackbar] + ) return ( - refreshCustomFields( - mailRelayConf, - setMailRelayConf, - loading, - setLoading, - setSnackbar - ) + refreshCustomFields(mailRelayConf, setMailRelayConf, loading, setLoading, setSnackbar) } className="icn-btn sh-sm ml-2 mr-2 tooltip" style={{ diff --git a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js index 7e12e06ce..73dddcd01 100644 --- a/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Mailjet/MailjetCommonFunc.js @@ -105,7 +105,15 @@ export const mailjetAuthentication = ( } export const getAllLists = (confTmp, setConf, loading, setLoading, type = 'refresh') => - mailjetAuthentication(confTmp, setConf, () => {}, () => {}, loading, setLoading, type === 'fetch' ? 'authentication' : 'refreshLists') + mailjetAuthentication( + confTmp, + setConf, + () => {}, + () => {}, + loading, + setLoading, + type === 'fetch' ? 'authentication' : 'refreshLists' + ) export const getCustomFields = (confTmp, setConf, setLoading) => { setLoading(prev => ({ ...prev, customFields: true })) diff --git a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx index 2dcfedae5..a0734a1bd 100644 --- a/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Mailster/MailsterAuthorization.jsx @@ -4,13 +4,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function MailsterAuthorization({ - mailsterConf, - setMailsterConf, - step, - setStep, - isInfo -}) { +export default function MailsterAuthorization({ mailsterConf, setMailsterConf, step, setStep, isInfo }) { return ( { return true } - export const mailsterFields = (confTmp, setConf, loading, setLoading) => { setLoading({ ...loading, fields: true }) diff --git a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx index fa5cb1282..1a9353993 100644 --- a/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MasterStudyLms/MasterStudyLmsAuthorization.jsx @@ -4,13 +4,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function MasterStudyLmsAuthorization({ - msLmsConf, - setMsLmsConf, - step, - setStep, - isInfo -}) { +export default function MasterStudyLmsAuthorization({ msLmsConf, setMsLmsConf, step, setStep, isInfo }) { return ( { - if (value === 2) { - getAllMemberShip(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) - paymentGateway(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) - } - setStep(value) - }, [setStep, memberpressConf, setMemberpressConf, setIsLoading, setSnackbar]) + const handleSetStep = useCallback( + value => { + if (value === 2) { + getAllMemberShip(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) + paymentGateway(memberpressConf, setMemberpressConf, setIsLoading, setSnackbar) + } + setStep(value) + }, + [setStep, memberpressConf, setMemberpressConf, setIsLoading, setSnackbar] + ) return ( - conf.connection_id - ? { connection_id: conf.connection_id } - : { authKey: conf.authKey || conf.api_key } + conf.connection_id ? { connection_id: conf.connection_id } : { authKey: conf.authKey || conf.api_key } export const getAllLists = async (conf, setConf, loading, setLoading) => { setLoading && setLoading({ ...loading, list: true }) diff --git a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx index aeca24194..f4a33f56a 100644 --- a/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/MoxieCRM/MoxieCRMAuthorization.jsx @@ -3,13 +3,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function MoxieCRMAuthorization({ - moxiecrmConf, - setMoxieCRMConf, - step, - setStep, - isInfo -}) { +export default function MoxieCRMAuthorization({ moxiecrmConf, setMoxieCRMConf, step, setStep, isInfo }) { const note = `

          ${__('Get API Key', 'bit-integrations')}

            diff --git a/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js b/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js index 641caeee1..a4019d583 100644 --- a/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Newsletter/NewsletterCommonFunc.js @@ -36,7 +36,6 @@ export const checkMappedFields = newsletterConf => { return true } - export const staticFields = [ { key: 'email', label: __('Email', 'bit-integrations'), required: true }, { key: 'name', label: __('First Name', 'bit-integrations'), required: false }, diff --git a/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js b/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js index 1c2625ca5..961c28836 100644 --- a/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Nimble/NimbleCommonFunc.js @@ -42,9 +42,7 @@ export const checkMappedFields = nimbleConf => { } const buildAuthRequestParams = confTmp => - confTmp?.connection_id - ? { connection_id: confTmp.connection_id } - : { api_key: confTmp.api_key } + confTmp?.connection_id ? { connection_id: confTmp.connection_id } : { api_key: confTmp.api_key } export const getAllFields = (confTmp, setConf, setLoading, setSnackbar = null) => { setLoading(prev => ({ ...prev, allFields: true })) diff --git a/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js b/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js index 7a8bd30d1..c4bc5eda1 100644 --- a/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js +++ b/frontend/src/components/AllIntegrations/NotificationX/NotificationXCommonFunc.js @@ -19,36 +19,44 @@ export const generateMappedField = allFields => { export const checkMappedFields = notificationXConf => { const unmapped = notificationXConf?.field_map ? notificationXConf.field_map.filter( - mappedField => - !mappedField.formField || - !mappedField.notificationXField || - (mappedField.formField === 'custom' && !mappedField.customValue) - ) + mappedField => + !mappedField.formField || + !mappedField.notificationXField || + (mappedField.formField === 'custom' && !mappedField.customValue) + ) : [] return unmapped.length === 0 } -export const refreshNotificationsBySource = (action, setNotificationXConf, setIsLoading, setSnackbar) => { +export const refreshNotificationsBySource = ( + action, + setNotificationXConf, + setIsLoading, + setSnackbar +) => { if (!action) return setIsLoading(true) - bitsFetch({ action: action }, 'notificationx_get_notifications_by_source').then(result => { - if (result && result.success) { - setNotificationXConf(prev => - create(prev, draft => { - draft.notifications = result.data + bitsFetch({ action: action }, 'notificationx_get_notifications_by_source') + .then(result => { + if (result && result.success) { + setNotificationXConf(prev => + create(prev, draft => { + draft.notifications = result.data + }) + ) + } else { + const errorMsg = typeof result?.data === 'string' ? result.data : result?.message + setSnackbar({ + msg: errorMsg || __('Failed to fetch notifications', 'bit-integrations'), + show: true }) - ) - } else { - const errorMsg = typeof result?.data === 'string' ? result.data : result?.message - setSnackbar({ msg: errorMsg || __('Failed to fetch notifications', 'bit-integrations'), show: true }) - } - - setIsLoading(false) - }).catch(() => { - setSnackbar({ msg: __('Failed to fetch notifications', 'bit-integrations'), show: true }) - setIsLoading(false) - }) + } + + setIsLoading(false) + }) + .catch(() => { + setSnackbar({ msg: __('Failed to fetch notifications', 'bit-integrations'), show: true }) + setIsLoading(false) + }) } - - diff --git a/frontend/src/components/AllIntegrations/OmniSend/OmniSendAuthorization.jsx b/frontend/src/components/AllIntegrations/OmniSend/OmniSendAuthorization.jsx index 0af6d48b9..c8b3e23a4 100644 --- a/frontend/src/components/AllIntegrations/OmniSend/OmniSendAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/OmniSend/OmniSendAuthorization.jsx @@ -4,13 +4,7 @@ import { __ } from '../../../Utils/i18nwrap' import Authorization from '../../Connections/Authorization' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' -export default function OmniSendAuthorization({ - omniSendConf, - setOmniSendConf, - step, - setstep, - isInfo -}) { +export default function OmniSendAuthorization({ omniSendConf, setOmniSendConf, step, setstep, isInfo }) { const note = `

            ${__('Step of generate API token:', 'bit-integrations')}

              diff --git a/frontend/src/components/AllIntegrations/OneDrive/OneDriveAuthorization.jsx b/frontend/src/components/AllIntegrations/OneDrive/OneDriveAuthorization.jsx index a9b8a8e7b..6da9b0865 100644 --- a/frontend/src/components/AllIntegrations/OneDrive/OneDriveAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/OneDrive/OneDriveAuthorization.jsx @@ -3,13 +3,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function OneDriveAuthorization({ - oneDriveConf, - setOneDriveConf, - step, - setStep, - isInfo -}) { +export default function OneDriveAuthorization({ oneDriveConf, setOneDriveConf, step, setStep, isInfo }) { const note = `

              ${__('OneDrive OAuth setup', 'bit-integrations')}

                diff --git a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx index 6ed675e4a..ad9b25e00 100644 --- a/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/OneHashCRM/OneHashCRMAuthorization.jsx @@ -16,7 +16,7 @@ export default function OneHashCRMAuthorization({

                ${__('Get API credentials', 'bit-integrations')}

                • ${__( - "Go to your OneHash CRM user dashboard and click profile from top-right corner.", + 'Go to your OneHash CRM user dashboard and click profile from top-right corner.', 'bit-integrations' )}
                • ${__('Select My Settings.', 'bit-integrations')}
                • diff --git a/frontend/src/components/AllIntegrations/PCloud/PCloudAuthorization.jsx b/frontend/src/components/AllIntegrations/PCloud/PCloudAuthorization.jsx index 0c0525fd5..5c7d30ca5 100644 --- a/frontend/src/components/AllIntegrations/PCloud/PCloudAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PCloud/PCloudAuthorization.jsx @@ -3,13 +3,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function PCloudAuthorization({ - pCloudConf, - setPCloudConf, - step, - setStep, - isInfo -}) { +export default function PCloudAuthorization({ pCloudConf, setPCloudConf, step, setStep, isInfo }) { const note = `

                  ${__('PCloud OAuth setup', 'bit-integrations')}

                    diff --git a/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js b/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js index 0372feb99..8c3aa6d80 100644 --- a/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js +++ b/frontend/src/components/AllIntegrations/PCloud/PCloudCommonFunc.js @@ -26,9 +26,7 @@ export const checkMappedFields = pCloudConf => { } const buildAuthRequestParams = conf => - conf?.connection_id - ? { connection_id: conf.connection_id } - : { tokenDetails: conf.tokenDetails } + conf?.connection_id ? { connection_id: conf.connection_id } : { tokenDetails: conf.tokenDetails } export const getAllPCloudFolders = (pCloudConf, setPCloudConf, type) => { const queryParams = buildAuthRequestParams(pCloudConf) diff --git a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx index eccf35fa2..07de48799 100644 --- a/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PaidMembershipPro/PaidMembershipProAuthorization.jsx @@ -22,7 +22,10 @@ export default function PaidMembershipProAuthorization({ tutorialLinks={tutorialLinks?.paidMembershipPro || {}} authDetails={{ authType: AUTH_TYPES.WP_PLUGIN_CHECK, - pluginCheck: { checks: [{ type: 'plugin_file', value: 'paid-memberships-pro/paid-memberships-pro.php' }], logic: 'AND' } + pluginCheck: { + checks: [{ type: 'plugin_file', value: 'paid-memberships-pro/paid-memberships-pro.php' }], + logic: 'AND' + } }} noteDetails={{ note: __( diff --git a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx index 12b14f261..b0d35db11 100644 --- a/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/PeepSo/PeepSoAuthorization.jsx @@ -4,13 +4,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function PeepSoAuthorization({ - peepSoConf, - setPeepSoConf, - step, - nextPage, - isInfo -}) { +export default function PeepSoAuthorization({ peepSoConf, setPeepSoConf, step, nextPage, isInfo }) { const setStep = useCallback(value => nextPage(value), [nextPage]) return ( { - if (value === 2) { - getAllLevels(restrictConf, setRestrictConf, setIsLoading) - } - setStep(value) - }, [setStep, restrictConf, setRestrictConf, setIsLoading]) + const handleSetStep = useCallback( + value => { + if (value === 2) { + getAllLevels(restrictConf, setRestrictConf, setIsLoading) + } + setStep(value) + }, + [setStep, restrictConf, setRestrictConf, setIsLoading] + ) return ( { const nextConf = connectionId ? { ...selzyConf, connection_id: connectionId } : selzyConf diff --git a/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx b/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx index bb2c73a72..1e82b613e 100644 --- a/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendFox/SendFoxAuthorization.jsx @@ -3,13 +3,7 @@ import { __ } from '../../../Utils/i18nwrap' import tutorialLinks from '../../../Utils/StaticData/tutorialLinks' import Authorization from '../../Connections/Authorization' -export default function SendFoxAuthorization({ - sendFoxConf, - setSendFoxConf, - step, - setstep, - isInfo -}) { +export default function SendFoxAuthorization({ sendFoxConf, setSendFoxConf, step, setstep, isInfo }) { const note = ` ${__('To generate an access token, please visit', 'bit-integrations')} diff --git a/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx b/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx index 95bdd09aa..75c9af879 100644 --- a/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendPulse/SendPulseAuthorization.jsx @@ -17,9 +17,7 @@ export default function SendPulseAuthorization({ }) { const loadLists = useCallback( connectionId => { - const nextConf = connectionId - ? { ...sendPulseConf, connection_id: connectionId } - : sendPulseConf + const nextConf = connectionId ? { ...sendPulseConf, connection_id: connectionId } : sendPulseConf refreshSendPulseList(nextConf, setSendPulseConf, setIsLoading, setSnackbar) }, diff --git a/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js b/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js index ec8b67783..c15a63214 100644 --- a/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js +++ b/frontend/src/components/AllIntegrations/SendPulse/SendPulseCommonFunc.js @@ -59,7 +59,7 @@ export const refreshSendPulseList = (sendPulseConf, setSendPulseConf, setIsLoadi export const refreshSendPulseHeader = (sendPulseConf, setSendPulseConf, setIsLoading, setSnackbar) => { const refreshListsRequestParams = { ...buildAuthRequestParams(sendPulseConf), - list_id: sendPulseConf.listId, + list_id: sendPulseConf.listId } bitsFetch(refreshListsRequestParams, 'sendPulse_headers') diff --git a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx index a3ced2b74..073732e8e 100644 --- a/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/SendinBlue/SendinBlueAuthorization.jsx @@ -15,9 +15,7 @@ export default function SendinBlueAuthorization({ }) { const loadLists = useCallback( connectionId => { - const nextConf = connectionId - ? { ...sendinBlueConf, connection_id: connectionId } - : sendinBlueConf + const nextConf = connectionId ? { ...sendinBlueConf, connection_id: connectionId } : sendinBlueConf refreshLists(nextConf, setSendinBlueConf, () => {}, setSnackbar) }, diff --git a/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx b/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx index 6668fb244..e900ff017 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx +++ b/frontend/src/components/AllIntegrations/Slack/SlackAuthorization.jsx @@ -16,9 +16,7 @@ export default function SlackAuthorization({ }) { const loadChannels = useCallback( async connectionId => { - const nextConf = connectionId - ? { ...slackConf, connection_id: connectionId } - : slackConf + const nextConf = connectionId ? { ...slackConf, connection_id: connectionId } : slackConf await fetchChannels(nextConf, setSlackConf, setIsLoading) }, diff --git a/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js b/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js index 8df5d0ed4..11a46b36a 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js +++ b/frontend/src/components/AllIntegrations/Slack/SlackCommonFunc.js @@ -13,12 +13,7 @@ export const handleInput = (e, slackConf, setSlackConf) => { setSlackConf({ ...newConf }) } -export const fetchChannels = async ( - confTmp, - setConf, - setIsLoading, - type = 'fetch' -) => { +export const fetchChannels = async (confTmp, setConf, setIsLoading, type = 'fetch') => { if (!confTmp.connection_id && !confTmp.accessToken) { toast.error(__("Access Token can't be empty", 'bit-integrations')) return diff --git a/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx b/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx index 30e482e40..91c8a3548 100644 --- a/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx +++ b/frontend/src/components/AllIntegrations/Slack/SlackIntegLayout.jsx @@ -33,10 +33,10 @@ export default function SlackIntegLayout({ className="btcd-paper-inp w-5"> {(slackConf?.channels || slackConf?.tokenDetails?.channels || []).map(({ id, name }) => ( - - ))} + + ))}
        {/* STEP 1 */} - + {/* STEP 2 */}
        ${__('Pro or higher plan only .', 'bit-integrations')}

        ${__('Client Id and Client Secret generate with OAuth .', 'bit-integrations')}

        ${__('Scope:', 'bit-integrations')}

        diff --git a/frontend/src/components/Connections/AddNewConnection.jsx b/frontend/src/components/Connections/AddNewConnection.jsx index d1998f37a..296492773 100644 --- a/frontend/src/components/Connections/AddNewConnection.jsx +++ b/frontend/src/components/Connections/AddNewConnection.jsx @@ -1,7 +1,7 @@ -import { AUTH_TYPES } from "../../Utils/connectionAuth" -import ApiConnection from "./ApiConnection" -import Oauth1Connection from "./Oauth1Connection" -import Oauth2Connection from "./Oauth2Connection" +import { AUTH_TYPES } from '../../Utils/connectionAuth' +import ApiConnection from './ApiConnection' +import Oauth1Connection from './Oauth1Connection' +import Oauth2Connection from './Oauth2Connection' export default function AddNewConnection(props) { if (props?.authDetails?.authType === AUTH_TYPES.OAUTH2) { diff --git a/frontend/src/components/Connections/ApiConnection.jsx b/frontend/src/components/Connections/ApiConnection.jsx index d2dde9926..ce92868e3 100644 --- a/frontend/src/components/Connections/ApiConnection.jsx +++ b/frontend/src/components/Connections/ApiConnection.jsx @@ -8,403 +8,413 @@ import LoaderSm from '../Loaders/LoaderSm' const ERROR_TEXT_STYLE = { color: 'red', fontSize: '15px' } const normalizeAdditionalHeaders = headers => { - if (!headers || typeof headers !== 'object') { - return {} - } + if (!headers || typeof headers !== 'object') { + return {} + } - return Object.entries(headers).reduce((acc, [key, value]) => { - const normalizedKey = String(key || '').trim() - const normalizedValue = value == null ? '' : String(value).trim() + return Object.entries(headers).reduce((acc, [key, value]) => { + const normalizedKey = String(key || '').trim() + const normalizedValue = value == null ? '' : String(value).trim() - if (normalizedKey && normalizedValue) { - acc[normalizedKey] = normalizedValue - } + if (normalizedKey && normalizedValue) { + acc[normalizedKey] = normalizedValue + } - return acc - }, {}) + return acc + }, {}) } // Resolves {fieldName} placeholders in URL templates using authData values. // Strips trailing slashes from substituted values to avoid double-slash in paths. // Unknown tokens are replaced with '' (empty required fields caught by validation). const resolveTemplate = (template, data) => { - if (!template) return '' - return template.replace(/\{(\w+)\}/g, (_, key) => { - const val = data[key] - if (val == null) return '' - return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) - }) + if (!template) return '' + return template.replace(/\{(\w+)\}/g, (_, key) => { + const val = data[key] + if (val == null) return '' + return typeof val === 'string' ? val.replace(/\/+$/, '') : String(val) + }) } const resolveHeaderTemplates = (headers, data) => { - if (!headers || typeof headers !== 'object') { - return {} - } - - return Object.entries(headers).reduce((acc, [key, value]) => { - acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value - return acc - }, {}) + if (!headers || typeof headers !== 'object') { + return {} + } + + return Object.entries(headers).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'string' ? resolveTemplate(value, data) : value + return acc + }, {}) } const resolvePayloadTemplates = (payload, data) => { - if (Array.isArray(payload)) { - return payload.map(item => resolvePayloadTemplates(item, data)) - } - - if (payload && typeof payload === 'object') { - return Object.entries(payload).reduce((acc, [key, value]) => { - acc[key] = resolvePayloadTemplates(value, data) - return acc - }, {}) - } + if (Array.isArray(payload)) { + return payload.map(item => resolvePayloadTemplates(item, data)) + } + + if (payload && typeof payload === 'object') { + return Object.entries(payload).reduce((acc, [key, value]) => { + acc[key] = resolvePayloadTemplates(value, data) + return acc + }, {}) + } - if (typeof payload === 'string') { - return resolveTemplate(payload, data) - } + if (typeof payload === 'string') { + return resolveTemplate(payload, data) + } - return payload + return payload } const resolveConfigValue = (value, data) => { - if (typeof value === 'function') { - return value(data) - } + if (typeof value === 'function') { + return value(data) + } - return value + return value } const getAuthPayload = ({ authType, apiEndpoint, method, authData, authDetails }) => { - const resolvedApiEndpoint = resolveConfigValue(apiEndpoint, authData) - const resolvedHeaders = resolveConfigValue(authDetails?.headers, authData) - const resolvedPayload = resolveConfigValue(authDetails?.payload, authData) - const additionalHeaders = resolveHeaderTemplates( - normalizeAdditionalHeaders(resolvedHeaders), - authData - ) - const sslVerify = authDetails?.ssl_verify !== false - - // Extra fields captured first; standard auth keys below always win on collision. - // Reserved auth_details keys: value, token, key, addTo, username, password, ssl_verify - const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { - if (authData[name] != null) acc[name] = authData[name] - return acc - }, {}) - - const basePayload = { - auth_type: authType, - api_endpoint: resolveTemplate(resolvedApiEndpoint, authData), - method: method || 'GET', - auth_details: { - ...extraAuthDetails, - ssl_verify: sslVerify - }, - headers: additionalHeaders - } - - if (resolvedPayload !== undefined) { - basePayload.payload = resolvePayloadTemplates(resolvedPayload, authData) - } - - if (authType === AUTH_TYPES.API_KEY) { - basePayload.auth_details = { - ...extraAuthDetails, - key: authDetails?.key || 'X-API-Key', - value: authData.api_key, - addTo: authData.addTo || authDetails?.addTo || 'header', - ssl_verify: sslVerify - } - return basePayload + const resolvedApiEndpoint = resolveConfigValue(apiEndpoint, authData) + const resolvedHeaders = resolveConfigValue(authDetails?.headers, authData) + const resolvedPayload = resolveConfigValue(authDetails?.payload, authData) + const additionalHeaders = resolveHeaderTemplates(normalizeAdditionalHeaders(resolvedHeaders), authData) + const sslVerify = authDetails?.ssl_verify !== false + + // Extra fields captured first; standard auth keys below always win on collision. + // Reserved auth_details keys: value, token, key, addTo, username, password, ssl_verify + const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (authData[name] != null) acc[name] = authData[name] + return acc + }, {}) + + const basePayload = { + auth_type: authType, + api_endpoint: resolveTemplate(resolvedApiEndpoint, authData), + method: method || 'GET', + auth_details: { + ...extraAuthDetails, + ssl_verify: sslVerify + }, + headers: additionalHeaders + } + + if (resolvedPayload !== undefined) { + basePayload.payload = resolvePayloadTemplates(resolvedPayload, authData) + } + + if (authType === AUTH_TYPES.API_KEY) { + basePayload.auth_details = { + ...extraAuthDetails, + key: authDetails?.key || 'X-API-Key', + value: authData.api_key, + addTo: authData.addTo || authDetails?.addTo || 'header', + ssl_verify: sslVerify } - - if (authType === AUTH_TYPES.BASIC_AUTH) { - basePayload.auth_details = { - ...extraAuthDetails, - username: authData.username, - password: authData.password, - ssl_verify: sslVerify - } - return basePayload + return basePayload + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + basePayload.auth_details = { + ...extraAuthDetails, + username: authData.username, + password: authData.password, + ssl_verify: sslVerify } + return basePayload + } - if (authType === AUTH_TYPES.BEARER_TOKEN) { - basePayload.auth_details = { - ...extraAuthDetails, - token: authData.token, - ssl_verify: sslVerify - } + if (authType === AUTH_TYPES.BEARER_TOKEN) { + basePayload.auth_details = { + ...extraAuthDetails, + token: authData.token, + ssl_verify: sslVerify } + } - return basePayload + return basePayload } const getValidationErrors = (authType, authData, extraFields = [], authDetails = {}) => { - const nextErrors = {} + const nextErrors = {} - if (!authData.connectionName?.trim()) { - nextErrors.connectionName = __('Connection name is required', 'bit-integrations') + if (!authData.connectionName?.trim()) { + nextErrors.connectionName = __('Connection name is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.API_KEY && !authData.api_key?.trim()) { + nextErrors.api_key = __('API key is required', 'bit-integrations') + } + + if (authType === AUTH_TYPES.BASIC_AUTH) { + if (!authData.username?.trim()) { + nextErrors.username = __('Username is required', 'bit-integrations') } - if (authType === AUTH_TYPES.API_KEY && !authData.api_key?.trim()) { - nextErrors.api_key = __('API key is required', 'bit-integrations') + if (!authDetails?.allowEmptyPassword && !authData.password?.trim()) { + nextErrors.password = __('Password is required', 'bit-integrations') } + } - if (authType === AUTH_TYPES.BASIC_AUTH) { - if (!authData.username?.trim()) { - nextErrors.username = __('Username is required', 'bit-integrations') - } + if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token?.trim()) { + nextErrors.token = __('Bearer token is required', 'bit-integrations') + } - if (!authDetails?.allowEmptyPassword && !authData.password?.trim()) { - nextErrors.password = __('Password is required', 'bit-integrations') - } + extraFields.forEach(field => { + const value = authData[field.name] + const normalized = typeof value === 'string' ? value.trim() : value + if (field.required && (normalized === '' || normalized == null)) { + nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` } + }) - if (authType === AUTH_TYPES.BEARER_TOKEN && !authData.token?.trim()) { - nextErrors.token = __('Bearer token is required', 'bit-integrations') + return nextErrors +} + +export default function ApiConnection({ + authDetails, + config, + setConfig, + isInfo = false, + customAuthFields, + onConnectionSaved +}) { + const [authData, setAuthData] = useState({}) + const [isAuthorized, setIsAuthorized] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [errors, setErrors] = useState({}) + const { authType, apiEndpoint, method } = authDetails || {} + + const handleChange = useCallback(event => { + const { name, value } = event.target + + setAuthData(prev => ({ ...prev, [name]: value })) + setErrors(prev => ({ ...prev, [name]: '' })) + }, []) + + const handleAuthorize = useCallback(async () => { + const validationErrors = getValidationErrors( + authType, + authData, + authDetails?.extraFields, + authDetails + ) + setErrors(validationErrors) + + if (Object.keys(validationErrors).length > 0) { + return } - extraFields.forEach(field => { - const value = authData[field.name] - const normalized = typeof value === 'string' ? value.trim() : value - if (field.required && (normalized === '' || normalized == null)) { - nextErrors[field.name] = `${field.label} ${__('is required', 'bit-integrations')}` - } + const payload = getAuthPayload({ + authType, + apiEndpoint, + method, + authData, + authDetails }) - return nextErrors -} + setIsLoading(true) -export default function ApiConnection({ - authDetails, - config, - setConfig, - isInfo = false, - customAuthFields, - onConnectionSaved -}) { - const [authData, setAuthData] = useState({}) - const [isAuthorized, setIsAuthorized] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [errors, setErrors] = useState({}) - const { authType, apiEndpoint, method } = authDetails || {} - - const handleChange = useCallback(event => { - const { name, value } = event.target - - setAuthData(prev => ({ ...prev, [name]: value })) - setErrors(prev => ({ ...prev, [name]: '' })) - }, []) - - const handleAuthorize = useCallback(async () => { - const validationErrors = getValidationErrors( - authType, - authData, - authDetails?.extraFields, - authDetails + try { + const authorizeRes = await authorizeConnection(payload) + + if (!authorizeRes?.success) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')}${ + authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` ) - setErrors(validationErrors) + return + } - if (Object.keys(validationErrors).length > 0) { - return - } + const saveRes = await saveConnection({ + app_slug: config?.app_slug || config?.type, + auth_type: authType, + connection_name: authData.connectionName, + account_name: authData.connectionName, + auth_details: payload.auth_details, + encrypt_keys: authDetails?.encryptKeys || defaultEncryptKeys[authType] || [] + }) + + if (!saveRes?.success) { + toast.error( + `${__('Failed to save connection Cause:', 'bit-integrations')}${ + saveRes?.data?.data || saveRes?.data || '' + }. ${__('please try again', 'bit-integrations')}` + ) + return + } - const payload = getAuthPayload({ - authType, - apiEndpoint, - method, - authData, - authDetails - }) - - setIsLoading(true) - - try { - const authorizeRes = await authorizeConnection(payload) - - if (!authorizeRes?.success) { - setIsAuthorized(false) - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')}${authorizeRes?.data?.data || authorizeRes?.data || 'Unknown error' - }. ${__('please try again', 'bit-integrations')}` - ) - return - } - - const saveRes = await saveConnection({ - app_slug: config?.app_slug || config?.type, - auth_type: authType, - connection_name: authData.connectionName, - account_name: authData.connectionName, - auth_details: payload.auth_details, - encrypt_keys: authDetails?.encryptKeys || defaultEncryptKeys[authType] || [] - }) - - if (!saveRes?.success) { - toast.error( - `${__('Failed to save connection Cause:', 'bit-integrations')}${saveRes?.data?.data || saveRes?.data || '' - }. ${__('please try again', 'bit-integrations')}` - ) - return - } - - const connection = saveRes?.data?.data || null - const persistedExtraFields = (authDetails?.extraFields || []).reduce((acc, { name }) => { - if (authData[name] != null) { - acc[name] = authData[name] - } - return acc - }, {}) - - setConfig(prev => ({ ...prev, connection_id: connection?.id, ...persistedExtraFields })) - - if (onConnectionSaved) { - await onConnectionSaved(connection) - } - - setIsAuthorized(true) - toast.success(__('Authorized Successfully', 'bit-integrations')) - } catch (error) { - setIsAuthorized(false) - toast.error( - `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error' - }. ${__('please try again', 'bit-integrations')}` - ) - } finally { - setIsLoading(false) + const connection = saveRes?.data?.data || null + const persistedExtraFields = (authDetails?.extraFields || []).reduce((acc, { name }) => { + if (authData[name] != null) { + acc[name] = authData[name] } - }, [apiEndpoint, authData, authDetails, authType, config?.app_slug, config?.type, method, onConnectionSaved, setConfig]) + return acc + }, {}) + + setConfig(prev => ({ ...prev, connection_id: connection?.id, ...persistedExtraFields })) + + if (onConnectionSaved) { + await onConnectionSaved(connection) + } + + setIsAuthorized(true) + toast.success(__('Authorized Successfully', 'bit-integrations')) + } catch (error) { + setIsAuthorized(false) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')} ${ + error?.message || 'Unknown error' + }. ${__('please try again', 'bit-integrations')}` + ) + } finally { + setIsLoading(false) + } + }, [ + apiEndpoint, + authData, + authDetails, + authType, + config?.app_slug, + config?.type, + method, + onConnectionSaved, + setConfig + ]) + + return ( + <> +
        + {__('Connection Name:', 'bit-integrations')} +
        + +
        {errors.connectionName || ''}
        + + {customAuthFields} + + {authDetails?.extraFields?.map(field => ( +
        +
        + {field.label}: +
        + {field.type === 'select' ? ( + + ) : ( + + )} +
        {errors[field.name] || ''}
        +
        + ))} - return ( + {authType === AUTH_TYPES.API_KEY && ( <> -
        - {__('Connection Name:', 'bit-integrations')} -
        - + {__('API Key:', 'bit-integrations')} +
        + +
        {errors.api_key || ''}
        + + )} + + {authType === AUTH_TYPES.BASIC_AUTH && ( + <> +
        + {__('Username:', 'bit-integrations')} +
        + +
        {errors.username || ''}
        + + {!authDetails?.allowEmptyPassword && ( + <> +
        + {__('Password:', 'bit-integrations')} +
        + -
        {errors.connectionName || ''}
        - - {customAuthFields} - - {authDetails?.extraFields?.map(field => ( -
        -
        - {field.label}: -
        - {field.type === 'select' ? ( - - ) : ( - - )} -
        {errors[field.name] || ''}
        -
        - ))} - - {authType === AUTH_TYPES.API_KEY && ( - <> -
        - {__('API Key:', 'bit-integrations')} -
        - -
        {errors.api_key || ''}
        - - )} - - {authType === AUTH_TYPES.BASIC_AUTH && ( - <> -
        - {__('Username:', 'bit-integrations')} -
        - -
        {errors.username || ''}
        - - {!authDetails?.allowEmptyPassword && ( - <> -
        - {__('Password:', 'bit-integrations')} -
        - -
        {errors.password || ''}
        - - )} - - )} - - {authType === AUTH_TYPES.BEARER_TOKEN && ( - <> -
        - {__('Bearer Token:', 'bit-integrations')} -
        - -
        {errors.token || ''}
        - - )} - - + name="password" + value={authData.password || ''} + type="password" + placeholder={__('Password...', 'bit-integrations')} + disabled={isInfo} + /> +
        {errors.password || ''}
        + + )} - ) + )} + + {authType === AUTH_TYPES.BEARER_TOKEN && ( + <> +
        + {__('Bearer Token:', 'bit-integrations')} +
        + +
        {errors.token || ''}
        + + )} + + + + ) } diff --git a/frontend/src/components/Connections/Authorization.jsx b/frontend/src/components/Connections/Authorization.jsx index ca792585c..860a5a100 100644 --- a/frontend/src/components/Connections/Authorization.jsx +++ b/frontend/src/components/Connections/Authorization.jsx @@ -48,8 +48,7 @@ export default function Authorization({ try { const res = await listConnections(appSlug) - const savedConnections = - res?.success && Array.isArray(res?.data?.data) ? res.data.data : [] + const savedConnections = res?.success && Array.isArray(res?.data?.data) ? res.data.data : [] setConnections(savedConnections) setShowNewConnection(savedConnections.length === 0) @@ -148,31 +147,32 @@ export default function Authorization({ [onConnectionSelected] ) - const canGoNext = isWpPluginCheck - ? isVerified - : Boolean(config?.connection_id) && !isPendingPostAuth + const canGoNext = isWpPluginCheck ? isVerified : Boolean(config?.connection_id) && !isPendingPostAuth const pageStyle = useMemo(() => (step === 1 ? STEP_ONE_STYLE : undefined), [step]) - const handleConnectionSaved = useCallback(async savedConnection => { - const refreshedConnections = await refreshConnections() - const savedConnectionId = savedConnection?.id - - if (savedConnectionId) { - const matchedConnection = refreshedConnections.find( - conn => String(conn.id) === String(savedConnectionId) - ) - const selectedConnectionId = matchedConnection?.id || savedConnectionId - setConfig(prev => ({ ...prev, connection_id: selectedConnectionId })) - setShowNewConnection(false) - await fireConnectionSelected(selectedConnectionId) - return - } + const handleConnectionSaved = useCallback( + async savedConnection => { + const refreshedConnections = await refreshConnections() + const savedConnectionId = savedConnection?.id + + if (savedConnectionId) { + const matchedConnection = refreshedConnections.find( + conn => String(conn.id) === String(savedConnectionId) + ) + const selectedConnectionId = matchedConnection?.id || savedConnectionId + setConfig(prev => ({ ...prev, connection_id: selectedConnectionId })) + setShowNewConnection(false) + await fireConnectionSelected(selectedConnectionId) + return + } - if (refreshedConnections.length > 0) { - setShowNewConnection(false) - } - }, [refreshConnections, setConfig, fireConnectionSelected]) + if (refreshedConnections.length > 0) { + setShowNewConnection(false) + } + }, + [refreshConnections, setConfig, fireConnectionSelected] + ) return (
        @@ -226,18 +226,13 @@ export default function Authorization({ className="btn btcd-btn-lg purple mt-3 sh-sm flx" type="button" disabled={isVerified || isVerifying}> - {isVerified - ? __('Authorized ✔', 'bit-integrations') - : __('Authorize', 'bit-integrations')} + {isVerified ? __('Authorized ✔', 'bit-integrations') : __('Authorize', 'bit-integrations')} {isVerifying && } )} {!isInfo && canGoNext && ( - diff --git a/frontend/src/components/Connections/Oauth1Connection.jsx b/frontend/src/components/Connections/Oauth1Connection.jsx index 36bdc078d..59afc2fe9 100644 --- a/frontend/src/components/Connections/Oauth1Connection.jsx +++ b/frontend/src/components/Connections/Oauth1Connection.jsx @@ -117,10 +117,7 @@ const getOauth1Payload = ({ const resolvedApiEndpoint = resolveConfigValue(authDetails?.apiEndpoint, formData) const resolvedHeaders = resolveConfigValue(authDetails?.headers, formData) const resolvedPayload = resolveConfigValue(authDetails?.payload, formData) - const additionalHeaders = resolveHeaderTemplates( - normalizeAdditionalHeaders(resolvedHeaders), - formData - ) + const additionalHeaders = resolveHeaderTemplates(normalizeAdditionalHeaders(resolvedHeaders), formData) const sslVerify = authDetails?.ssl_verify !== false const extraAuthDetails = (authDetails?.extraFields || []).reduce((acc, { name }) => { @@ -295,11 +292,10 @@ export default function Oauth1Connection({ ) const popupResponse = normalizePopupResponse( - await openOauthPopup( - authUrl, - authDetails?.authorizationWindowLabel || 'OAuth1', - { channelKey: oauthChannelKey, includeLegacyFallback: true } - ) + await openOauthPopup(authUrl, authDetails?.authorizationWindowLabel || 'OAuth1', { + channelKey: oauthChannelKey, + includeLegacyFallback: true + }) ) if (popupResponse?.error) { @@ -346,7 +342,9 @@ export default function Oauth1Connection({ await saveOauth1Connection(payload) } catch (error) { setIsAuthorized(false) - toast.error(`${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}`) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}` + ) } finally { setIsLoading(false) } @@ -378,12 +376,16 @@ export default function Oauth1Connection({ />
        {errors.connectionName || ''}
        - {(authDetails?.showCallbackInfo !== false) && ( + {authDetails?.showCallbackInfo !== false && ( <>
        {__('Homepage URL:', 'bit-integrations')}
        - +
        {authDetails?.callbackLabel || __('Callback / Return URL:', 'bit-integrations')}
        diff --git a/frontend/src/components/Connections/Oauth2Connection.jsx b/frontend/src/components/Connections/Oauth2Connection.jsx index 376551bba..9387285f5 100644 --- a/frontend/src/components/Connections/Oauth2Connection.jsx +++ b/frontend/src/components/Connections/Oauth2Connection.jsx @@ -197,11 +197,10 @@ export default function Oauth2Connection({ const oauthChannelKey = createOauthChannelKey() const state = buildCallbackState(oauthChannelKey) const authUrl = buildAuthUrl(populatedAuthCodeEndpoint, { state, redirectUri, extraParams }) - const popupResponse = await openOauthPopup( - authUrl, - formData.connectionName || 'OAuth', - { channelKey: oauthChannelKey, includeLegacyFallback: true } - ) + const popupResponse = await openOauthPopup(authUrl, formData.connectionName || 'OAuth', { + channelKey: oauthChannelKey, + includeLegacyFallback: true + }) if (popupResponse?.error) { throw new Error( @@ -212,7 +211,9 @@ export default function Oauth2Connection({ } if (!popupResponse?.code) { - throw new Error(popupResponse?.error_description || __('Authorization code missing', 'bit-integrations')) + throw new Error( + popupResponse?.error_description || __('Authorization code missing', 'bit-integrations') + ) } const tokenRes = await exchangeAuthCodeForToken({ @@ -231,7 +232,16 @@ export default function Oauth2Connection({ } return tokenRes?.data?.data || {} - }, [clientAuthentication, formData, grantType, redirectUri, resolvedAuthCodeEndpoint, resolvedTokenEndpoint, scope, sslVerify]) + }, [ + clientAuthentication, + formData, + grantType, + redirectUri, + resolvedAuthCodeEndpoint, + resolvedTokenEndpoint, + scope, + sslVerify + ]) const handleClientCredentialsFlow = useCallback(async () => { const tokenRes = await exchangeClientCredentialsForToken({ @@ -248,7 +258,14 @@ export default function Oauth2Connection({ } return tokenRes?.data?.data || {} - }, [clientAuthentication, formData.clientId, formData.clientSecret, resolvedTokenEndpoint, scope, sslVerify]) + }, [ + clientAuthentication, + formData.clientId, + formData.clientSecret, + resolvedTokenEndpoint, + scope, + sslVerify + ]) const handleAuthorize = useCallback(async () => { if (!validate()) return @@ -284,7 +301,9 @@ export default function Oauth2Connection({ await storeConnection(savedAuthDetails) } catch (error) { setIsAuthorized(false) - toast.error(`${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}`) + toast.error( + `${__('Authorization failed Cause:', 'bit-integrations')} ${error?.message || 'Unknown error'}` + ) } finally { setIsLoading(false) } @@ -305,8 +324,7 @@ export default function Oauth2Connection({ ]) const isAuthCodeFlow = - grantType === GRANT_TYPES.AUTHORIZATION_CODE || - grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE + grantType === GRANT_TYPES.AUTHORIZATION_CODE || grantType === GRANT_TYPES.AUTHORIZATION_CODE_PKCE return ( <> @@ -372,11 +390,7 @@ export default function Oauth2Connection({
        {__('Callback / Redirect URL:', 'bit-integrations')}
        - + )} diff --git a/frontend/src/hooks/useFetch.js b/frontend/src/hooks/useFetch.js index f80067c49..578be73f5 100644 --- a/frontend/src/hooks/useFetch.js +++ b/frontend/src/hooks/useFetch.js @@ -1,7 +1,7 @@ import useSWR from 'swr' import bitsFetch from '../Utils/bitsFetch' -const useFetch = ({ payload, action, method = 'POST', params=null }) => { +const useFetch = ({ payload, action, method = 'POST', params = null }) => { const { data, error, mutate } = useSWR(action, uri => bitsFetch(payload, Array.isArray(uri) ? uri[0] : uri, params, method) ) diff --git a/frontend/src/pages/Connections.jsx b/frontend/src/pages/Connections.jsx index 7b599c42b..01fcffb4e 100644 --- a/frontend/src/pages/Connections.jsx +++ b/frontend/src/pages/Connections.jsx @@ -5,11 +5,7 @@ import EditIcn from '../Icons/EditIcn' import TrashIcn from '../Icons/TrashIcn' import Table from '../components/Utilities/Table' import ConfirmModal from '../components/Utilities/ConfirmModal' -import { - deleteConnection, - listConnections, - updateConnection -} from '../Utils/connectionApi' +import { deleteConnection, listConnections, updateConnection } from '../Utils/connectionApi' import { __, sprintf } from '../Utils/i18nwrap' export default function Connections() { @@ -156,83 +152,82 @@ export default function Connections() { .finally(() => setIsConfirmPending(false)) }, [deletingId, isConfirmPending, getDeleteErrorMessage]) - const setBulkDelete = useCallback(rows => { - const ids = [] + const setBulkDelete = useCallback( + rows => { + const ids = [] - if (Array.isArray(rows)) { - rows.forEach(row => { - if (row?.original?.id) { - ids.push(row.original.id) - } - }) - } else if (rows?.original?.id) { - ids.push(rows.original.id) - } - - if (ids.length < 1) { - return - } - - const promise = Promise.allSettled( - ids.map(id => - deleteConnection(id).then(res => { - if (!res?.success) { - throw new Error(getDeleteErrorMessage(res)) + if (Array.isArray(rows)) { + rows.forEach(row => { + if (row?.original?.id) { + ids.push(row.original.id) } - return id }) - ) - ).then(results => { - const deletedIds = results - .filter(r => r.status === 'fulfilled') - .map(r => r.value) - - if (deletedIds.length > 0) { - setConnections(prev => prev.filter(item => !deletedIds.includes(item.id))) + } else if (rows?.original?.id) { + ids.push(rows.original.id) } - const failedCount = results.filter(r => r.status === 'rejected').length + if (ids.length < 1) { + return + } - if (failedCount > 0) { - const uniqueFailureMessages = [ - ...new Set( - results - .filter(r => r.status === 'rejected') - .map(r => r.reason?.message) - .filter(Boolean) - ) - ] + const promise = Promise.allSettled( + ids.map(id => + deleteConnection(id).then(res => { + if (!res?.success) { + throw new Error(getDeleteErrorMessage(res)) + } + return id + }) + ) + ).then(results => { + const deletedIds = results.filter(r => r.status === 'fulfilled').map(r => r.value) - if (failedCount === ids.length && uniqueFailureMessages.length === 1) { - throw new Error(uniqueFailureMessages[0]) + if (deletedIds.length > 0) { + setConnections(prev => prev.filter(item => !deletedIds.includes(item.id))) } - throw new Error( - failedCount === ids.length ? 'bulk_delete_failed' : 'bulk_delete_partial' - ) - } + const failedCount = results.filter(r => r.status === 'rejected').length - return deletedIds.length > 1 - ? __('Connections deleted', 'bit-integrations') - : __('Connection deleted', 'bit-integrations') - }) + if (failedCount > 0) { + const uniqueFailureMessages = [ + ...new Set( + results + .filter(r => r.status === 'rejected') + .map(r => r.reason?.message) + .filter(Boolean) + ) + ] - toast.promise(promise, { - success: msg => msg, - error: error => { - if (error?.message === 'bulk_delete_partial') { - return __('Some selected connections could not be deleted.', 'bit-integrations') - } + if (failedCount === ids.length && uniqueFailureMessages.length === 1) { + throw new Error(uniqueFailureMessages[0]) + } - if (error?.message === 'bulk_delete_failed') { - return __('Failed to delete', 'bit-integrations') + throw new Error(failedCount === ids.length ? 'bulk_delete_failed' : 'bulk_delete_partial') } - return error?.message || __('Failed to delete', 'bit-integrations') - }, - loading: __('Deleting...', 'bit-integrations') - }) - }, [getDeleteErrorMessage]) + return deletedIds.length > 1 + ? __('Connections deleted', 'bit-integrations') + : __('Connection deleted', 'bit-integrations') + }) + + toast.promise(promise, { + success: msg => msg, + error: error => { + if (error?.message === 'bulk_delete_partial') { + return __('Some selected connections could not be deleted.', 'bit-integrations') + } + + if (error?.message === 'bulk_delete_failed') { + return __('Failed to delete', 'bit-integrations') + } + + return error?.message || __('Failed to delete', 'bit-integrations') + }, + loading: __('Deleting...', 'bit-integrations') + }) + }, + [getDeleteErrorMessage] + ) const columns = useMemo( () => [ @@ -321,7 +316,8 @@ export default function Connections() { .filter(Boolean) const extraCount = linkedIntegrations.length - previewNames.length - const linkedText = extraCount > 0 ? `${previewNames.join(', ')} +${extraCount}` : previewNames.join(', ') + const linkedText = + extraCount > 0 ? `${previewNames.join(', ')} +${extraCount}` : previewNames.join(', ') const tooltipText = linkedIntegrations .map(item => item?.name || `#${item?.id || ''}`) .filter(Boolean) @@ -438,7 +434,10 @@ export default function Connections() { {!isLoading && filteredConnections.length === 0 && (

        {connections.length === 0 - ? __('No connections saved yet. Authorize an app from any integration to add one.', 'bit-integrations') + ? __( + 'No connections saved yet. Authorize an app from any integration to add one.', + 'bit-integrations' + ) : __('No connections match the current filters.', 'bit-integrations')}

        )}