Compare commits

...
Author SHA1 Message Date
Paul Atreides 676493eff2 chore: more changes 2025-08-24 10:04:13 +05:30
Paul Atreides b0d8eb3a2b feat: Roll out WhatsApp Embedded Signup for all users 2025-08-24 09:59:22 +05:30
7 changed files with 52 additions and 144 deletions
@@ -1,5 +1,4 @@
class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts::BaseController
before_action :validate_feature_enabled!
before_action :fetch_and_validate_inbox, if: -> { params[:inbox_id].present? }
# POST /api/v1/accounts/:account_id/whatsapp/authorization
@@ -65,15 +64,6 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts:
}, status: :unprocessable_entity
end
def validate_feature_enabled!
return if Current.account.feature_whatsapp_embedded_signup?
render json: {
success: false,
error: 'WhatsApp embedded signup is not enabled for this account'
}, status: :forbidden
end
def validate_embedded_signup_params!
missing_params = []
missing_params << 'code' if params[:code].blank?
-1
View File
@@ -37,7 +37,6 @@ export const FEATURE_FLAGS = {
REPORT_V4: 'report_v4',
CHANNEL_INSTAGRAM: 'channel_instagram',
CONTACT_CHATWOOT_SUPPORT_TEAM: 'contact_chatwoot_support_team',
WHATSAPP_EMBEDDED_SIGNUP: 'whatsapp_embedded_signup',
CAPTAIN_V2: 'captain_integration_v2',
};
@@ -272,8 +272,8 @@
},
"SUBMIT_BUTTON": "Create WhatsApp Channel",
"EMBEDDED_SIGNUP": {
"TITLE": "Quick Setup with Meta",
"DESC": "You will be redirected to Meta to log into your WhatsApp Business account. Having admin access will help make the setup smooth and easy.",
"TITLE": "Quick setup with Meta",
"DESC": "Use the WhatsApp Embedded Signup flow to quickly connect new numbers. You will be redirected to Meta to log into your WhatsApp Business account. Having admin access will help make the setup smooth and easy.",
"BENEFITS": {
"TITLE": "Benefits of Embedded Signup:",
"EASY_SETUP": "No manual configuration required",
@@ -296,7 +296,10 @@
"INVALID_BUSINESS_DATA": "Invalid business data received from Facebook. Please try again.",
"SIGNUP_ERROR": "Signup error occurred",
"AUTH_NOT_COMPLETED": "Authentication not completed. Please restart the process.",
"SUCCESS_FALLBACK": "WhatsApp Business Account has been successfully configured"
"SUCCESS_FALLBACK": "WhatsApp Business Account has been successfully configured",
"MANUAL_FALLBACK": "If your number is already linked to an Embedded Signup, or if you're a tech provider onboarding your own number, please use the",
"MANUAL_SETUP_LINK": "manual setup flow",
"MANUAL_FALLBACK_INSTEAD": "instead"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the WhatsApp channel"
@@ -2,23 +2,22 @@
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import Twilio from './Twilio.vue';
import ThreeSixtyDialogWhatsapp from './360DialogWhatsapp.vue';
import CloudWhatsapp from './CloudWhatsapp.vue';
import WhatsappEmbeddedSignup from './WhatsappEmbeddedSignup.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import Button from 'dashboard/components-next/button/Button.vue';
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const store = useStore();
const PROVIDER_TYPES = {
WHATSAPP: 'whatsapp',
TWILIO: 'twilio',
WHATSAPP_CLOUD: 'whatsapp_cloud',
WHATSAPP_EMBEDDED: 'whatsapp_embedded',
WHATSAPP_MANUAL: 'whatsapp_manual',
THREE_SIXTY_DIALOG: '360dialog',
};
@@ -29,14 +28,6 @@ const hasWhatsappAppId = computed(() => {
);
});
const isWhatsappEmbeddedSignupEnabled = computed(() => {
const accountId = route.params.accountId;
return store.getters['accounts/isFeatureEnabledonAccount'](
accountId,
FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP
);
});
const selectedProvider = computed(() => route.query.provider);
const showProviderSelection = computed(() => !selectedProvider.value);
@@ -66,28 +57,11 @@ const selectProvider = providerValue => {
});
};
const shouldShowEmbeddedSignup = provider => {
// Check if the feature is enabled for the account
if (!isWhatsappEmbeddedSignupEnabled.value) {
return false;
}
return (
(provider === PROVIDER_TYPES.WHATSAPP && hasWhatsappAppId.value) ||
provider === PROVIDER_TYPES.WHATSAPP_EMBEDDED
);
};
const shouldShowCloudWhatsapp = provider => {
// If embedded signup feature is enabled and app ID is configured, don't show cloud whatsapp
if (isWhatsappEmbeddedSignupEnabled.value && hasWhatsappAppId.value) {
return false;
}
// Show cloud whatsapp when:
// 1. Provider is whatsapp AND
// 2. Either no app ID is configured OR embedded signup feature is disabled
return provider === PROVIDER_TYPES.WHATSAPP;
return (
provider === PROVIDER_TYPES.WHATSAPP_MANUAL ||
(provider === PROVIDER_TYPES.WHATSAPP && !hasWhatsappAppId.value)
);
};
</script>
@@ -138,10 +112,45 @@ const shouldShowCloudWhatsapp = provider => {
<div v-else-if="showConfiguration">
<div class="px-6 py-5 rounded-2xl border bg-n-solid-2 border-n-weak">
<WhatsappEmbeddedSignup
v-if="shouldShowEmbeddedSignup(selectedProvider)"
/>
<!-- Show embedded signup if app ID is configured -->
<div
v-if="
hasWhatsappAppId && selectedProvider === PROVIDER_TYPES.WHATSAPP
"
>
<WhatsappEmbeddedSignup />
<!-- Manual setup fallback option -->
<div class="pt-6 mt-6 border-t border-n-weak">
<p class="mb-4 text-sm text-n-slate-11">
{{
$t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.MANUAL_FALLBACK')
}}
<Button
variant="link"
size="sm"
color="slate"
@click="selectProvider(PROVIDER_TYPES.WHATSAPP_MANUAL)"
>
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.MANUAL_SETUP_LINK'
)
}}
</Button>
{{
$t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.MANUAL_FALLBACK_INSTEAD'
)
}}
</p>
</div>
</div>
<!-- Show manual setup -->
<CloudWhatsapp v-else-if="shouldShowCloudWhatsapp(selectedProvider)" />
<!-- Other providers -->
<Twilio
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
type="whatsapp"
+1
View File
@@ -184,6 +184,7 @@
- name: whatsapp_embedded_signup
display_name: WhatsApp Embedded Signup
enabled: false
deprecated: true
- name: whatsapp_campaign
display_name: WhatsApp Campaign
enabled: false
@@ -16,31 +16,7 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
let(:agent) { create(:user, account: account, role: :agent) }
let(:administrator) { create(:user, account: account, role: :administrator) }
context 'when feature is not enabled' do
before do
account.disable_features!(:whatsapp_embedded_signup)
end
it 'returns forbidden' do
post "/api/v1/accounts/#{account.id}/whatsapp/authorization",
params: {
code: 'test_code',
business_id: 'test_business_id',
waba_id: 'test_waba_id'
},
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:forbidden)
expect(response.parsed_body['error']).to eq('WhatsApp embedded signup is not enabled for this account')
end
end
context 'when feature is enabled' do
before do
account.enable_features!(:whatsapp_embedded_signup)
end
context 'when authenticated user makes request' do
it 'returns unprocessable entity when code is missing' do
post "/api/v1/accounts/#{account.id}/whatsapp/authorization",
params: {
@@ -246,10 +222,6 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
context 'when user is not authorized for the account' do
let(:other_account) { create(:account) }
before do
account.enable_features!(:whatsapp_embedded_signup)
end
it 'returns unauthorized' do
post "/api/v1/accounts/#{other_account.id}/whatsapp/authorization",
params: {
@@ -265,10 +237,6 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
end
context 'when user is an administrator' do
before do
account.enable_features!(:whatsapp_embedded_signup)
end
it 'allows channel creation' do
embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
whatsapp_channel = create(:channel_whatsapp, account: account, validate_provider_config: false, sync_templates: false)
@@ -321,10 +289,6 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
context 'when user is an administrator' do
let(:administrator) { create(:user, account: account, role: :administrator) }
before do
account.enable_features!(:whatsapp_embedded_signup)
end
context 'with valid parameters' do
let(:valid_params) do
{
@@ -489,7 +453,6 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
let(:agent) { create(:user, account: account, role: :agent) }
before do
account.enable_features!(:whatsapp_embedded_signup)
create(:inbox_member, inbox: whatsapp_inbox, user: agent)
end
-57
View File
@@ -1,57 +0,0 @@
require 'rails_helper'
RSpec.describe Featurable do
let(:account) { create(:account) }
describe 'WhatsApp embedded signup feature' do
it 'is disabled by default' do
expect(account.feature_whatsapp_embedded_signup?).to be false
expect(account.feature_enabled?('whatsapp_embedded_signup')).to be false
end
describe '#enable_features!' do
it 'enables the whatsapp embedded signup feature' do
account.enable_features!(:whatsapp_embedded_signup)
expect(account.feature_whatsapp_embedded_signup?).to be true
expect(account.feature_enabled?('whatsapp_embedded_signup')).to be true
end
it 'enables multiple features at once' do
account.enable_features!(:whatsapp_embedded_signup, :help_center)
expect(account.feature_whatsapp_embedded_signup?).to be true
expect(account.feature_help_center?).to be true
end
end
describe '#disable_features!' do
before do
account.enable_features!(:whatsapp_embedded_signup)
end
it 'disables the whatsapp embedded signup feature' do
expect(account.feature_whatsapp_embedded_signup?).to be true
account.disable_features!(:whatsapp_embedded_signup)
expect(account.feature_whatsapp_embedded_signup?).to be false
end
end
describe '#enabled_features' do
it 'includes whatsapp_embedded_signup when enabled' do
account.enable_features!(:whatsapp_embedded_signup)
expect(account.enabled_features).to include('whatsapp_embedded_signup' => true)
end
it 'does not include whatsapp_embedded_signup when disabled' do
account.disable_features!(:whatsapp_embedded_signup)
expect(account.enabled_features).not_to include('whatsapp_embedded_signup' => true)
end
end
describe '#all_features' do
it 'includes whatsapp_embedded_signup in all features list' do
expect(account.all_features).to have_key('whatsapp_embedded_signup')
end
end
end
end