diff --git a/app/controllers/api/v1/accounts/base_controller.rb b/app/controllers/api/v1/accounts/base_controller.rb index e30effc59..f08b87e60 100644 --- a/app/controllers/api/v1/accounts/base_controller.rb +++ b/app/controllers/api/v1/accounts/base_controller.rb @@ -2,5 +2,14 @@ class Api::V1::Accounts::BaseController < Api::BaseController include SwitchLocale include EnsureCurrentAccountHelper before_action :current_account + before_action :validate_token_api_access, if: :authenticate_by_access_token? around_action :switch_locale_using_account_locale + + private + + def validate_token_api_access + return if Current.account.api_and_webhooks_enabled? + + render json: { error: 'API access is not enabled for this account' }, status: :forbidden + end end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index fb991949a..9438a1660 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -8,6 +8,7 @@ class Api::V1::AccountsController < Api::BaseController before_action :ensure_account_name, only: [:create] before_action :validate_captcha, only: [:create] before_action :fetch_account, except: [:create] + before_action :validate_token_api_access, if: :authenticate_by_access_token?, except: [:create] before_action :check_authorization, except: [:create] rescue_from CustomExceptions::Account::InvalidEmail, @@ -105,6 +106,12 @@ class Api::V1::AccountsController < Api::BaseController @current_account_user = @account.account_users.find_by(user_id: current_user.id) end + def validate_token_api_access + return if @account.api_and_webhooks_enabled? + + render json: { error: 'API access is not enabled for this account' }, status: :forbidden + end + def account_params params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :user_full_name) end diff --git a/app/javascript/dashboard/components-next/button/ConfirmButton.vue b/app/javascript/dashboard/components-next/button/ConfirmButton.vue index 854d5d452..00b9300c6 100644 --- a/app/javascript/dashboard/components-next/button/ConfirmButton.vue +++ b/app/javascript/dashboard/components-next/button/ConfirmButton.vue @@ -14,6 +14,7 @@ const props = defineProps({ icon: { type: [String, Object, Function], default: '' }, trailingIcon: { type: Boolean, default: false }, isLoading: { type: Boolean, default: false }, + disabled: { type: Boolean, default: false }, }); const emit = defineEmits(['click']); @@ -61,6 +62,7 @@ const handleClick = () => { :icon="icon" :trailing-icon="trailingIcon" :is-loading="isLoading" + :disabled="disabled" @click="handleClick" @blur="resetConfirmMode" > diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js index 058921eea..e3c57e99d 100644 --- a/app/javascript/dashboard/featureFlags.js +++ b/app/javascript/dashboard/featureFlags.js @@ -12,6 +12,7 @@ export const FEATURE_FLAGS = { CRM: 'crm', CUSTOM_ATTRIBUTES: 'custom_attributes', DATA_IMPORT: 'data_import', + API_AND_WEBHOOKS: 'api_and_webhooks', INBOX_MANAGEMENT: 'inbox_management', INTEGRATIONS: 'integrations', LABELS: 'labels', diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 629dbd27c..82782988f 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -31,6 +31,13 @@ "WEBHOOK": { "SUBSCRIBED_EVENTS": "Subscribed Events", "LEARN_MORE": "Learn more about webhooks", + "PAYWALL": { + "TITLE": "Webhooks are available on paid plans", + "AVAILABLE_ON": "Use webhooks to receive real-time events from your Chatwoot account.", + "UPGRADE_PROMPT": "Upgrade to the Startups, Business, or Enterprise plan to use webhooks.", + "UPGRADE_NOW": "Upgrade now", + "CANCEL_ANYTIME": "Change or cancel your plan anytime." + }, "SECRET": { "LABEL": "Secret", "COPY": "Copy secret to clipboard", diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index eaecd7b80..ceb0438b1 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -100,6 +100,7 @@ "ACCESS_TOKEN": { "TITLE": "Access Token", "NOTE": "This token can be used if you are building an API based integration", + "PAID_PLAN_NOTE": "API access tokens are available on paid plans.", "COPY": "Copy", "RESET": "Reset", "CONFIRM_RESET": "Are you sure?", diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/Index.vue index 7213c735a..75dc30812 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/Index.vue @@ -5,9 +5,11 @@ import { useBranding } from 'shared/composables/useBranding'; import { picoSearch } from '@scmmishra/pico-search'; import NextButton from 'dashboard/components-next/button/Button.vue'; import { BaseTable } from 'dashboard/components-next/table'; +import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import NewWebhook from './NewWebHook.vue'; import EditWebhook from './EditWebHook.vue'; import WebhookRow from './WebhookRow.vue'; +import WebhookPaywall from './WebhookPaywall.vue'; import BaseSettingsHeader from '../../components/BaseSettingsHeader.vue'; import SettingsLayout from '../../SettingsLayout.vue'; @@ -20,6 +22,7 @@ export default { NewWebhook, EditWebhook, WebhookRow, + WebhookPaywall, }, setup() { const { replaceInstallationName } = useBranding(); @@ -39,7 +42,19 @@ export default { ...mapGetters({ records: 'webhooks/getWebhooks', uiFlags: 'webhooks/getUIFlags', + accountId: 'getCurrentAccountId', + isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount', + isOnChatwootCloud: 'globalConfig/isOnChatwootCloud', }), + apiAndWebhooksEnabled() { + return ( + !this.isOnChatwootCloud || + this.isFeatureEnabledonAccount( + this.accountId, + FEATURE_FLAGS.API_AND_WEBHOOKS + ) + ); + }, integration() { return this.$store.getters['integrations/getIntegration']('webhook'); }, @@ -57,9 +72,16 @@ export default { ]; }, }, + watch: { + apiAndWebhooksEnabled: { + immediate: true, + handler(enabled) { + if (enabled) this.$store.dispatch('webhooks/get'); + }, + }, + }, mounted() { this.$store.dispatch('integrations/get', 'webhook'); - this.$store.dispatch('webhooks/get'); }, methods: { openAddPopup() { @@ -105,10 +127,10 @@ export default {