feat: Whatsapp embedded signup (#11612)

## Description

This PR introduces WhatsApp Embedded Signup functionality, enabling
users to connect their WhatsApp Business accounts through Meta's
streamlined OAuth flow without manual webhook configuration. This
significantly improves the user experience by automating the entire
setup process.

**Key Features:**

- Embedded signup flow using Facebook SDK and Meta's OAuth 2.0
- Automatic webhook registration and phone number configuration
- Enhanced provider selection UI with card-based design
- Real-time progress tracking during signup process
- Comprehensive error handling and user feedback


## Required Configuration

The following environment variables must be configured by administrators
before this feature can be used:
Super Admin Configuration (via
super_admin/app_config?config=whatsapp_embedded)

- `WHATSAPP_APP_ID`: The Facebook App ID for WhatsApp Business API
integration
- `WHATSAPP_CONFIGURATION_ID`: The Configuration ID for WhatsApp
Embedded Signup flow (obtained from Meta Developer Portal)
- `WHATSAPP_APP_SECRET`: The App Secret for WhatsApp Embedded Signup
flow (required for token exchange)
![Screenshot 2025-06-09 at 11 21
08 AM](https://github.com/user-attachments/assets/1615fb0d-27fc-4d9e-b193-9be7894ea93a)


## How Has This Been Tested?

#### Backend Tests (RSpec):

- Authentication validation for embedded signup endpoints
- Authorization code validation and error handling
- Missing business parameter validation
- Proper response format for configuration endpoint
- Unauthorized access prevention

#### Manual Test Cases:

- Complete embedded signup flow (happy path)
- Provider selection UI navigation
- Facebook authentication popup handling
- Error scenarios (cancelled auth, invalid business data, API failures)
- Configuration presence/absence behavior

## Related Screenshots:

![Screenshot 2025-06-09 at 7 48
18 PM](https://github.com/user-attachments/assets/34001425-df11-4d78-9424-334461e3178f)
![Screenshot 2025-06-09 at 7 48
22 PM](https://github.com/user-attachments/assets/c09f4964-3aba-4c39-9285-d1e8e37d0e33)
![Screenshot 2025-06-09 at 7 48
32 PM](https://github.com/user-attachments/assets/a34d5382-7a91-4e1c-906e-dc2d570c864a)
![Screenshot 2025-06-09 at 10 43
05 AM](https://github.com/user-attachments/assets/a15840d8-8223-4513-82e4-b08f23c95927)
![Screenshot 2025-06-09 at 10 42
56 AM](https://github.com/user-attachments/assets/8c345022-38b5-44c4-aba2-0cda81389c69)


Fixes
https://linear.app/chatwoot/issue/CW-2131/spec-for-whatsapp-cloud-channels-sign-in-with-facebook

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Tanmay Deep Sharma
2025-07-15 11:37:06 +07:00
committed by GitHub
parent 4378506a35
commit 61d10044a0
58 changed files with 2384 additions and 63 deletions

View File

@@ -0,0 +1,14 @@
/* global axios */
import ApiClient from '../ApiClient';
class WhatsappChannel extends ApiClient {
constructor() {
super('whatsapp', { accountScoped: true });
}
createEmbeddedSignup(params) {
return axios.post(`${this.baseUrl()}/whatsapp/authorization`, params);
}
}
export default new WhatsappChannel();

View File

@@ -36,6 +36,7 @@ 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',
};

View File

@@ -222,10 +222,17 @@
"DESC": "Start supporting your customers via WhatsApp.",
"PROVIDERS": {
"LABEL": "API Provider",
"WHATSAPP_EMBEDDED": "WhatsApp Business",
"TWILIO": "Twilio",
"WHATSAPP_CLOUD": "WhatsApp Cloud",
"WHATSAPP_CLOUD_DESC": "Quick setup through Meta",
"TWILIO_DESC": "Connect via Twilio credentials",
"360_DIALOG": "360Dialog"
},
"SELECT_PROVIDER": {
"TITLE": "Select your API provider",
"DESCRIPTION": "Choose your WhatsApp provider. You can connect directly through Meta which requires no setup, or connect through Twilio using your account credentials."
},
"INBOX_NAME": {
"LABEL": "Inbox Name",
"PLACEHOLDER": "Please enter an inbox name",
@@ -264,6 +271,28 @@
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
},
"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.",
"BENEFITS": {
"TITLE": "Benefits of Embedded Signup:",
"EASY_SETUP": "No manual configuration required",
"SECURE_AUTH": "Secure OAuth based authentication",
"AUTO_CONFIG": "Automatic webhook and phone number configuration"
},
"SUBMIT_BUTTON": "Connect with WhatsApp Business",
"AUTH_PROCESSING": "Authenticating with Meta",
"WAITING_FOR_BUSINESS_INFO": "Please complete business setup in the Meta window...",
"PROCESSING": "Setting up your WhatsApp Business Account",
"LOADING_SDK": "Loading Facebook SDK...",
"CANCELLED": "WhatsApp Signup was cancelled",
"SUCCESS_TITLE": "WhatsApp Business Account Connected!",
"WAITING_FOR_AUTH": "Waiting for authentication...",
"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"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the WhatsApp channel"
}

View File

@@ -47,6 +47,13 @@ export default {
this.currentInbox.provider === 'whatsapp_cloud'
);
},
// If the inbox is a whatsapp cloud inbox and the source is not embedded signup, then show the webhook details
shouldShowWhatsAppWebhookDetails() {
return (
this.isWhatsAppCloudInbox &&
this.currentInbox.provider_config?.source !== 'embedded_signup'
);
},
message() {
if (this.isATwilioInbox) {
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
@@ -66,7 +73,7 @@ export default {
)}`;
}
if (this.isWhatsAppCloudInbox) {
if (this.isWhatsAppCloudInbox && this.shouldShowWhatsAppWebhookDetails) {
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
'INBOX_MGMT.ADD.WHATSAPP.API_CALLBACK.SUBTITLE'
)}`;
@@ -113,8 +120,11 @@ export default {
:script="currentInbox.callback_webhook_url"
/>
</div>
<div v-if="isWhatsAppCloudInbox" class="w-[50%] max-w-[50%] ml-[25%]">
<p class="mt-8 font-medium text-n-slate-11">
<div
v-if="shouldShowWhatsAppWebhookDetails"
class="w-[50%] max-w-[50%] ml-[25%]"
>
<p class="mt-8 font-medium text-slate-700 dark:text-slate-200">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.API_CALLBACK.WEBHOOK_URL') }}
</p>
<woot-code lang="html" :script="currentInbox.callback_webhook_url" />

View File

@@ -86,6 +86,12 @@ export default {
selectedTabKey() {
return this.tabs[this.selectedTabIndex]?.key;
},
shouldShowWhatsAppConfiguration() {
return !!(
this.isAWhatsAppCloudChannel &&
this.inbox.provider_config?.source !== 'embedded_signup'
);
},
whatsAppAPIProviderName() {
if (this.isAWhatsAppCloudChannel) {
return this.$t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD');
@@ -137,7 +143,7 @@ export default {
this.isALineChannel ||
this.isAPIInbox ||
(this.isAnEmailChannel && !this.inbox.provider) ||
this.isAWhatsAppChannel ||
this.shouldShowWhatsAppConfiguration ||
this.isAWebWidgetInbox
) {
visibleToAllChannelTabs = [
@@ -383,7 +389,7 @@ export default {
<template>
<div
class="flex-grow flex-shrink w-full min-w-0 pl-0 pr-0 overflow-auto settings"
class="overflow-auto flex-grow flex-shrink pr-0 pl-0 w-full min-w-0 settings"
>
<SettingIntroBanner
:header-image="inbox.avatarUrl"
@@ -405,7 +411,7 @@ export default {
/>
</woot-tabs>
</SettingIntroBanner>
<section class="w-full max-w-6xl mx-auto">
<section class="mx-auto w-full max-w-6xl">
<MicrosoftReauthorize v-if="microsoftUnauthorized" :inbox="inbox" />
<FacebookReauthorize v-if="facebookUnauthorized" :inbox="inbox" />
<GoogleReauthorize v-if="googleUnauthorized" :inbox="inbox" />
@@ -735,7 +741,7 @@ export default {
:business-name="businessName"
@update="toggleSenderNameType"
/>
<div class="flex flex-col items-start gap-2 mt-2">
<div class="flex flex-col gap-2 items-start mt-2">
<NextButton
ghost
blue

View File

@@ -1,48 +1,156 @@
<script>
import PageHeader from '../../SettingsSubPageHeader.vue';
<script setup>
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';
export default {
components: {
PageHeader,
Twilio,
ThreeSixtyDialogWhatsapp,
CloudWhatsapp,
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',
THREE_SIXTY_DIALOG: '360dialog',
};
const hasWhatsappAppId = computed(() => {
return (
window.chatwootConfig?.whatsappAppId &&
window.chatwootConfig.whatsappAppId !== 'none'
);
});
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);
const showConfiguration = computed(() => Boolean(selectedProvider.value));
const availableProviders = computed(() => [
{
value: PROVIDER_TYPES.WHATSAPP,
label: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD'),
description: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD_DESC'),
icon: '/assets/images/dashboard/channels/whatsapp.png',
},
data() {
return {
provider: 'whatsapp_cloud',
};
{
value: PROVIDER_TYPES.TWILIO,
label: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO'),
description: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO_DESC'),
icon: '/assets/images/dashboard/channels/twilio.png',
},
]);
const selectProvider = providerValue => {
router.push({
name: route.name,
params: route.params,
query: { provider: 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;
};
</script>
<template>
<div
class="border border-n-weak bg-n-solid-1 rounded-t-lg border-b-0 h-full w-full p-6 col-span-6 overflow-auto"
class="overflow-auto col-span-6 p-6 w-full h-full rounded-t-lg border border-b-0 border-n-weak bg-n-solid-1"
>
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.WHATSAPP.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.WHATSAPP.DESC')"
/>
<div class="flex-shrink-0 flex-grow-0">
<label>
{{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.LABEL') }}
<select v-model="provider">
<option value="whatsapp_cloud">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD') }}
</option>
<option value="twilio">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO') }}
</option>
</select>
</label>
<div v-if="showProviderSelection">
<div class="mb-10 text-left">
<h1 class="mb-2 text-lg font-medium text-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.SELECT_PROVIDER.TITLE') }}
</h1>
<p class="text-sm leading-relaxed text-slate-11">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.SELECT_PROVIDER.DESCRIPTION') }}
</p>
</div>
<div class="flex gap-6 justify-start">
<div
v-for="provider in availableProviders"
:key="provider.value"
class="gap-6 px-5 py-6 w-96 rounded-2xl border transition-all duration-200 cursor-pointer border-n-weak hover:bg-n-slate-3"
@click="selectProvider(provider.value)"
>
<div class="flex justify-start mb-5">
<div
class="flex justify-center items-center rounded-full size-10 bg-n-alpha-2"
>
<img
:src="provider.icon"
:alt="provider.label"
class="object-contain size-[26px]"
/>
</div>
</div>
<div class="text-start">
<h3 class="mb-1.5 text-sm font-medium text-slate-12">
{{ provider.label }}
</h3>
<p class="text-sm text-slate-11">
{{ provider.description }}
</p>
</div>
</div>
</div>
</div>
<Twilio v-if="provider === 'twilio'" type="whatsapp" />
<ThreeSixtyDialogWhatsapp v-else-if="provider === '360dialog'" />
<CloudWhatsapp v-else />
<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)"
/>
<CloudWhatsapp v-else-if="shouldShowCloudWhatsapp(selectedProvider)" />
<Twilio
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
type="whatsapp"
/>
<ThreeSixtyDialogWhatsapp
v-else-if="selectedProvider === PROVIDER_TYPES.THREE_SIXTY_DIALOG"
/>
<CloudWhatsapp v-else />
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,327 @@
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import Icon from 'next/icon/Icon.vue';
import NextButton from 'next/button/Button.vue';
import LoadingState from 'dashboard/components/widgets/LoadingState.vue';
import { loadScript } from 'dashboard/helper/DOMHelpers';
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
const store = useStore();
const router = useRouter();
const { t } = useI18n();
// State
const fbSdkLoaded = ref(false);
const isProcessing = ref(false);
const processingMessage = ref('');
const authCodeReceived = ref(false);
const authCode = ref(null);
const businessData = ref(null);
const isAuthenticating = ref(false);
// Computed
const whatsappIconPath = '/assets/images/dashboard/channels/whatsapp.png';
const benefits = computed(() => [
{
key: 'EASY_SETUP',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.EASY_SETUP'),
},
{
key: 'SECURE_AUTH',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.SECURE_AUTH'),
},
{
key: 'AUTO_CONFIG',
text: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.BENEFITS.AUTO_CONFIG'),
},
]);
const showLoader = computed(() => isAuthenticating.value || isProcessing.value);
// Error handling
const handleSignupError = data => {
isProcessing.value = false;
authCodeReceived.value = false;
isAuthenticating.value = false;
const errorMessage =
data.error ||
data.message ||
t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
useAlert(errorMessage);
};
const handleSignupCancellation = () => {
isProcessing.value = false;
authCodeReceived.value = false;
isAuthenticating.value = false;
};
const handleSignupSuccess = inboxData => {
isProcessing.value = false;
isAuthenticating.value = false;
if (inboxData && inboxData.id) {
useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
router.replace({
name: 'settings_inboxes_add_agents',
params: {
page: 'new',
inbox_id: inboxData.id,
},
});
} else {
useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
router.replace({
name: 'settings_inbox_list',
});
}
};
// Signup flow
const completeSignupFlow = async businessDataParam => {
if (!authCodeReceived.value || !authCode.value) {
handleSignupError({
error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
});
return;
}
isProcessing.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
);
try {
const params = {
code: authCode.value,
business_id: businessDataParam.business_id,
waba_id: businessDataParam.waba_id,
phone_number_id: businessDataParam.phone_number_id,
};
const responseData = await store.dispatch(
'inboxes/createWhatsAppEmbeddedSignup',
params
);
authCode.value = null;
handleSignupSuccess(responseData);
} catch (error) {
const errorMessage =
parseAPIErrorResponse(error) ||
t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
handleSignupError({ error: errorMessage });
}
};
const isValidBusinessData = businessDataLocal => {
return (
businessDataLocal &&
businessDataLocal.business_id &&
businessDataLocal.waba_id
);
};
// Message handling
const handleEmbeddedSignupData = async data => {
if (data.event === 'FINISH') {
const businessDataLocal = data.data;
if (isValidBusinessData(businessDataLocal)) {
businessData.value = businessDataLocal;
if (authCodeReceived.value && authCode.value) {
await completeSignupFlow(businessDataLocal);
} else {
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_AUTH'
);
}
} else {
handleSignupError({
error: t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.INVALID_BUSINESS_DATA'
),
});
}
} else if (data.event === 'CANCEL') {
handleSignupCancellation();
} else if (data.event === 'error') {
handleSignupError({
error:
data.error_message ||
t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SIGNUP_ERROR'),
error_id: data.error_id,
session_id: data.session_id,
});
}
};
const fbLoginCallback = response => {
if (response.authResponse && response.authResponse.code) {
authCode.value = response.authResponse.code;
authCodeReceived.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
);
if (businessData.value) {
completeSignupFlow(businessData.value);
}
} else if (response.error) {
handleSignupError({ error: response.error });
} else {
isProcessing.value = false;
isAuthenticating.value = false;
useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
}
};
const handleSignupMessage = event => {
// Validate origin for security - following Facebook documentation
// https://developers.facebook.com/docs/whatsapp/embedded-signup/implementation#step-3--add-embedded-signup-to-your-website
if (!event.origin.endsWith('facebook.com')) return;
// Parse and handle WhatsApp embedded signup events
try {
const data = JSON.parse(event.data);
if (data.type === 'WA_EMBEDDED_SIGNUP') {
handleEmbeddedSignupData(data);
}
} catch {
// Ignore non-JSON or irrelevant messages
}
};
const runFBInit = () => {
window.FB.init({
appId: window.chatwootConfig?.whatsappAppId,
autoLogAppEvents: true,
xfbml: true,
version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
});
fbSdkLoaded.value = true;
};
const loadFacebookSdk = async () => {
return loadScript('https://connect.facebook.net/en_US/sdk.js', {
async: true,
defer: true,
crossOrigin: 'anonymous',
});
};
const tryWhatsAppLogin = () => {
isAuthenticating.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
);
window.FB.login(fbLoginCallback, {
config_id: window.chatwootConfig?.whatsappConfigurationId,
response_type: 'code',
override_default_response_type: true,
extras: {
setup: {},
featureType: '',
sessionInfoVersion: '3',
},
});
};
const launchEmbeddedSignup = async () => {
try {
// Load SDK first if not loaded, following Facebook.vue pattern exactly
await loadFacebookSdk();
runFBInit(); // Initialize FB after loading
// Now proceed with login
tryWhatsAppLogin();
} catch (error) {
handleSignupError({
error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SDK_LOAD_ERROR'),
});
}
};
// Lifecycle
const setupMessageListener = () => {
window.addEventListener('message', handleSignupMessage);
};
const cleanupMessageListener = () => {
window.removeEventListener('message', handleSignupMessage);
};
const initialize = () => {
window.fbAsyncInit = runFBInit;
setupMessageListener();
};
onMounted(() => {
initialize();
});
onBeforeUnmount(() => {
cleanupMessageListener();
});
</script>
<template>
<div class="h-full">
<LoadingState v-if="showLoader" :message="processingMessage" />
<div v-else>
<div class="flex flex-col items-start mb-6 text-start">
<div class="flex justify-start mb-6">
<div
class="flex justify-center items-center w-12 h-12 rounded-full bg-n-alpha-2"
>
<img
:src="whatsappIconPath"
:alt="$t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD')"
class="object-contain w-8 h-8"
draggable="false"
/>
</div>
</div>
<h3 class="mb-2 text-base font-medium text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.TITLE') }}
</h3>
<p class="text-sm leading-[24px] text-n-slate-12">
{{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.DESC') }}
</p>
</div>
<div class="flex flex-col gap-2 mb-6">
<div
v-for="benefit in benefits"
:key="benefit.key"
class="flex gap-2 items-center text-sm text-n-slate-11"
>
<Icon icon="i-lucide-check" class="text-n-slate-11 size-4" />
{{ benefit.text }}
</div>
</div>
<div class="flex mt-4">
<NextButton
:disabled="isAuthenticating"
:is-loading="isAuthenticating"
faded
slate
class="w-full"
@click="launchEmbeddedSignup"
>
{{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUBMIT_BUTTON') }}
</NextButton>
</div>
</div>
</div>
</template>

View File

@@ -5,6 +5,7 @@ import InboxesAPI from '../../api/inboxes';
import WebChannel from '../../api/channel/webChannel';
import FBChannel from '../../api/channel/fbChannel';
import TwilioChannel from '../../api/channel/twilioChannel';
import WhatsappChannel from '../../api/channel/whatsappChannel';
import { throwErrorMessage } from '../utils/api';
import AnalyticsHelper from '../../helper/AnalyticsHelper';
import camelcaseKeys from 'camelcase-keys';
@@ -198,6 +199,19 @@ export const actions = {
throw new Error(error);
}
},
createWhatsAppEmbeddedSignup: async ({ commit }, params) => {
try {
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: true });
const response = await WhatsappChannel.createEmbeddedSignup(params);
commit(types.default.ADD_INBOXES, response.data);
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: false });
sendAnalyticsEvent('whatsapp');
return response.data;
} catch (error) {
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: false });
throw error;
}
},
...channelActions,
// TODO: Extract other create channel methods to separate files to reduce file size
// - createChannel